Embeddings & Search
How Qdrant-backed embedding collections let the agent search its own knowledge base.
Most AI chat tools work in sessions. You open a new chat, ask a question, get an answer, and the context is gone. Start a new chat tomorrow and the agent has no idea what you talked about. If the conversation gets too long, it gets truncated or “compacted” into a summary. You manage the lifecycle manually: which chat am I in, what context is still alive, what did I lose when it got too long.
q15 takes a different position. There is one conversation. It never resets. There are no sessions to start, no context windows to manage, no “new chat” button. You send a message on Monday, another on Thursday, another next month — and the agent picks up where you left off because its memory persists across the gap.
The question is: how do you keep one infinite conversation inside a model with a finite context window?
Every LLM has a fixed context window. When you send a message to the model, the harness assembles a prompt: system instructions, identity, recent conversation history, and your new message. The model processes all of it at once.
If your conversation is 500 turns long, you cannot send all 500 turns every time. The context window fills up. Something has to be dropped.
The standard approach is compaction. When the transcript exceeds a threshold, the harness asks the model to summarize the older portion into a compressed paragraph, then keeps only recent turns verbatim. The summary replaces the original messages.
flowchart TD
subgraph compaction["Compaction cycle"]
T1["Turns 1–200<br/>(full transcript)"] --> S["Model summarizes<br/>turns 1–180"]
S --> Summary["Compressed summary<br/>(~500 tokens of prose)"]
Summary --> M1["Next prompt:<br/>summary + turns 181–200"]
T2["Turns 201–400<br/>(grows again)"] --> S2["Summarize again:<br/>summary + turns 181–380"]
S2 --> Summary2["New compressed summary"]
Summary2 --> M2["Next prompt:<br/>new summary + turns 381–400"]
end
style S fill:#fab387,stroke:#d97706,color:#1e1e2e
style S2 fill:#fab387,stroke:#d97706,color:#1e1e2e
style Summary fill:#313244,stroke:#89b4fa,color:#cdd6f4
style Summary2 fill:#313244,stroke:#89b4fa,color:#cdd6f4
This works, but it has problems:
q15 does not compress history. Instead, it maintains structured memory artifacts through background model calls called cognition jobs. These jobs run independently of the user’s conversation — on schedules and state-change triggers — and keep the memory layers current.
The full transcript is always persisted as JSON files under /memory/history/. Nothing is deleted. But only the recent unconsolidated turns are replayed into the prompt. Everything before the consolidation checkpoint is represented by the working memory artifact, which was built from those turns by a background job.
flowchart TD
subgraph session["One continuous conversation (no sessions)"]
U1["User message<br/>Monday"] --> R1["Agent reply"]
R1 --> P1["Persist turn to<br/>/memory/history/"]
P1 --> D1["Dirty state<br/>+1 turn"]
D1 -->|"6+ dirty turns"| C1["Working memory<br/>consolidation job"]
C1 --> CP1["Advance consolidation<br/>checkpoint to turn N"]
CP1 --> WR["Working memory artifact<br/>updated with active state"]
WR -->|"Next reply"| N1["Replay only turns<br/>after checkpoint"]
U2["User message<br/>Thursday"] --> R2["Agent reply"]
R2 --> N1
N1 --> D2["Dirty state<br/>+1 turn"]
D2 -->|"12+ dirty turns"| C2["Semantic memory<br/>extraction job"]
C2 --> SM["facts.md, preferences.md,<br/>projects.md updated"]
SM -->|"Available via tools"| N2["Agent can fetch<br/>on demand"]
end
style C1 fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style C2 fill:#cba6f7,stroke:#8839ef,color:#1e1e2e
style CP1 fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style WR fill:#313244,stroke:#89b4fa,color:#cdd6f4
style SM fill:#313244,stroke:#cba6f7,color:#cdd6f4
The key difference: compaction replaces history with a summary. Cognition jobs extract structured state and advance a checkpoint. The history is still on disk. The prompt carries the structured artifact plus only the recent tail that has not been consolidated yet.
The cognition system has three components: a controller that decides when to run jobs, a runner that executes them on the shared model/tool engine, and typed job definitions that describe what each job reads, writes, and how it validates its own output.
flowchart TD
subgraph interactive["Interactive loop (user-facing)"]
UM["User message"] --> Agent["Agent assembles prompt<br/>core + working memory + recent turns"]
Agent --> Model["Model call + tools"]
Model --> Reply["Reply to user"]
Model --> Persist["Persist turn to<br/>/memory/history/"]
Persist --> Notify["Notify controller"]
end
subgraph cognition["Cognition system (background)"]
Controller["Controller<br/>trigger evaluation"] --> Runner["Runner<br/>shared engine"]
Runner --> Job["Job definition<br/>build → run → apply"]
Job --> Artifacts["Memory artifacts<br/>+ checkpoints"]
end
Notify --> Controller
Artifacts -.->|"next prompt"| Agent
style Controller fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style Runner fill:#cba6f7,stroke:#8839ef,color:#1e1e2e
style Job fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style Artifacts fill:#313244,stroke:#89b4fa,color:#cdd6f4
style Notify fill:#f38ba8,stroke:#dc2626,color:#1e1e2e
The controller and the interactive loop share the same model client, tool registry, and model-selection planner. Cognition jobs can use a different model than the interactive loop — each job type can be configured with its own model override, or it inherits the current interactive model. This lets you assign a stronger reasoning model to verification, a large-context model to semantic extraction, and a lighter model to working memory consolidation.
Every cognition job follows the same five-phase lifecycle, enforced by the runner:
flowchart TD
Trigger["1. Trigger fires<br/>startup, schedule, or state"] --> Build["2. Build phase<br/>job loads context via ContextLoader"]
Build --> Run["3. Run phase<br/>model call on shared engine<br/>with scoped tools"]
Run --> Apply["4. Apply phase<br/>job validates output<br/>and persists artifacts"]
Apply --> Record["5. Record phase<br/>run record appended<br/>checkpoints advanced"]
style Trigger fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style Build fill:#cba6f7,stroke:#8839ef,color:#1e1e2e
style Run fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style Apply fill:#f9e2af,stroke:#df8e1d,color:#1e1e2e
style Record fill:#313244,stroke:#89b4fa,color:#cdd6f4
Build method loads its required context through the ContextLoader interface: memory files, transcript slices, checkpoints, and prior cognition artifacts. It assembles a Spec containing the objective, completion contract, prompt sections, allowed tools, and tool-call policy.ApplyResult method validates the output. Each job enforces a completion contract: working memory must touch its target file with a file tool; semantic extraction must inspect all three canonical files; verification must produce non-empty artifact content. If validation fails, the run is recorded as a failure and the dirty state is preserved for retry.RunRecord to /memory/cognition/runs/ with full provenance: job type, trigger cause, start/finish time, input/output sequence, model used, success/failure, summary, and any attempt failures. Checkpoints are advanced only on success.Each cognition job has three trigger types:
| Trigger | When it fires | Example |
|---|---|---|
| Startup | Once, when the controller starts | Consolidate any unconsolidated tail from before the last restart |
| Schedule | On a UTC cron spec | 0 4 * * * (daily 04:00) for working memory |
| State | When the dirty-turn count exceeds a threshold | 6+ unconsolidated turns for working memory |
State triggers fire opportunistically. Every time a turn is persisted, the controller is notified. It checks whether any job’s dirty-turn threshold has been met. If yes, the job runs immediately — no need to wait for the next cron tick.
sequenceDiagram
participant User
participant Agent as q15-agent
participant Store as Memory store
participant Ctrl as Cognition controller
participant Runner as Cognition runner
User->>Agent: Message
Agent->>Agent: Assemble prompt<br/>(core + working + recent turns)
Agent->>Agent: Call model, run tools
Agent->>Store: AppendTurn()
Store->>Ctrl: NotifyStateChange()
Ctrl->>Ctrl: Check dirty-turn thresholds
Note over Ctrl: 6+ dirty turns? Run working memory job
Ctrl->>Runner: Run consolidation job
Runner->>Runner: Load recent transcript<br/>+ working memory + verification
Runner->>Runner: Model call: consolidate state
Runner->>Store: Update WORKING_MEMORY.md
Ctrl->>Store: Advance consolidation checkpoint
Note over Ctrl: Next reply replays only<br/>turns after checkpoint
The controller runs serially — one job at a time — and never blocks the interactive loop. If a job is running when the user sends a new message, the reply uses the current (possibly slightly stale) memory artifacts. The next consolidation cycle will pick up the new turns.
Job type: verification_review
Triggers:
| Rule | Spec | Threshold |
|---|---|---|
| Schedule | 0 3,15 * * * (twice daily) |
— |
| State | dirty-tail threshold | 8+ turns |
What it reads:
| Input | Source | How loaded |
|---|---|---|
| Core memory | /memory/core/AGENT.md, USER.md, SOUL.md |
LoadCoreMemory |
| Working memory | /memory/working/WORKING_MEMORY.md |
LoadWorkingMemory |
| Transcript head state | /memory/history/state/head.json |
LoadHead |
| Consolidation checkpoint | /memory/history/state/consolidation_checkpoint.json |
LoadConsolidationCheckpoint |
| Prior verification review | /memory/cognition/state/verification_review.md |
LoadCognitionArtifact |
| Recent transcript (24 turns) | /memory/history/turns/ |
LoadRecentMessages |
What it produces:
| Output | How persisted |
|---|---|
| Verification review artifact (markdown) | StoreCognitionArtifact → /memory/cognition/state/verification_review.md |
Allowed tools: read_file, web_fetch, web_search — read-only. No mutating tools. The verification job never edits files directly. It can inspect files and fetch external evidence, but its output is the review artifact itself, produced as the model’s final response text.
Completion contract: The final response must be non-empty markdown. The framework persists it to the artifact path. The model is instructed to use the section order: Review Target, Assessment Summary, Issues Identified, Recommendations, Unresolved Items. If the response is empty, the run fails.
How its output is consumed: The verification review artifact is not read by the interactive agent. It is consumed exclusively by the other two cognition jobs:
verification_review_input and uses it as correction input: if the review flags a working memory entry as stale or unsupported, the consolidation job removes or rewrites it.flowchart TD
subgraph reads["Verification review reads"]
VR_WM["Working memory"] --> VR
VR_CM["Core memory"] --> VR
VR_HS["Head state"] --> VR
VR_CC["Consolidation checkpoint"] --> VR
VR_PV["Prior verification review"] --> VR
VR_RT["Recent transcript<br/>(24 turns)"] --> VR
end
VR["Verification review job"] --> Artifact["verification_review.md"]
Artifact -->|"correction input"| WM_C["Working memory consolidation"]
Artifact -->|"correction input"| SM_C["Semantic memory extraction"]
style VR fill:#f9e2af,stroke:#df8e1d,color:#1e1e2e
style Artifact fill:#313244,stroke:#89b4fa,color:#cdd6f4
style WM_C fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style SM_C fill:#cba6f7,stroke:#8839ef,color:#1e1e2e
Job type: working_memory.consolidate
Triggers:
| Rule | Spec | Threshold |
|---|---|---|
| Startup | unconsolidated tail | any |
| Schedule | 0 4 * * * (daily 04:00) |
— |
| State | dirty-tail threshold | 6+ turns |
What it reads:
| Input | Source | How loaded |
|---|---|---|
| Working memory | /memory/working/WORKING_MEMORY.md |
LoadWorkingMemory |
| Verification review | /memory/cognition/state/verification_review.md |
LoadCognitionArtifact |
| Recent transcript (16 turns) | /memory/history/turns/ |
LoadRecentMessages |
What it produces:
| Output | How persisted |
|---|---|
| Updated working memory artifact (markdown) | File tools → /memory/working/WORKING_MEMORY.md |
| Advanced consolidation checkpoint | StoreConsolidationCheckpoint → /memory/history/state/consolidation_checkpoint.json |
Allowed tools: read_file, write_file, edit_file, apply_patch — scoped to /memory/working/WORKING_MEMORY.md only. The job must touch its target file with a file tool before completing. A response without a target-file tool call is invalid and the run fails.
Completion contract: The model must call read_file, write_file, edit_file, or apply_patch on the target file. If the file needs changes, it updates it while preserving the canonical section structure: Current Priorities, Active Tasks, Open Threads, Recent Progress, Pending Checks, Temporary Context. If no change is needed, it calls read_file and leaves the file unchanged.
How its output is consumed: The working memory artifact is auto-injected into every prompt as a system prompt section. This is the primary mechanism for sessionless continuity. When the consolidation job succeeds and advances the checkpoint, the next reply replays only turns after the checkpoint boundary — the working memory artifact carries the state of everything before it.
flowchart TD
subgraph reads["Working memory consolidation reads"]
WM_WM["Current working memory"] --> WM
WM_VR["Verification review"] --> WM
WM_RT["Recent transcript<br/>(16 turns)"] --> WM
end
WM["Working memory consolidation job"] -->|"file tools"| WMF["WORKING_MEMORY.md<br/>updated"]
WM -->|"on success"| CP["Consolidation checkpoint<br/>advanced"]
WMF -->|"auto-injected"| Prompt["Next prompt<br/>system section"]
CP -->|"replay boundary"| Replay["Recent turns<br/>after checkpoint only"]
style WM fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style WMF fill:#313244,stroke:#a6e3a1,color:#cdd6f4
style CP fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style Prompt fill:#313244,stroke:#89b4fa,color:#cdd6f4
style Replay fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
Job type: semantic_memory.extract
Triggers:
| Rule | Spec | Threshold |
|---|---|---|
| Schedule | 0 5 * * * (daily 05:00) |
— |
| State | dirty-tail threshold | 12+ turns |
What it reads:
| Input | Source | How loaded |
|---|---|---|
| Semantic memory (3 files) | /memory/semantic/facts.md, preferences.md, projects.md |
LoadSemanticMemory |
| Working memory | /memory/working/WORKING_MEMORY.md |
LoadWorkingMemory |
| Verification review | /memory/cognition/state/verification_review.md |
LoadCognitionArtifact |
| Semantic extraction checkpoint | /memory/cognition/state/semantic_extraction_checkpoint.json |
LoadSemanticExtractionCheckpoint |
| Transcript since last checkpoint | /memory/history/turns/ |
LoadMessagesSinceSeq (or LoadLatestMessages fallback) |
What it produces:
| Output | How persisted |
|---|---|
Updated facts.md |
File tools → /memory/semantic/facts.md |
Updated preferences.md |
File tools → /memory/semantic/preferences.md |
Updated projects.md |
File tools → /memory/semantic/projects.md |
| Advanced semantic extraction checkpoint | StoreSemanticExtractionCheckpoint → /memory/cognition/state/semantic_extraction_checkpoint.json |
Allowed tools: read_file, write_file, edit_file, apply_patch — scoped to the three semantic memory paths only. The job must inspect all three files with file tools. Missing any target file invalidates the run.
Completion contract: The model must call a file tool on each of the three canonical files. If a file needs changes, it updates only that file while preserving the fixed section structure. If a file does not need changes, it calls read_file on it and leaves it unchanged. The job also runs a cross-file de-duplication pass: fact side clauses are removed from preferences.md and preference phrasing is removed from facts.md.
Canonical file structure:
| File | Required H2 headings | Content scope |
|---|---|---|
facts.md |
Confirmed Facts, Grounded Inferences | Objective durable facts: biography, capabilities, tool/runtime facts |
preferences.md |
User Preferences, Collaboration Preferences | Durable subjective preferences: likes, dislikes, styles, choices |
projects.md |
Active Projects, Durable Project Knowledge | Stable project knowledge beyond the working-memory window |
How its output is consumed: Semantic memory is not auto-injected. The interactive agent fetches it on demand using the read_file tool when it determines that durable context is relevant. This keeps the prompt bounded while still making long-term knowledge available. The system prompt tells the agent where these files live and what headings they use, so it knows when and how to look them up.
flowchart TD
subgraph reads["Semantic memory extraction reads"]
SM_SF["Semantic files<br/>(facts, preferences, projects)"] --> SM
SM_WM["Working memory"] --> SM
SM_VR["Verification review"] --> SM
SM_CP["Semantic extraction checkpoint"] --> SM
SM_RT["Transcript since<br/>last checkpoint"] --> SM
end
SM["Semantic memory extraction job"] -->|"file tools"| F1["facts.md"]
SM -->|"file tools"| F2["preferences.md"]
SM -->|"file tools"| F3["projects.md"]
SM -->|"on success"| SCP["Semantic extraction<br/>checkpoint advanced"]
F1 -.->|"tool-fetched on demand"| Agent1["Interactive agent<br/>read_file"]
F2 -.->|"tool-fetched on demand"| Agent2["Interactive agent<br/>read_file"]
F3 -.->|"tool-fetched on demand"| Agent3["Interactive agent<br/>read_file"]
style SM fill:#cba6f7,stroke:#8839ef,color:#1e1e2e
style F1 fill:#313244,stroke:#cba6f7,color:#cdd6f4
style F2 fill:#313244,stroke:#cba6f7,color:#cdd6f4
style F3 fill:#313244,stroke:#cba6f7,color:#cdd6f4
style SCP fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style Agent1 fill:#f38ba8,stroke:#dc2626,color:#1e1e2e
style Agent2 fill:#f38ba8,stroke:#dc2626,color:#1e1e2e
style Agent3 fill:#f38ba8,stroke:#dc2626,color:#1e1e2e
The three jobs produce artifacts that reach the interactive agent through four distinct consumption paths. Understanding which path each artifact uses is key to understanding how the sessionless workflow stays bounded.
flowchart TD
subgraph jobs["Cognition jobs produce artifacts"]
VR["Verification review"] --> VRA["verification_review.md"]
WM["Working memory consolidation"] --> WMA["WORKING_MEMORY.md"]
SM["Semantic memory extraction"] --> SMA["facts.md<br/>preferences.md<br/>projects.md"]
end
subgraph consumption["Consumption paths"]
VRA -->|"cognition-internal<br/>never reaches prompt"| VR_C["Read by next<br/>consolidation + extraction"]
WMA -->|"auto-injected<br/>every turn"| Prompt["System prompt section"]
SMA -->|"tool-fetched<br/>on demand"| Tools["read_file call<br/>by interactive agent"]
History["Full transcript<br/>on disk"] -->|"checkpoint replay<br/>bounded window"| Prompt
end
subgraph vector["Vector search path"]
SMA -->|"optional sync"| Qdrant["Qdrant collection<br/>'semantic'"]
Core["Core memory files"] -->|"optional sync"| Qdrant2["Qdrant collection<br/>'core'"]
Library["Library content"] -->|"optional sync"| Qdrant3["Qdrant collection<br/>'library'"]
Zettel["Zettelkasten notes"] -->|"optional sync"| Qdrant4["Qdrant collection<br/>'zettelkasten'"]
Qdrant -->|"embed_search<br/>hybrid query"| Agent["Interactive agent"]
Qdrant2 --> Agent
Qdrant3 --> Agent
Qdrant4 --> Agent
end
style VR fill:#f9e2af,stroke:#df8e1d,color:#1e1e2e
style WM fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style SM fill:#cba6f7,stroke:#8839ef,color:#1e1e2e
style Prompt fill:#313244,stroke:#89b4fa,color:#cdd6f4
style Qdrant fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style Agent fill:#f38ba8,stroke:#dc2626,color:#1e1e2e
Core memory files and the working memory artifact are injected into the system prompt on every turn. They are always present. The agent does not need to call any tool to access them.
| Artifact | Path | Injected as |
|---|---|---|
| Core memory files | /memory/core/*.md |
System prompt section |
| Working memory artifact | /memory/working/WORKING_MEMORY.md |
System prompt section |
Semantic memory files and zettelkasten notes are not injected. The system prompt tells the agent where they live and what structure they use, but the agent must call read_file to access them. This keeps the prompt bounded — only the artifacts that are likely relevant to the current turn are loaded.
| Artifact | Path | Accessed via |
|---|---|---|
| Semantic memory files | /memory/semantic/*.md |
read_file tool call |
| Zettelkasten notes | /memory/notes/zettel/*.md |
read_file tool call |
| Zettelkasten inbox | /memory/notes/inbox/*.md |
read_file tool call |
| Structure maps | /memory/notes/maps/*.md |
read_file tool call |
The full transcript is persisted on disk under /memory/history/turns/. It is never deleted. But only the recent unconsolidated tail is replayed into the prompt. The consolidation checkpoint marks the boundary: everything before it is represented by the working memory artifact; everything after it is replayed verbatim.
flowchart TD
subgraph disk["Full transcript on disk (never deleted)"]
Old["Turns 1 to 47<br/>(before checkpoint)"]
New["Turns 48 to 50<br/>(after checkpoint)"]
end
CP["Consolidation checkpoint<br/>at turn 47"]
CP -.->|"marks the boundary"| New
Old -.->|"represented by"| WM["WORKING_MEMORY.md"]
New -->|"replayed into"| Prompt["Goes into prompt"]
WM -->|"injected into"| Prompt
style CP fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style Old fill:#313244,stroke:#585b70,color:#cdd6f4
style New fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style WM fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style Prompt fill:#313244,stroke:#89b4fa,color:#cdd6f4
The transcript sequence metadata lives at /memory/history/state/head.json. The consolidation checkpoint lives at /memory/history/state/consolidation_checkpoint.json. On startup, existing history is eagerly upgraded in place to the current schema. Unreadable turn files are moved to quarantine, not dropped.
When embeddings are configured, memory content can be indexed into Qdrant and searched semantically. This is a fourth consumption path that supplements the others — the agent can call embed_search to find relevant content across collections without knowing which file to read.
| Collection | Source content | Search tool |
|---|---|---|
semantic |
Semantic memory files (facts.md, preferences.md, projects.md) |
embed_search |
core |
Core memory files (AGENT.md, USER.md, SOUL.md) |
embed_search |
library |
Books, articles, and documents from /workspace/library/ |
embed_search |
zettelkasten |
Atomic knowledge notes from /memory/notes/zettel/ |
embed_search |
Search runs in hybrid mode by default: dense Gemini vectors for semantic recall combined with Qdrant BM25 sparse vectors for lexical precision. The agent calls embed_search with a natural-language query and receives ranked results with collection labels, file paths, and relevance scores.
flowchart TD
subgraph sources["Memory content sources"]
SM_F["Semantic memory<br/>files"] --> Sync["embed_sync"]
Core_F["Core memory<br/>files"] --> Sync
Lib["Library<br/>/workspace/library/"] --> Sync
Zet["Zettelkasten<br/>/memory/notes/zettel/"] --> Sync
end
Sync --> Qdrant["Qdrant<br/>vector database"]
Qdrant -->|"dense + sparse<br/>hybrid index"| Indexed["Indexed<br/>embeddings + BM25"]
Indexed -->|"embed_search<br/>query"| Search["Agent calls<br/>embed_search"]
Search --> Results["Ranked results<br/>with file paths<br/>+ relevance scores"]
Results --> Agent["Agent reads<br/>relevant files"]
style Qdrant fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style Sync fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style Search fill:#f38ba8,stroke:#dc2626,color:#1e1e2e
style Results fill:#313244,stroke:#89b4fa,color:#cdd6f4
Embeddings are optional. When not configured, the agent relies on the other three consumption paths. When configured, the semantic and core collections let the agent search its own memory semantically — finding relevant facts, preferences, or identity context without manually reading every file. The library and zettelkasten collections extend search to the broader knowledge base.
See the embeddings documentation for configuration details.
Each cognition job type can be assigned a specific model, independent of the interactive model. Jobs without an override inherit the current interactive model. This lets you optimize for the specific demands of each job:
| Job type | Typical demand | Suggested model profile |
|---|---|---|
verification_review |
Strong reasoning, critical analysis | Highest-quality reasoning model |
semantic_memory.extract |
Large context (3 files + transcript + verification), complex categorization | Large context window, strong instruction following |
working_memory.consolidate |
Bounded context (1 file + 16 turns), simpler single-file update | Lighter, faster model — runs most frequently |
The runner shares the same engine infrastructure as the interactive loop (model client, tool registry, model-selection planner). Cognition model refs are configured at startup. If no cognition model is specified, the interactive model is used for all jobs.
Every cognition run is recorded as an append-only JSON file under /memory/cognition/runs/YYYY/MM/DD/. Each record contains:
| Field | Description |
|---|---|
type |
Job type (verification_review, semantic_memory.extract, working_memory.consolidate) |
cause |
Trigger cause (kind, rule ID, fired-at, scheduled-for, reason) |
started_at |
Run start time (UTC) |
finished_at |
Run finish time (UTC) |
input_seq |
Transcript head sequence at run start |
output_seq |
Transcript head sequence at run finish |
succeeded |
Whether the run completed successfully |
summary |
Short model-produced summary of what changed |
metadata |
Job-specific metadata (file paths, changed flags) |
model_ref |
Which model was used |
attempt_failures |
Failed model attempts within the run (model ref + error) |
error |
Error message if the run failed |
Job trigger state (last run, dirty tracking, consecutive failures, scheduled-for times) is persisted per job type under /memory/cognition/triggers/jobs/<job_type>.json. This state survives restarts and lets the controller resume where it left off.
The three jobs form a self-correcting loop. The verification job audits state; the consolidation and extraction jobs apply corrections. This cycle runs continuously in the background, keeping memory artifacts accurate without any user intervention.
flowchart TD
VR["Verification review<br/>audits state"] -->|"flags stale<br/>unsupported<br/>contradicted entries"| WM["Working memory<br/>consolidation"]
VR -->|"flags stale<br/>unsupported<br/>contradicted entries"| SM["Semantic memory<br/>extraction"]
WM -->|"removes or rewrites<br/>flagged entries"| WMF["WORKING_MEMORY.md<br/>corrected"]
SM -->|"removes, rewrites,<br/>or downgrades"| SMF["facts.md<br/>preferences.md<br/>projects.md<br/>corrected"]
WM -->|"updates active state"| SM
WMF -->|"next turn"| Prompt["Prompt"]
SMF -.->|"on demand"| Prompt
style VR fill:#f9e2af,stroke:#df8e1d,color:#1e1e2e
style WM fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style SM fill:#cba6f7,stroke:#8839ef,color:#1e1e2e
style WMF fill:#313244,stroke:#a6e3a1,color:#cdd6f4
style SMF fill:#313244,stroke:#cba6f7,color:#cdd6f4
style Prompt fill:#313244,stroke:#89b4fa,color:#cdd6f4
The correction cycle has a specific ordering:
This ordering is not hardcoded — it emerges from the trigger thresholds and cron schedules. The state triggers fire opportunistically when dirty-turn counts cross thresholds, so a burst of conversation can trigger all three jobs in sequence within a few minutes.
/memory/cognition/ is system-owned state. The agent does not read or write here during interactive turns. It contains:
/memory/cognition/├── state/│ ├── verification_review.md # Latest verification review artifact│ └── semantic_extraction_checkpoint.json # Semantic extraction boundary├── triggers/│ └── jobs/│ ├── verification_review.json # Trigger state per job type│ ├── semantic_memory.extract.json│ └── working_memory.consolidate.json└── runs/ └── YYYY/MM/DD/ └── HHMMSS.<job_type>.json # Append-only run recordsThe consolidation checkpoint lives under /memory/history/state/consolidation_checkpoint.json alongside the transcript head state, because it is a replay boundary — it belongs to the history layer, not the cognition layer.
Here is what actually goes into the model’s context on every turn, ordered from most stable to least stable:
flowchart TD
subgraph prompt["System messages (cached prefix)"]
SP["System prompt<br/>code-owned execution policy"]
CM["Core memory<br/>AGENT.md · USER.md · SOUL.md"]
SC["Skill catalog<br/>available skills + descriptions"]
WM["Working memory<br/>WORKING_MEMORY.md"]
end
subgraph ctx["Conversation context"]
RT["Recent transcript<br/>turns after consolidation checkpoint<br/>(bounded replay window)"]
UM["Current user message<br/>+ temporal metadata"]
end
SP --> CM --> SC --> WM --> RT --> UM
style SP fill:#89b4fa,stroke:#2563eb,color:#1e1e2e
style CM fill:#cba6f7,stroke:#8839ef,color:#1e1e2e
style SC fill:#fab387,stroke:#d97706,color:#1e1e2e
style WM fill:#a6e3a1,stroke:#16a34a,color:#1e1e2e
style RT fill:#313244,stroke:#89b4fa,color:#cdd6f4
style UM fill:#f38ba8,stroke:#dc2626,color:#1e1e2e
AGENT.md, USER.md, SOUL.md. Agent identity, user profile, voice and principles. Auto-injected every turn. These are durable files that change rarely.WORKING_MEMORY.md. Bounded active state: current priorities, active tasks, open threads, recent progress, pending checks. Auto-injected every turn. This is the artifact maintained by the consolidation cognition job.The ordering is deliberate: most stable first, least stable last. This lets providers cache the prefix across working memory changes — the system prompt and core memory rarely change, so the cached prefix can be reused even when working memory updates.
| Layer | Path | Purpose | Auto-injected? | Maintained by |
|---|---|---|---|---|
| Core | /memory/core/ |
Agent identity, personality, self-model | Yes, every turn | Operator (you) |
| Working | /memory/working/WORKING_MEMORY.md |
Bounded active state | Yes, every turn | Consolidation job |
| Semantic | /memory/semantic/ |
Durable extracted knowledge | No (tool-fetched) | Semantic extraction job |
| History | /memory/history/ |
Full episodic transcript as JSON | No (replayed) | Append on every turn |
| Cognition | /memory/cognition/ |
Job state, run records, review artifacts | No (system-owned) | Cognition controller |
| Notes | /memory/notes/ |
Zettelkasten notebook | No (tool-fetched) | Agent on demand |
Files under /memory/core/ define who the agent is. These are always in the prompt:
AGENT.md — role and behavioral protocolUSER.md — user identity, preferences, communication normsSOUL.md — voice, teaching style, working principlesThese are durable identity files. They change when you change them — not automatically. The cognition jobs never write to core memory. This is deliberate: your agent’s identity is not something a background model call should rewrite.
/memory/working/WORKING_MEMORY.md holds bounded active state: what we are working on right now, what is unresolved, what was recently done, what needs checking. It is auto-injected into every turn and is the primary mechanism for sessionless continuity.
The consolidation job keeps it compact. When a task is resolved, the job removes it. When a thread is abandoned, the job drops it. When the user adds a new constraint, the job captures it. The working memory artifact is the agent’s scratch pad for the current moment — not a history log.
Other files under /memory/working/ are not prompt-visible. Only WORKING_MEMORY.md is auto-injected.
Three canonical files hold durable extracted knowledge:
facts.md — confirmed facts and grounded inferencespreferences.md — user preferences and collaboration preferencesprojects.md — active projects and durable project knowledgeSemantic memory is not auto-injected. The agent fetches it on demand using read_file when it determines that durable context is relevant. This keeps the prompt bounded while still making long-term knowledge available.
The semantic extraction job maintains these files. It promotes only explicit, durable statements or claims corroborated by repeated evidence. It does not promote single-turn temporary tasks or speculative guesses. If the verification review flags a semantic entry as stale or contradicted, the extraction job removes or rewrites it.
Completed turns are stored as JSON files under /memory/history/turns/YYYY/MM/DD/. The full transcript persists forever. Nothing is deleted.
The replay window is checkpoint-aware. When the consolidation job succeeds, it advances a checkpoint recording the last consolidated turn sequence number. The next LoadRecentMessages call starts from that checkpoint — it replays only turns after it. If no checkpoint exists (first boot), it falls back to the last N turns.
The semantic extraction job has its own independent checkpoint at /memory/cognition/state/semantic_extraction_checkpoint.json. It loads turns since its last checkpoint, not since the consolidation checkpoint. This means semantic extraction can process a different (typically larger) slice of transcript than working memory consolidation.
/memory/cognition/ is system-owned state. The agent does not read or write here during interactive turns. It contains:
state/ — verification review artifact, semantic extraction checkpointtriggers/jobs/ — per-job trigger state (last run, dirty tracking, consecutive failures)runs/ — append-only run records with full provenance (model used, input/output sequence, success/failure, summary, attempt failures)/memory/notes/ contains the auxiliary zettelkasten notebook:
notes/inbox/ — incoming notesnotes/zettel/ — atomic knowledge notesnotes/maps/ — structure maps and indexesThese are not prompt-visible. They are durable knowledge infrastructure that the agent can search and reference on demand, often backed by embedding collections.
| Compaction | Cognition jobs | |
|---|---|---|
| What happens to old turns | Summarized into prose, then dropped from prompt | Persisted on disk forever; represented by structured artifacts |
| What the prompt carries | Summary paragraph + recent turns | Structured working memory + recent unconsolidated turns |
| Lossiness | Lossy — detail erodes with each compaction cycle | Non-lossy — full transcript on disk; structured extraction is additive |
| Structure | Free-form prose, no schema | Canonical headings, typed files, enforced section structure |
| Verifiability | Cannot query what was lost | Full run records with model, input/output seq, success/failure |
| Correction | No mechanism — errors in summary compound | Verification job flags stale/unsupported claims; next cycle corrects them |
| When it runs | Blocking, during the user’s turn | Background, between turns |
| Failure mode | Conversation stalls if summarization fails | Job fails, dirty state preserved, retries on next trigger |
| Model choice | Same model as conversation | Per-job model override or interactive model inheritance |
When you talk to q15, you are not managing sessions. You are having one conversation with an agent that remembers. The working memory artifact carries your current thread forward. The semantic memory files carry durable facts about you and your projects. The full transcript is on disk if anything ever needs to be revisited. And all of this is maintained by background jobs that run while you are not looking — not by compressing your words into a paragraph, but by extracting structured state that the next turn can use.
The verification job audits that state for accuracy. The consolidation job keeps the active-state artifact current. The extraction job promotes durable knowledge. The correction cycle between them means memory does not just persist — it gets better over time, as stale entries are flagged and corrected without any user intervention.
Embeddings & Search
How Qdrant-backed embedding collections let the agent search its own knowledge base.
Architecture
The three-service model and how memory fits into the turn flow.
Workspace
The durable project tree that pairs with memory for long-running work.