feat(memory): Spec 23 — Memory Pipeline — Complete (all 6 phases) - #83
Conversation
- Add /add_direct and /add_batch sidecar endpoints that embed + store directly in Qdrant, bypassing Mem0's LLM re-extraction - Content hash + semantic dedup (cosine > 0.90) prevents duplicates - Create MemoryFlushTarget implementation in aletheia.ts calling /add_batch - Wire memoryTarget through NousManager to all 3 distillSession call sites - Wire memoryTarget to nightly reflection cron - Expose memoryTarget on AletheiaRuntime interface - Confidence score stored per memory (default 0.8 for distillation) Previously, distillation extracted facts via Haiku but dropped them — memoryTarget was defined as an interface but never implemented or passed. The system generated zero persistent memories from conversations. Tested: sidecar endpoints verified (add, dedup, batch). TypeScript compiles clean. Extract tests pass (17/17).
- New turn-facts.ts module: lightweight Haiku extraction of 0-3 durable facts per turn, with noise pattern filtering and length constraints - Wired into finalize.ts: async, non-blocking, fires after each turn with >150 char response. Stores via sidecar /add_batch (source: turn) - Added memoryTarget to RuntimeServices + pipeline types - Manager buildServices() now passes memoryTarget to pipeline stages - Confidence 0.7 for turn facts (lower than distillation's 0.8) - 9 tests: extraction, noise filtering, dedup, markdown parsing, graceful failure, tool summary passthrough Memory is now fresh: facts appear in recall within seconds of a turn completing, not hours later at distillation time.
- Add post-extraction noise filtering with regex patterns based on actual garbage found in Qdrant corpus audit (Uses X, Familiar with, Ran grep, The user asked, etc.) - Min/max length constraints (15-300 chars) catch trivially short and unreasonably long extractions - Enhanced EXTRACTION_PROMPT with real BAD/GOOD examples from corpus audit showing exactly what to avoid and what to produce - Noise filtering applies to facts and decisions (not openItems or keyEntities which have different quality characteristics) - Updated existing tests with realistic-length fact strings - Added 2 dedicated noise filtering tests (19 total, all pass)
…tial) - New entity_resolver.py: canonical entity registry with fuzzy matching - Known alias table for Aletheia-specific entities (aletheia_runtime → aletheia, etc.) - 50+ stopword filter prevents garbage entity creation - SequenceMatcher fuzzy matching (threshold 0.85) finds near-duplicates - merge_duplicate_entities() and cleanup_orphan_entities() Neo4j operations - Validates APOC availability for relationship transfer during merges Still needed: API endpoints, integration into extraction flow, tests.
…rtial) - Replace simple exact-text dedup with MMR-style diversity selection - Jaccard token overlap penalty prevents 'N memories saying the same thing' - Lambda=0.7 balances relevance (70%) and diversity (30%) - Exact-text dedup still happens first (preserves existing behavior) - Zero latency cost — operates on text tokens, no vector fetch needed - All 12 recall tests pass
…lifecycle (correct/forget/contradictions), thread-aware recall Phase 4b: Entity resolution API - POST /entities/resolve — resolve names to canonical forms via alias table + fuzzy matching - POST /entities/merge_duplicates — find and merge duplicate Neo4j entity nodes - POST /entities/cleanup_orphans — remove orphan entity nodes - Entity resolution wired into /add_direct and /add_batch — resolved entities stored in payload Phase 5: Memory lifecycle - Contradiction detection in /add_direct and /add_batch — checks cosine > 0.80, detects negation asymmetry - POST /memory/correct — supersede old memory (confidence → 0.1), store corrected version (confidence 0.95) - POST /memory/forget — soft-delete by setting confidence=0.0 + forgotten flag, with dry_run support - memory_correct and memory_forget agent tools registered in runtime - Search endpoint filters out forgotten and superseded memories - Tools classified as destructive in reversibility system Phase 6b: Thread-aware recall - recallMemories() now accepts optional threadSummary parameter - Thread summary appended to search query for broader context matching - Wired in context.ts — existing thread summary passed to recall automatically
|
| result: dict[str, Any] = {"ok": True, "result": "added", "id": point_id} | ||
| if contradictions: | ||
| result["contradictions"] = contradictions | ||
| logger.info(f"add_direct: stored '{text[:80]}' with {len(contradictions)} contradiction(s)") |
Check failure
Code scanning / CodeQL
Log Injection High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 6 months ago
In general, to fix log injection when logging user-provided data, ensure that the data is sanitized before logging: remove or replace newline (\n, \r) and other control characters that can break log structure, or encode the data (e.g., using repr() or similar) so that control characters are rendered harmlessly.
For this specific case, the best low-impact fix is to sanitize the text snippet used in the log message before passing it to logger.info. We can do this locally in the add_direct_v2 function without changing external behavior: define a short sanitized snippet from text[:80] that strips or replaces \r and \n (and optionally other control characters) and then interpolate that sanitized value into the log message. This avoids altering what is stored in Qdrant or how the API behaves; it only affects how the text appears in logs.
Concretely, in infrastructure/memory/sidecar/aletheia_memory/routes.py, inside add_direct_v2, right before the first logger.info call (line 1956), we can create a variable like:
safe_snippet = text[:80].replace("\r", "\\r").replace("\n", "\\n")or alternatively strip them. Then use safe_snippet in both log lines:
logger.info(f"add_direct: stored '{safe_snippet}' with {len(contradictions)} contradiction(s)")
...
logger.info(f"add_direct: stored '{safe_snippet}' (source={req.source}, agent={req.agent_id})")This keeps semantics similar (the log still shows the same characters, just with newlines escaped), prevents log injection, and requires no new imports or additional helper functions.
| @@ -1951,11 +1951,17 @@ | ||
| ) | ||
|
|
||
| result: dict[str, Any] = {"ok": True, "result": "added", "id": point_id} | ||
| # Sanitize user-provided text before logging to prevent log injection | ||
| safe_snippet = text[:80].replace("\r", "\\r").replace("\n", "\\n") | ||
| if contradictions: | ||
| result["contradictions"] = contradictions | ||
| logger.info(f"add_direct: stored '{text[:80]}' with {len(contradictions)} contradiction(s)") | ||
| logger.info( | ||
| f"add_direct: stored '{safe_snippet}' with {len(contradictions)} contradiction(s)" | ||
| ) | ||
| else: | ||
| logger.info(f"add_direct: stored '{text[:80]}' (source={req.source}, agent={req.agent_id})") | ||
| logger.info( | ||
| f"add_direct: stored '{safe_snippet}' (source={req.source}, agent={req.agent_id})" | ||
| ) | ||
|
|
||
| return result | ||
|
|
| result["contradictions"] = contradictions | ||
| logger.info(f"add_direct: stored '{text[:80]}' with {len(contradictions)} contradiction(s)") | ||
| else: | ||
| logger.info(f"add_direct: stored '{text[:80]}' (source={req.source}, agent={req.agent_id})") |
Check failure
Code scanning / CodeQL
Log Injection High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 6 months ago
In general, to fix log injection, any user-controlled data written to logs should be sanitized so that control characters (notably \r and \n) cannot alter the structure of the log output. A common approach for plain-text logs is to remove or replace newline and carriage-return characters from untrusted fields before interpolating them into the log message. Alternatively, one can use a helper function to sanitize any string before logging.
For this specific code, the issue is the logger.info call on line 1958:
logger.info(f"add_direct: stored '{text[:80]}' (source={req.source}, agent={req.agent_id})")We should avoid logging req.source and req.agent_id directly. The minimal, behavior-preserving fix is:
- Introduce local sanitized variables derived from
req.sourceandreq.agent_idthat remove\rand\n(and safely handleNone). - Use those sanitized variables in the log message instead of the raw request fields.
No external dependencies are needed; we can rely on standard string methods. The changes are localized to the add_direct_v2 function body in infrastructure/memory/sidecar/aletheia_memory/routes.py, near the existing log statement.
| @@ -1955,7 +1955,11 @@ | ||
| result["contradictions"] = contradictions | ||
| logger.info(f"add_direct: stored '{text[:80]}' with {len(contradictions)} contradiction(s)") | ||
| else: | ||
| logger.info(f"add_direct: stored '{text[:80]}' (source={req.source}, agent={req.agent_id})") | ||
| safe_source = (req.source or "").replace("\r", "").replace("\n", "") | ||
| safe_agent = (req.agent_id or "").replace("\r", "").replace("\n", "") | ||
| logger.info( | ||
| f"add_direct: stored '{text[:80]}' (source={safe_source}, agent={safe_agent})" | ||
| ) | ||
|
|
||
| return result | ||
|
|
| ], | ||
| ) | ||
|
|
||
| logger.info(f"memory/correct: '{old_text[:60]}' → '{corrected_text[:60]}' (reason: {req.reason})") |
Check failure
Code scanning / CodeQL
Log Injection High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 6 months ago
In general, to fix log injection in this context, all user-controlled values interpolated into log messages should be sanitized to remove or neutralize control characters that can affect log structure (especially \r and \n). This can be done by replacing these characters with safe alternatives (such as a space or empty string) before logging, and ideally centralizing this behavior in a small helper to avoid repetition and keep behavior consistent.
For this specific instance, we should ensure that both corrected_text and req.reason are sanitized for logging. old_text is persisted data and less critical, but for defense in depth we can sanitize all three values before constructing the log line. The minimally invasive and clear approach is:
- Define a small helper function, e.g.
_sanitize_for_log(value: str) -> str, near the top of this module (within the snippet we control) that:- Returns the input with
\rand\nremoved or replaced (e.g.,.replace("\r", "\\r").replace("\n", "\\n")or stripped out). - Handles
Nonegracefully by returning an empty string or a representation like"<none>".
- Returns the input with
- At the start of
correct_memory, or right before thelogger.infocall, derive sanitized versions of the strings:safe_old = _sanitize_for_log(old_text[:60])safe_corrected = _sanitize_for_log(corrected_text[:60])safe_reason = _sanitize_for_log(req.reason or "")(if reason can be optional)
- Update the
logger.infocall to use these sanitized values instead of the raw user-controlled substrings.
This preserves existing functionality (messages and truncation semantics stay the same aside from control-character handling) while preventing users from injecting newlines or other problematic characters into the logs. It requires no new external dependencies—just a helper function in the same file.
| @@ -37,6 +37,18 @@ | ||
| router = APIRouter() | ||
|
|
||
|
|
||
| def _sanitize_for_log(value: Any) -> str: | ||
| """ | ||
| Sanitize a value for safe logging by removing newline characters that could | ||
| be used for log injection. Converts non-string values to string first. | ||
| """ | ||
| if value is None: | ||
| return "" | ||
| text = str(value) | ||
| # Remove CR/LF to prevent log entry splitting | ||
| return text.replace("\r", "").replace("\n", "") | ||
|
|
||
|
|
||
| def _get_memory(request: Request): | ||
| """Safely retrieve Memory instance from app state.""" | ||
| mem = getattr(request.app.state, "memory", None) | ||
| @@ -2035,7 +2047,10 @@ | ||
| ], | ||
| ) | ||
|
|
||
| logger.info(f"memory/correct: '{old_text[:60]}' → '{corrected_text[:60]}' (reason: {req.reason})") | ||
| safe_old = _sanitize_for_log(old_text[:60]) | ||
| safe_corrected = _sanitize_for_log(corrected_text[:60]) | ||
| safe_reason = _sanitize_for_log(getattr(req, "reason", "")) | ||
| logger.info(f"memory/correct: '{safe_old}' → '{safe_corrected}' (reason: {safe_reason})") | ||
|
|
||
| return { | ||
| "ok": True, |
|
|
||
| client.upsert(collection_name=COLLECTION_NAME, points=points_to_update) | ||
|
|
||
| logger.info(f"memory/forget: {len(matches)} memories forgotten (reason: {req.reason})") |
Check failure
Code scanning / CodeQL
Log Injection High
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 6 months ago
In general, to fix log injection when logging user input, you should sanitize or encode any user‑controlled strings before including them in log messages. For plain‑text logs, a common mitigation is to strip or replace control characters that can affect log structure (especially \r and \n), and optionally truncate very long inputs. This preserves the semantic content of the field while preventing an attacker from forging new log lines.
For this specific instance in infrastructure/memory/sidecar/aletheia_memory/routes.py, the minimal, non‑functional change is to sanitize req.reason immediately before it is logged in the forget_memory route. We can create a local variable, e.g. safe_reason, that removes carriage‑return and newline characters (and optionally other control characters) from req.reason, and then use that in the log message. This does not alter how req.reason is used to update Qdrant payloads (we leave payload["forgotten_reason"] = req.reason as is to avoid changing existing behavior), but ensures the log line itself cannot be structurally altered by user input.
Concretely, within the forget_memory function, just before the logger.info call at line 2107, introduce a sanitized version of the reason. For example:
safe_reason = (req.reason or "").replace("\r", "").replace("\n", "")
logger.info(f"memory/forget: {len(matches)} memories forgotten (reason: {safe_reason})")This change is localized to the shown snippet, needs no new imports or helpers, and preserves all existing functionality except that logs will now display a normalized version of the reason without line breaks.
| @@ -2104,7 +2104,8 @@ | ||
|
|
||
| client.upsert(collection_name=COLLECTION_NAME, points=points_to_update) | ||
|
|
||
| logger.info(f"memory/forget: {len(matches)} memories forgotten (reason: {req.reason})") | ||
| safe_reason = (req.reason or "").replace("\r", "").replace("\n", "") | ||
| logger.info(f"memory/forget: {len(matches)} memories forgotten (reason: {safe_reason})") | ||
|
|
||
| return { | ||
| "ok": True, |
Archived (all phases implemented): - 07 Knowledge Graph (PRs #61, #85, #86) - 11 Chat Output Quality (PR #86) - 12 Session Continuity (PRs #53, #85) - 13 Sub-Agent Workforce (PRs #72, #86) - 14 Development Workflow (PRs #71, #79, #86) - 15 UI Interaction Quality (PRs #54, #72, #86) - 19 Sleep-Time Compute (PR #80) - 23 Memory Pipeline (PR #83) Also tracks archive/ in git (was blocked by blanket gitignore rule). Previously-untracked archived specs (01, 02, 06, 08, 10) now included. Score: 18 archived, 5 in progress, 8 draft/skeleton.
## Summary - Audit PRs 82-88 for unjustified capability removal - Fix migrate-memory persistence bug (was writing to in-memory CozoDB) - Fix pre-existing graph-algo compilation errors blocking aletheia binary - Validate migration against production Qdrant data ## PR 82-88 Audit Results | PR | Title | Verdict | Notes | |----|-------|---------|-------| | #82 | fix: memory sidecar env var race + set slicing crash | OK | TS/Python bug fixes for infrastructure the Rust stack replaces | | #83 | feat(memory): Memory Pipeline (all 6 phases) | OK | Turn-facts, entity resolver, MMR recall — all reimplemented in Rust mneme | | #84 | docs: Spec 18 + Spec 24 | OK | Documentation only, no code changes | | #85 | feat: post-distillation priming + sufficiency gates | OK | Distillation priming is TS session-layer; recall gates map to Rust RecallWeights | | #86 | feat: multi-spec batch (6 specs) | OK | Sub-agent guards, doctor, graph UI, tool stats — TS/UI features still active | | #87 | fix: tool stats payload key mismatch | OK | One-line TS fix, still active code | | #88 | docs: sync spec statuses | OK | Archived 8 completed specs, no capability changes | **Verdict: No unjustified capability removal.** All deleted code was refactored within the same PR or belongs to infrastructure replaced by the Rust stack. No surgical reverts needed. ## Migration Persistence Fix The `migrate-memory` CLI command previously used `KnowledgeStore::open_mem_with_config()` — all migrated data was lost when the process exited. **Changes:** - Non-dry-run mode now uses `KnowledgeStore::open_redb()` for persistent storage - Added `--knowledge-path` CLI arg (defaults to `Oikos::knowledge_db()`) - Dry-run mode keeps in-memory store (no side effects) - Added `mneme-engine` + `storage-redb` to `migrate-qdrant` feature deps - Fixed payload field mapping: tries `data` then `memory` for content, `confidence` then `score` for scores ## Migration Results Qdrant source: 492 points in `aletheia_memories` collection (1024-dim cosine vectors) ``` agent: main -- 12 fetched, 0 duplicates removed, 12 imported, 0 flagged agent: unknown -- 480 fetched, 0 duplicates removed, 480 imported, 0 flagged Total: 492 imported, 0 deduplicated, 0 flagged ``` Persistent redb file: 8.6MB at `instance/data/knowledge.redb` ## Graph-algo Compilation Fixes Pre-existing errors in the CozoDB engine's `fixed_rule` module prevented the `aletheia` binary from compiling with default features (`recall` enables `mneme-engine` which enables `graph-algo`). - Added `BadExprValueError` struct (used by astar, random_walk algorithms) - Wrapped `AdhocError` in `Box::new()` for `Option<Box<dyn Error>>` assignments - Imported `bail!` macro in `fixed_rule/mod.rs` - Added `DbResult as Result` import to `pagerank.rs` - Removed 2 unfulfilled lint expectations from `lib.rs` - Fixed `let_and_return` clippy lint in `shortest_path_dijkstra.rs` ## Test Plan - [x] `cargo test -p aletheia-mneme` — 183 tests pass - [x] `cargo clippy -p aletheia-mneme --all-targets -- -D warnings` — clean - [x] `cargo clippy -p aletheia -p aletheia-mneme --all-targets -- -D warnings` — clean - [x] `cargo check -p aletheia` — compiles (default features) - [x] `cargo check -p aletheia --features migrate-qdrant` — compiles - [x] Dry-run migration: 492 points found, 0 flagged - [x] Live migration: 492 facts persisted to redb, 8.6MB file Note: `cargo clippy --workspace` has a pre-existing integration-tests error (NousManager argument count mismatch) unrelated to this PR. Co-authored-by: admin <admin@ardentleatherworks.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
## Summary - Audit PRs 82-88 for unjustified capability removal - Fix migrate-memory persistence bug (was writing to in-memory CozoDB) - Fix pre-existing graph-algo compilation errors blocking aletheia binary - Validate migration against production Qdrant data ## PR 82-88 Audit Results | PR | Title | Verdict | Notes | |----|-------|---------|-------| | #82 | fix: memory sidecar env var race + set slicing crash | OK | TS/Python bug fixes for infrastructure the Rust stack replaces | | #83 | feat(memory): Memory Pipeline (all 6 phases) | OK | Turn-facts, entity resolver, MMR recall — all reimplemented in Rust mneme | | #84 | docs: Spec 18 + Spec 24 | OK | Documentation only, no code changes | | #85 | feat: post-distillation priming + sufficiency gates | OK | Distillation priming is TS session-layer; recall gates map to Rust RecallWeights | | #86 | feat: multi-spec batch (6 specs) | OK | Sub-agent guards, doctor, graph UI, tool stats — TS/UI features still active | | #87 | fix: tool stats payload key mismatch | OK | One-line TS fix, still active code | | #88 | docs: sync spec statuses | OK | Archived 8 completed specs, no capability changes | **Verdict: No unjustified capability removal.** All deleted code was refactored within the same PR or belongs to infrastructure replaced by the Rust stack. No surgical reverts needed. ## Migration Persistence Fix The `migrate-memory` CLI command previously used `KnowledgeStore::open_mem_with_config()` — all migrated data was lost when the process exited. **Changes:** - Non-dry-run mode now uses `KnowledgeStore::open_redb()` for persistent storage - Added `--knowledge-path` CLI arg (defaults to `Oikos::knowledge_db()`) - Dry-run mode keeps in-memory store (no side effects) - Added `mneme-engine` + `storage-redb` to `migrate-qdrant` feature deps - Fixed payload field mapping: tries `data` then `memory` for content, `confidence` then `score` for scores ## Migration Results Qdrant source: 492 points in `aletheia_memories` collection (1024-dim cosine vectors) ``` agent: main -- 12 fetched, 0 duplicates removed, 12 imported, 0 flagged agent: unknown -- 480 fetched, 0 duplicates removed, 480 imported, 0 flagged Total: 492 imported, 0 deduplicated, 0 flagged ``` Persistent redb file: 8.6MB at `instance/data/knowledge.redb` ## Graph-algo Compilation Fixes Pre-existing errors in the CozoDB engine's `fixed_rule` module prevented the `aletheia` binary from compiling with default features (`recall` enables `mneme-engine` which enables `graph-algo`). - Added `BadExprValueError` struct (used by astar, random_walk algorithms) - Wrapped `AdhocError` in `Box::new()` for `Option<Box<dyn Error>>` assignments - Imported `bail!` macro in `fixed_rule/mod.rs` - Added `DbResult as Result` import to `pagerank.rs` - Removed 2 unfulfilled lint expectations from `lib.rs` - Fixed `let_and_return` clippy lint in `shortest_path_dijkstra.rs` ## Test Plan - [x] `cargo test -p aletheia-mneme` — 183 tests pass - [x] `cargo clippy -p aletheia-mneme --all-targets -- -D warnings` — clean - [x] `cargo clippy -p aletheia -p aletheia-mneme --all-targets -- -D warnings` — clean - [x] `cargo check -p aletheia` — compiles (default features) - [x] `cargo check -p aletheia --features migrate-qdrant` — compiles - [x] Dry-run migration: 492 points found, 0 flagged - [x] Live migration: 492 facts persisted to redb, 8.6MB file Note: `cargo clippy --workspace` has a pre-existing integration-tests error (NousManager argument count mismatch) unrelated to this PR.
🤖 I have created a release *beep* *boop* --- ## [0.22.0](v0.21.1...v0.22.0) (2026-05-09) ### Features * **_llm:** add T0 corpus per [#667](#667) / [#673](#673) fleet rollout ([#137](#137)) ([1d96566](1d96566)) * **aletheia-classify:** scaffold author-classifier inference crate ([#3797](#3797)) ([f3fa126](f3fa126)) * **aletheia-lexica:** centralize scattered pattern lists ([#3799](#3799)) ([6d1c5eb](6d1c5eb)), closes [#3785](#3785) * **aletheia-memory-mcp:** write tools behind per-process capability token ([#3813](#3813)) ([69e6386](69e6386)), closes [#3688](#3688) * **aletheia-sessions-migrate:** one-shot SQLite → fjall sessions importer for legacy 0.15 instances ([#32](#32)) ([9482dbf](9482dbf)) * **aletheia:** seed_psyche_facts bin — import v2.2 identity facts into psyche cohort ([#61](#61)) ([d893e54](d893e54)) * **basanos:** API-consistency lint for interface uniformity ([#3821](#3821)) ([372fadd](372fadd)) * **basanos:** audit component subcommand with 8-check report ([#3828](#3828)) ([c303580](c303580)) * **basanos:** derive-vs-declare detector for announced properties ([#3820](#3820)) ([fc989a2](fc989a2)) * **basanos:** hub-words discipline rule + registry ([#3816](#3816)) ([bb6e394](bb6e394)), closes [#3486](#3486) * **basanos:** purpose-language + citation-compression writing rules ([#3810](#3810)) ([b8757b1](b8757b1)), closes [#3490](#3490) * **daemon:** prosoche self-audit framework with 5 check types ([#3818](#3818)) ([3c8ea6c](3c8ea6c)), closes [#3245](#3245) * **eidos,episteme:** BookkeepingProvider trait surface ([#50](#50)) ([f539b7c](f539b7c)) * **eidos:** add visibility and reflected epistemic tier ([#43](#43)) ([09a9846](09a9846)) * **eidos:** architecture-fact layer + MCP tool + basanos rule ([#3800](#3800)) ([0f689f3](0f689f3)), closes [#3789](#3789) * **eidos:** extend ArtefactMeta for mnemosyne interop ([#3803](#3803)) ([12db8a1](12db8a1)), closes [#3796](#3796) * **eidos:** promote EvalFinding to eidos::knowledge::finding ([#3791](#3791)) ([b5b2f2c](b5b2f2c)), closes [#3779](#3779) * **eidos:** provenance + multi-agent verification types ([#55](#55)) ([4939792](4939792)) * **eidos:** Stamped trait + ArtefactMeta for uniform provenance ([#3801](#3801)) ([6b70e1a](6b70e1a)), closes [#3787](#3787) * **energeia:** friction capture — observations parser + PR template ([#3788](#3788)) ([57416c0](57416c0)), closes [#3465](#3465) * **energeia:** frontier computation — parallel group derivation from DAG ([#3777](#3777)) ([889f422](889f422)), closes [#3463](#3463) * **energeia:** phronesis recovery + persona routing + expertise affinity ([#3835](#3835)) ([4dfb78b](4dfb78b)) * **energeia:** predictive budget allocation ([#3778](#3778)) ([fbc6ddf](fbc6ddf)), closes [#3457](#3457) * **episteme,nous,taxis:** wire extraction provider config ([#54](#54)) ([8829fc4](8829fc4)) * **episteme,nous:** schema v11 — propagate Visibility + MemoryScope through Datalog facts ([#208](#208)) ([#63](#63)) ([577ad58](577ad58)) * **episteme:** add GLiNER ONNX extraction adapter ([#52](#52)) ([f9ec65b](f9ec65b)) * **episteme:** add HTTP reranker and extraction profile knobs ([#44](#44)) ([a8b91e8](a8b91e8)) * **episteme:** add OpenAI-compatible embedding provider ([#3806](#3806)) ([7349728](7349728)) * **episteme:** add visibility to scored recall results ([#45](#45)) ([cefd2b9](cefd2b9)) * **episteme:** cohort-respecting detect_conflict in extraction (W8 follow-up) ([#59](#59)) ([8fd6a98](8fd6a98)) * **episteme:** optional reranker stage in recall pipeline ([#3798](#3798)) ([1eb997a](1eb997a)), closes [#3744](#3744) * **episteme:** verification protocol module + schema v9->v10 ([#56](#56)) ([b064ca6](b064ca6)) * **eval:** typed-tag namespace over RunReport — sliceable training data ([#77](#77)) ([60ef285](60ef285)) * **gnosis:** code-graph index + MCP query tool ([#3833](#3833)) ([4831373](4831373)) * **hermeneus,nous:** agent-loop detector extension (closes [#203](#203)) ([#91](#91)) ([be985a7](be985a7)) * **hermeneus:** doom-loop detector with (args, result) signature ring ([#72](#72)) ([7b9d463](7b9d463)) * **krites:** hot-reload rule files from disk via notify ([#3809](#3809)) ([e694a70](e694a70)) * **krites:** tokio-native async surface over blocking core ([#3804](#3804)) ([6438ce1](6438ce1)), closes [#3795](#3795) * **nous,koina:** spawn-class isolation guard + consecutive-mistake brake (closes [#186](#186), [#187](#187)) ([#75](#75)) ([276bbe8](276bbe8)) * **nous,organon:** per-turn agent-curated working-memory injection (closes [#196](#196)) ([#96](#96)) ([37e6d29](37e6d29)) * **nous,organon:** tool-group gating per role (closes [#185](#185)) ([#71](#71)) ([cf9c3d7](cf9c3d7)) * **nous/bootstrap:** pre-injection scan — invisible-Unicode + threat-pattern (closes [#184](#184)) ([#79](#79)) ([c5158b9](c5158b9)) * **nous/bootstrap:** SOUL persona slot — typed BootstrapSlot enum (closes [#194](#194)) ([#67](#67)) ([758e28b](758e28b)) * **nous/compact:** two-prompt split — COMPACT_PROMPT vs RESTORE_PROMPT (closes [#189](#189)) ([#64](#64)) ([ba362b6](ba362b6)) * **nous/memory:** structured Step model + CompactionStrategy enum (closes [#210](#210), unblocks [#193](#193)) ([#88](#88)) ([2c5ed8b](2c5ed8b)) * **nous/skills,organon:** always-vs-lazy skill gating with YAML frontmatter (closes [#195](#195)) ([#89](#89)) ([f42eaab](f42eaab)) * **nous:** add cross-nous address masks ([#49](#49)) ([c9b7f38](c9b7f38)) * **nous:** add optional reflection pipeline stage ([#46](#46)) ([73aaf68](73aaf68)) * **nous:** add recall profile wiring ([#48](#48)) ([976f260](976f260)) * **nous:** cached microcompact — cache_control on distilled summary ([#3793](#3793)) ([8859c59](8859c59)) * **nous:** cross-nous verification messages ([#58](#58)) ([83c79ea](83c79ea)) * **nous:** expand hook taxonomy to after-tool, session-start, before/after-compact ([#3792](#3792)) ([aa7a5e9](aa7a5e9)) * **nous:** extend recall configuration controls ([#47](#47)) ([12b87fc](12b87fc)) * **nous:** pre-LLM triage stage (intent + sensitivity + tier) ([#3805](#3805)) ([a306099](a306099)) * **organon,nous:** tool receipts HMAC-SHA256 with active hallucination detection (closes [#202](#202)) ([#83](#83)) ([572e58b](572e58b)) * **organon:** add ToolTag enum and definitions_for_tags registry method ([#74](#74)) ([cca2699](cca2699)) * **organon:** add z3 SMT solver tool behind `z3` feature ([#3772](#3772)) ([dea0da4](dea0da4)) * **organon:** deferred tool schemas via tool_schema meta-tool ([#3807](#3807)) ([05e8a2d](05e8a2d)) * **organon:** file-ref interpolation `{{file:path:start:end}}` (closes [#197](#197)) ([#65](#65)) ([3067e67](3067e67)) * **poiesis-doc:** DOCX render + inspect backend ([#3827](#3827)) ([95ecd75](95ecd75)), closes [#3701](#3701) * **poiesis-intake:** parse Slack-style request text into structured scaffold ([#3823](#3823)) ([76ed88d](76ed88d)) * **poiesis-scaffold:** project-template scaffolder ([#3824](#3824)) ([ffd454f](ffd454f)), closes [#3703](#3703) * **poiesis-sheet:** JSON-first render_xlsx + inspect_xlsx ([#3830](#3830)) ([d66369b](d66369b)), closes [#3700](#3700) * **poiesis-slides:** JSON-first render_pptx + inspect_pptx ([#3829](#3829)) ([2eb2cd4](2eb2cd4)), closes [#3702](#3702) * **poiesis:** diff + inspect crates for output review ([#3831](#3831)) ([9a06a74](9a06a74)), closes [#3705](#3705) * **poiesis:** wire eval + graph-audit into render_typst_report (Wave 7 closure) ([#10](#10)) ([bc6dbf5](bc6dbf5)) * **proskenion:** restore canonical dye palette from ardent-site ([#18](#18)) ([6cc8738](6cc8738)) * **pylon,proskenion:** meta-insights endpoints (agent perf + quality + journal) — closes [#209](#209) ([#86](#86)) ([025a933](025a933)) * **pylon:** Deprecation + Sunset headers per RFC 8594 ([#3812](#3812)) ([2f86c1a](2f86c1a)), closes [#3280](#3280) * **pylon:** ETag + conditional request middleware ([#3817](#3817)) ([2c613dc](2c613dc)) * **pylon:** filtered SSE event subscription endpoint ([#3822](#3822)) ([56312b5](56312b5)) * **r722:** add per-nous episteme keyspace ([#53](#53)) ([90955a1](90955a1)) * **routing:** unify empirical router across dispatch + interactive paths ([#3815](#3815)) ([bedcd36](bedcd36)) * **taxis,nous,diaporeia,pylon:** private workspace flag ([#51](#51)) ([7b19bc3](7b19bc3)) * **thesauros,aletheia,nous:** full AgentOverlay (model/agency/system-prompt) + spawn_blocking + actual duration_ms (closes [#179](#179), [#180](#180), [#181](#181)) ([#80](#80)) ([0dc5e44](0dc5e44)) * tier-aware model resolution ([#3737](#3737), [#3739](#3739), [#3740](#3740)) ([#3775](#3775)) ([f48622c](f48622c)) * **training:** author classifier for training capture decontamination ([#3786](#3786)) ([#14](#14)) ([544c0d5](544c0d5)) ### Bug Fixes * **agora:** truth cluster — capability honesty + error propagation + dead-code (closes [#153](#153) [#154](#154) [#155](#155) [#156](#156) [#157](#157) [#158](#158)) ([#101](#101)) ([3faa46c](3faa46c)) * **aletheia-classify:** replace map_or with is_some_and (clippy) ([#35](#35)) ([bf92937](bf92937)) * **aletheia-lexica,poiesis:** coherence cluster (closes [#135](#135) [#136](#136) [#137](#137) [#139](#139)) ([#94](#94)) ([d4e08d7](d4e08d7)) * **aletheia-memory-mcp:** coherence cluster — namespace + boundary + drift (closes [#159](#159) [#160](#160) [#161](#161) [#162](#162) [#163](#163) [#164](#164) [#165](#165)) ([#100](#100)) ([f3c9f19](f3c9f19)) * **aletheia-routing,daemon:** cluster - routing honesty and maintenance ([#133](#133)) ([44356e0](44356e0)) * **aletheia:** clear Wave 7 clippy debt (poiesis-scaffold/diff, partial) ([#16](#16)) ([fded55d](fded55d)) * **aletheia:** drift-claim cluster — guard doc + seed path + audit heuristics + violation spans + ARCHITECTURE config (closes [#75](#75) [#105](#105) [#109](#109) [#110](#110) [#111](#111)) ([#122](#122)) ([dd5e3b9](dd5e3b9)) * **aletheia:** HIGH violations cluster — hot-reload + planning + readiness + workspace + cancellation + delegation (closes [#38](#38) [#40](#40) [#87](#87) [#88](#88) [#89](#89) [#90](#90)) ([#140](#140)) ([43be8c7](43be8c7)) * **aletheia:** stub/scaffold CLI cluster — export-agent + seed-skills persistence + consolidate wire + dry-run guard (closes [#128](#128) [#132](#132) [#133](#133) [#134](#134)) ([#126](#126)) ([fe6f36e](fe6f36e)) * **aletheia:** wire-or-delete cluster — substrate wire-ins + provenance unification + dead-code (closes [#95](#95) [#98](#98) [#99](#99) [#100](#100) [#101](#101) [#102](#102) [#103](#103)) ([#125](#125)) ([86ac027](86ac027)) * **basanos:** dedup duplicate linter and clean koina lint baseline ([#135](#135)) ([ae9360d](ae9360d)) * **daemon,aletheia:** observability cluster — backup staleness + prosoche self-audit + task-state persistence (closes [#9](#9) [#14](#14) [#18](#18)) ([#145](#145)) ([50bee14](50bee14)) * **daemon,maintenance:** cluster — sqlite_recovery delete + fact-extraction persistence + maintenance wire-in (closes [#20](#20) [#50](#50) [#72](#72)) ([#108](#108)) ([786aede](786aede)) * **diaporeia,organon:** MCP cluster — stdio transport + external tool plane bridge (closes [#17](#17) [#41](#41)) ([#114](#114)) ([b4068d9](b4068d9)) * **diaporeia:** tools cluster — knowledge_search + depth + CLAUDE.md + McpClaims + RBAC (closes [#113](#113) [#114](#114) [#115](#115) [#116](#116) [#118](#118)) ([#117](#117)) ([cec5dfe](cec5dfe)) * **docs,taxis,aletheia:** config/doc drift cluster — sqlite→fjall + Phase 05d ownership + taxis registry (closes [#21](#21) [#27](#27) [#23](#23) [#24](#24) [#25](#25) [#84](#84) [#85](#85)) ([#131](#131)) ([3f893cf](3f893cf)) * **dokimion,krites:** eval truth cluster — fake-recall + question_timeout + descriptions + now_iso8601 + TriggerConfig (closes [#117](#117) [#119](#119) [#120](#120) [#121](#121) [#122](#122)) ([#118](#118)) ([b291a4a](b291a4a)) * **energeia,hermeneus:** engine cluster — sdk doc + budget + session abort + post-processing + observability (closes [#79](#79) [#80](#80) [#81](#81) [#82](#82) [#83](#83)) ([#153](#153)) ([6a5a859](6a5a859)) * **episteme:** observability + recall cluster — wire-in + threshold + silent-failure (closes [#10](#10) [#11](#11) [#12](#12) [#63](#63) [#64](#64)) ([#105](#105)) ([2aa723a](2aa723a)) * **episteme:** tier-arm coverage for Reflected + Training in recall scoring ([#60](#60)) ([ffc8ed8](ffc8ed8)) * **gnosis:** drift cluster — SHA-256 + orphan cleanup + module_path + rdeps coverage ([#95](#95)) ([3457f18](3457f18)) * **graphe:** drift cluster — TTL overflow + error propagation + cleanup wire-in + bench doc-comments ([#98](#98)) ([7ca3905](7ca3905)) * **hermeneus,nous,aletheia:** drift cluster — fallback chain + token redact + max_tokens forward (closes [#48](#48) [#68](#68) [#69](#69)) ([#106](#106)) ([0b3b3f7](0b3b3f7)) * **hermeneus:** propagate deployment_target to OpenAI-compat provider (sovereignty) ([#3773](#3773)) ([d679332](d679332)) * **koilon/graph_analysis:** replace hardcoded staleness reference date with live clock (closes [#178](#178)) ([#68](#68)) ([cdde47c](cdde47c)) * **koilon:** gate planning checkpoints and memory search on missing pylon endpoints (closes [#170](#170), [#175](#175)) ([#69](#69)) ([866e140](866e140)) * **koina, hermeneus:** accept legacy ses_<24hex> session IDs and CC 2.x assistant-event shape ([#33](#33)) ([2db21ca](2db21ca)) * **koina:** hygiene cluster — jiff timestamps + dead code + tracing/cleanup/events wire-in (closes [#106](#106) [#107](#107) [#123](#123) [#124](#124) [#125](#125) [#126](#126) [#127](#127)) ([#128](#128)) ([d602ebf](d602ebf)) * **mneme:** drift cluster — facade boundary + suppressions + wildcard re-exports (closes [#22](#22) [#61](#61) [#62](#62)) ([#111](#111)) ([0da2404](0da2404)) * **nous,aletheia-lexica:** triage regexes rebuild from lexica constants (closes [#138](#138)) ([#66](#66)) ([7d10cb4](7d10cb4)) * **nous,daemon:** wire-in cluster — knowledge maintenance + bootstrap tool summary + per-stage timeouts (closes [#5](#5) [#6](#6) [#8](#8)) ([#129](#129)) ([7542645](7542645)) * **nous:** wire organon deferred-schemas into execute tool block ([#3811](#3811)) ([b346139](b346139)), closes [#3808](#3808) * **organon,aletheia,taxis:** security-hardening cluster — path validation + config defaults + organon drift (closes [#221](#221) [#222](#222) [#76](https://github.com/forkwright/aletheia/issues/76) [#77](#77) [#78](https://github.com/forkwright/aletheia/issues/78) [#91](#91) [#130](https://github.com/forkwright/aletheia/issues/130)) ([#152](https://github.com/forkwright/aletheia/issues/152)) ([eafa661](eafa661)) * **poiesis-inspect:** clippy under -D warnings — unblock aletheia main CI ([#9](#9)) ([1929dfa](1929dfa)) * **poiesis-slides:** replace if-let-guard with body if-let to compile on stable 1.94 ([#138](#138)) ([63d4a76](63d4a76)) * **poiesis:** truth cluster — content-drop + XLSX/ODT/PDF drift + lint + intake doc (closes [#45](#45) [#65](#65) [#66](#66) [#67](#67)) ([#107](#107)) ([0843d63](0843d63)) * **proskenion:** converge on canonical design tokens (theatron-lint clean) ([#22](#22)) ([b389fac](b389fac)) * **proskenion:** gate meta agent perf + quality charts + system journal on capability discovery (closes [#171](https://github.com/forkwright/aletheia/issues/171), [#172](https://github.com/forkwright/aletheia/issues/172), [#173](https://github.com/forkwright/aletheia/issues/173)) ([#70](https://github.com/forkwright/aletheia/issues/70)) ([125970b](125970b)) * **proskenion:** post-extraction cleanup batch (QA swarm follow-ups) ([#24](#24)) ([9a3fbbb](9a3fbbb)) * **pylon,aletheia-memory-mcp,dianoia:** mcp-api-drift cluster — error canonical + planning 501 + memory tools docs (closes [#7](https://github.com/forkwright/aletheia/issues/7) [#16](#16) [#44](#44)) ([#146](https://github.com/forkwright/aletheia/issues/146)) ([bd1a9c9](bd1a9c9)) * **pylon:** coherence cluster — auth + OpenAPI + SSE/handler/docs drift (closes [#42](https://github.com/forkwright/aletheia/issues/42) [#55](#55) [#56](#56) [#57](https://github.com/forkwright/aletheia/issues/57) [#58](#58) [#59](#59) [#60](#60)) ([#102](#102)) ([d35c982](d35c982)) * **rand:** migrate 0.9 → 0.10 API (RngExt, SysRng, TryRng renames) ([#3770](https://github.com/forkwright/aletheia/issues/3770)) ([098b133](098b133)), closes [#3767](https://github.com/forkwright/aletheia/issues/3767) * **runtime:** close NousGenerationConfig main breakage from [#3775](#3775) ([#3776](https://github.com/forkwright/aletheia/issues/3776)) ([c765c65](c765c65)) * **symbolon,pylon:** auth cluster — remove terminal UX from library + wire admin auth facade (closes [#19](https://github.com/forkwright/aletheia/issues/19) [#43](#43)) ([#115](#115)) ([083b7f3](083b7f3)) * **taxis,aletheia:** misc-config-claim cluster — provider/deployment enums + provider verify + self-prompting (closes [#1](https://github.com/forkwright/aletheia/issues/1) [#2](https://github.com/forkwright/aletheia/issues/2) [#4](https://github.com/forkwright/aletheia/issues/4)) ([#147](https://github.com/forkwright/aletheia/issues/147)) ([4ea5d2d](4ea5d2d)) * **taxis,pylon,daemon:** coherence cluster — schema preflight + dead config delete + daemonBehavior wire-in (closes [#15](https://github.com/forkwright/aletheia/issues/15) [#47](#47) [#54](#54)) ([#109](#109)) ([319241c](319241c)) ### Documentation * add CLAUDE.md precedence preamble (forge[#153](#153)) ([#11](#11)) ([f27a4d4](f27a4d4)) * **aletheia,_llm:** refresh after substrate push ([#134](#134)) ([2c64b14](2c64b14)) * **aletheia:** refresh agent-docs + cross-references + stale-link cleanup ([#132](#132)) ([194e9cb](194e9cb)) * **aletheia:** replace standards copy with kanon pointer ([#41](#41)) ([fc8a4ae](fc8a4ae)) * **aletheia:** retire stale duplicate planning tree (B-026) ([#36](https://github.com/forkwright/aletheia/issues/36)) ([5c01ee6](5c01ee6)) * **storage:** rebase runbooks + recovery on fjall-backed store ([#3840](https://github.com/forkwright/aletheia/issues/3840)) ([#12](#12)) ([45a53e0](45a53e0)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
🤖 I have created a release *beep* *boop* --- ## [0.22.0](v0.21.1...v0.22.0) (2026-05-09) ### Features * **_llm:** add T0 corpus per [#667](#667) / [#673](#673) fleet rollout ([#137](#137)) ([bdf0cd6](bdf0cd6)) * **aletheia-classify:** scaffold author-classifier inference crate ([#3797](#3797)) ([565a15f](565a15f)) * **aletheia-lexica:** centralize scattered pattern lists ([#3799](#3799)) ([71d6b89](71d6b89)), closes [#3785](#3785) * **aletheia-memory-mcp:** write tools behind per-process capability token ([#3813](#3813)) ([8e13491](8e13491)), closes [#3688](#3688) * **aletheia-sessions-migrate:** one-shot SQLite → fjall sessions importer for legacy 0.15 instances ([#32](#32)) ([ca566bf](ca566bf)) * **aletheia:** seed_psyche_facts bin — import v2.2 identity facts into psyche cohort ([#61](#61)) ([03f1694](03f1694)) * **basanos:** API-consistency lint for interface uniformity ([#3821](#3821)) ([37bbacd](37bbacd)) * **basanos:** audit component subcommand with 8-check report ([#3828](#3828)) ([ded0c99](ded0c99)) * **basanos:** derive-vs-declare detector for announced properties ([#3820](#3820)) ([fa0119e](fa0119e)) * **basanos:** hub-words discipline rule + registry ([#3816](#3816)) ([ef9dd59](ef9dd59)), closes [#3486](#3486) * **basanos:** purpose-language + citation-compression writing rules ([#3810](#3810)) ([8920766](8920766)), closes [#3490](#3490) * **daemon:** prosoche self-audit framework with 5 check types ([#3818](#3818)) ([9cae103](9cae103)), closes [#3245](#3245) * **eidos,episteme:** BookkeepingProvider trait surface ([#50](#50)) ([ee87c6d](ee87c6d)) * **eidos:** add visibility and reflected epistemic tier ([#43](#43)) ([0c08391](0c08391)) * **eidos:** architecture-fact layer + MCP tool + basanos rule ([#3800](#3800)) ([e405d1d](e405d1d)), closes [#3789](#3789) * **eidos:** extend ArtefactMeta for mnemosyne interop ([#3803](#3803)) ([3559e83](3559e83)), closes [#3796](#3796) * **eidos:** promote EvalFinding to eidos::knowledge::finding ([#3791](#3791)) ([3d19df7](3d19df7)), closes [#3779](#3779) * **eidos:** provenance + multi-agent verification types ([#55](#55)) ([b1745dd](b1745dd)) * **eidos:** Stamped trait + ArtefactMeta for uniform provenance ([#3801](#3801)) ([7a5899e](7a5899e)), closes [#3787](#3787) * **energeia:** friction capture — observations parser + PR template ([#3788](#3788)) ([93f8d12](93f8d12)), closes [#3465](#3465) * **energeia:** frontier computation — parallel group derivation from DAG ([#3777](#3777)) ([05e9db3](05e9db3)), closes [#3463](#3463) * **energeia:** phronesis recovery + persona routing + expertise affinity ([#3835](#3835)) ([e70fcba](e70fcba)) * **energeia:** predictive budget allocation ([#3778](#3778)) ([3301137](3301137)), closes [#3457](#3457) * **episteme,nous,taxis:** wire extraction provider config ([#54](#54)) ([01ecc61](01ecc61)) * **episteme,nous:** schema v11 — propagate Visibility + MemoryScope through Datalog facts ([#208](#208)) ([#63](#63)) ([7a65599](7a65599)) * **episteme:** add GLiNER ONNX extraction adapter ([#52](#52)) ([356c3a5](356c3a5)) * **episteme:** add HTTP reranker and extraction profile knobs ([#44](#44)) ([29886ec](29886ec)) * **episteme:** add OpenAI-compatible embedding provider ([#3806](#3806)) ([b5f3c2e](b5f3c2e)) * **episteme:** add visibility to scored recall results ([#45](#45)) ([8b2a5ca](8b2a5ca)) * **episteme:** cohort-respecting detect_conflict in extraction (W8 follow-up) ([#59](#59)) ([2afa74f](2afa74f)) * **episteme:** optional reranker stage in recall pipeline ([#3798](#3798)) ([f8e3652](f8e3652)), closes [#3744](#3744) * **episteme:** verification protocol module + schema v9->v10 ([#56](#56)) ([fc0c834](fc0c834)) * **eval:** typed-tag namespace over RunReport — sliceable training data ([#77](#77)) ([67f9270](67f9270)) * **gnosis:** code-graph index + MCP query tool ([#3833](#3833)) ([ba5d6cb](ba5d6cb)) * **hermeneus,nous:** agent-loop detector extension (closes [#203](#203)) ([#91](#91)) ([b0c2699](b0c2699)) * **hermeneus:** doom-loop detector with (args, result) signature ring ([#72](#72)) ([b29f212](b29f212)) * **krites:** hot-reload rule files from disk via notify ([#3809](#3809)) ([4f8872d](4f8872d)) * **krites:** tokio-native async surface over blocking core ([#3804](#3804)) ([2208494](2208494)), closes [#3795](#3795) * **nous,koina:** spawn-class isolation guard + consecutive-mistake brake (closes [#186](#186), [#187](#187)) ([#75](#75)) ([9b894dc](9b894dc)) * **nous,organon:** per-turn agent-curated working-memory injection (closes [#196](#196)) ([#96](#96)) ([e4b006c](e4b006c)) * **nous,organon:** tool-group gating per role (closes [#185](#185)) ([#71](#71)) ([69bd386](69bd386)) * **nous/bootstrap:** pre-injection scan — invisible-Unicode + threat-pattern (closes [#184](#184)) ([#79](#79)) ([9edb292](9edb292)) * **nous/bootstrap:** SOUL persona slot — typed BootstrapSlot enum (closes [#194](#194)) ([#67](#67)) ([e2edc5e](e2edc5e)) * **nous/compact:** two-prompt split — COMPACT_PROMPT vs RESTORE_PROMPT (closes [#189](#189)) ([#64](#64)) ([f43963a](f43963a)) * **nous/memory:** structured Step model + CompactionStrategy enum (closes [#210](#210), unblocks [#193](#193)) ([#88](#88)) ([2721801](2721801)) * **nous/skills,organon:** always-vs-lazy skill gating with YAML frontmatter (closes [#195](#195)) ([#89](#89)) ([4a7e9ca](4a7e9ca)) * **nous:** add cross-nous address masks ([#49](#49)) ([6df2a63](6df2a63)) * **nous:** add optional reflection pipeline stage ([#46](#46)) ([d83741b](d83741b)) * **nous:** add recall profile wiring ([#48](#48)) ([49a1b99](49a1b99)) * **nous:** cached microcompact — cache_control on distilled summary ([#3793](#3793)) ([672081b](672081b)) * **nous:** cross-nous verification messages ([#58](#58)) ([34fa6ed](34fa6ed)) * **nous:** expand hook taxonomy to after-tool, session-start, before/after-compact ([#3792](#3792)) ([cf5465f](cf5465f)) * **nous:** extend recall configuration controls ([#47](#47)) ([42dc08f](42dc08f)) * **nous:** pre-LLM triage stage (intent + sensitivity + tier) ([#3805](#3805)) ([61170f4](61170f4)) * **organon,nous:** tool receipts HMAC-SHA256 with active hallucination detection (closes [#202](#202)) ([#83](#83)) ([79a124b](79a124b)) * **organon:** add ToolTag enum and definitions_for_tags registry method ([#74](#74)) ([443aead](443aead)) * **organon:** add z3 SMT solver tool behind `z3` feature ([#3772](#3772)) ([8632803](8632803)) * **organon:** deferred tool schemas via tool_schema meta-tool ([#3807](#3807)) ([a24e21c](a24e21c)) * **organon:** file-ref interpolation `{{file:path:start:end}}` (closes [#197](#197)) ([#65](#65)) ([b1b859a](b1b859a)) * **poiesis-doc:** DOCX render + inspect backend ([#3827](#3827)) ([e814b28](e814b28)), closes [#3701](#3701) * **poiesis-intake:** parse Slack-style request text into structured scaffold ([#3823](#3823)) ([bd23176](bd23176)) * **poiesis-scaffold:** project-template scaffolder ([#3824](#3824)) ([e3abc1d](e3abc1d)), closes [#3703](#3703) * **poiesis-sheet:** JSON-first render_xlsx + inspect_xlsx ([#3830](#3830)) ([9803a67](9803a67)), closes [#3700](#3700) * **poiesis-slides:** JSON-first render_pptx + inspect_pptx ([#3829](#3829)) ([c86bbb2](c86bbb2)), closes [#3702](#3702) * **poiesis:** diff + inspect crates for output review ([#3831](#3831)) ([8b8f60a](8b8f60a)), closes [#3705](#3705) * **poiesis:** wire eval + graph-audit into render_typst_report (Wave 7 closure) ([#10](#10)) ([3d3cec1](3d3cec1)) * **proskenion:** restore canonical dye palette from ardent-site ([#18](#18)) ([90c26c1](90c26c1)) * **pylon,proskenion:** meta-insights endpoints (agent perf + quality + journal) — closes [#209](#209) ([#86](#86)) ([b7b68e7](b7b68e7)) * **pylon:** Deprecation + Sunset headers per RFC 8594 ([#3812](#3812)) ([2d34856](2d34856)), closes [#3280](#3280) * **pylon:** ETag + conditional request middleware ([#3817](#3817)) ([13c95cc](13c95cc)) * **pylon:** filtered SSE event subscription endpoint ([#3822](#3822)) ([e2ac393](e2ac393)) * **r722:** add per-nous episteme keyspace ([#53](#53)) ([2771b43](2771b43)) * **routing:** unify empirical router across dispatch + interactive paths ([#3815](#3815)) ([33aedf5](33aedf5)) * **taxis,nous,diaporeia,pylon:** private workspace flag ([#51](#51)) ([a597181](a597181)) * **thesauros,aletheia,nous:** full AgentOverlay (model/agency/system-prompt) + spawn_blocking + actual duration_ms (closes [#179](#179), [#180](#180), [#181](#181)) ([#80](#80)) ([c9b97ff](c9b97ff)) * tier-aware model resolution ([#3737](#3737), [#3739](#3739), [#3740](#3740)) ([#3775](#3775)) ([c8faf9a](c8faf9a)) * **training:** author classifier for training capture decontamination ([#3786](#3786)) ([#14](#14)) ([ba8fc3b](ba8fc3b)) ### Bug Fixes * **agora:** truth cluster — capability honesty + error propagation + dead-code (closes [#153](#153) [#154](#154) [#155](#155) [#156](#156) [#157](#157) [#158](#158)) ([#101](#101)) ([cf29be3](cf29be3)) * **aletheia-classify:** replace map_or with is_some_and (clippy) ([#35](#35)) ([8aac8b2](8aac8b2)) * **aletheia-lexica,poiesis:** coherence cluster (closes [#135](#135) [#136](#136) [#137](#137) [#139](#139)) ([#94](#94)) ([532b2c9](532b2c9)) * **aletheia-memory-mcp:** coherence cluster — namespace + boundary + drift (closes [#159](#159) [#160](#160) [#161](#161) [#162](#162) [#163](#163) [#164](#164) [#165](#165)) ([#100](#100)) ([8ea4c15](8ea4c15)) * **aletheia-routing,daemon:** cluster - routing honesty and maintenance ([#133](#133)) ([04ee64a](04ee64a)) * **aletheia:** clear Wave 7 clippy debt (poiesis-scaffold/diff, partial) ([#16](#16)) ([df59390](df59390)) * **aletheia:** drift-claim cluster — guard doc + seed path + audit heuristics + violation spans + ARCHITECTURE config (closes [#75](#75) [#105](#105) [#109](#109) [#110](#110) [#111](#111)) ([#122](#122)) ([790f1a1](790f1a1)) * **aletheia:** HIGH violations cluster — hot-reload + planning + readiness + workspace + cancellation + delegation (closes [#38](#38) [#40](#40) [#87](#87) [#88](#88) [#89](#89) [#90](#90)) ([#140](#140)) ([366a96e](366a96e)) * **aletheia:** stub/scaffold CLI cluster — export-agent + seed-skills persistence + consolidate wire + dry-run guard (closes [#128](#128) [#132](#132) [#133](#133) [#134](#134)) ([#126](#126)) ([c8c5f5e](c8c5f5e)) * **aletheia:** wire-or-delete cluster — substrate wire-ins + provenance unification + dead-code (closes [#95](#95) [#98](#98) [#99](#99) [#100](#100) [#101](#101) [#102](#102) [#103](#103)) ([#125](#125)) ([350ad0d](350ad0d)) * **basanos:** dedup duplicate linter and clean koina lint baseline ([#135](#135)) ([814edc6](814edc6)) * **daemon,aletheia:** observability cluster — backup staleness + prosoche self-audit + task-state persistence (closes [#9](#9) [#14](#14) [#18](#18)) ([#145](#145)) ([3091af1](3091af1)) * **daemon,maintenance:** cluster — sqlite_recovery delete + fact-extraction persistence + maintenance wire-in (closes [#20](#20) [#50](#50) [#72](#72)) ([#108](#108)) ([d3a090a](d3a090a)) * **diaporeia,organon:** MCP cluster — stdio transport + external tool plane bridge (closes [#17](#17) [#41](#41)) ([#114](#114)) ([e0f5fc4](e0f5fc4)) * **diaporeia:** tools cluster — knowledge_search + depth + CLAUDE.md + McpClaims + RBAC (closes [#113](#113) [#114](#114) [#115](#115) [#116](#116) [#118](#118)) ([#117](#117)) ([404d0e9](404d0e9)) * **docs,taxis,aletheia:** config/doc drift cluster — sqlite→fjall + Phase 05d ownership + taxis registry (closes [#21](#21) [#27](#27) [#23](#23) [#24](#24) [#25](#25) [#84](#84) [#85](#85)) ([#131](#131)) ([ca1a054](ca1a054)) * **dokimion,krites:** eval truth cluster — fake-recall + question_timeout + descriptions + now_iso8601 + TriggerConfig (closes [#117](#117) [#119](#119) [#120](#120) [#121](#121) [#122](#122)) ([#118](#118)) ([79b0513](79b0513)) * **energeia,hermeneus:** engine cluster — sdk doc + budget + session abort + post-processing + observability (closes [#79](#79) [#80](#80) [#81](#81) [#82](#82) [#83](#83)) ([#153](#153)) ([e8436b6](e8436b6)) * **episteme:** observability + recall cluster — wire-in + threshold + silent-failure (closes [#10](#10) [#11](#11) [#12](#12) [#63](#63) [#64](#64)) ([#105](#105)) ([a0b88e3](a0b88e3)) * **episteme:** tier-arm coverage for Reflected + Training in recall scoring ([#60](#60)) ([b4883a2](b4883a2)) * **gnosis:** drift cluster — SHA-256 + orphan cleanup + module_path + rdeps coverage ([#95](#95)) ([79814c2](79814c2)) * **graphe:** drift cluster — TTL overflow + error propagation + cleanup wire-in + bench doc-comments ([#98](#98)) ([72c2e45](72c2e45)) * **hermeneus,nous,aletheia:** drift cluster — fallback chain + token redact + max_tokens forward (closes [#48](#48) [#68](#68) [#69](#69)) ([#106](#106)) ([5550eeb](5550eeb)) * **hermeneus:** propagate deployment_target to OpenAI-compat provider (sovereignty) ([#3773](#3773)) ([4f0f708](4f0f708)) * **koilon/graph_analysis:** replace hardcoded staleness reference date with live clock (closes [#178](#178)) ([#68](#68)) ([e9ef02c](e9ef02c)) * **koilon:** gate planning checkpoints and memory search on missing pylon endpoints (closes [#170](#170), [#175](#175)) ([#69](#69)) ([806f670](806f670)) * **koina, hermeneus:** accept legacy ses_<24hex> session IDs and CC 2.x assistant-event shape ([#33](#33)) ([974920e](974920e)) * **koina:** hygiene cluster — jiff timestamps + dead code + tracing/cleanup/events wire-in (closes [#106](#106) [#107](#107) [#123](#123) [#124](#124) [#125](#125) [#126](#126) [#127](#127)) ([#128](#128)) ([2cb0a09](2cb0a09)) * **mneme:** drift cluster — facade boundary + suppressions + wildcard re-exports (closes [#22](#22) [#61](#61) [#62](#62)) ([#111](#111)) ([a145463](a145463)) * **nous,aletheia-lexica:** triage regexes rebuild from lexica constants (closes [#138](#138)) ([#66](#66)) ([e0a600d](e0a600d)) * **nous,daemon:** wire-in cluster — knowledge maintenance + bootstrap tool summary + per-stage timeouts (closes [#5](#5) [#6](#6) [#8](#8)) ([#129](#129)) ([18c4775](18c4775)) * **nous:** wire organon deferred-schemas into execute tool block ([#3811](#3811)) ([58fcf50](58fcf50)), closes [#3808](#3808) * **organon,aletheia,taxis:** security-hardening cluster — path validation + config defaults + organon drift (closes [#221](#221) [#222](#222) [#76](https://github.com/forkwright/aletheia/issues/76) [#77](#77) [#78](https://github.com/forkwright/aletheia/issues/78) [#91](#91) [#130](https://github.com/forkwright/aletheia/issues/130)) ([#152](https://github.com/forkwright/aletheia/issues/152)) ([2685be8](2685be8)) * **poiesis-inspect:** clippy under -D warnings — unblock aletheia main CI ([#9](#9)) ([d61985f](d61985f)) * **poiesis-slides:** replace if-let-guard with body if-let to compile on stable 1.94 ([#138](#138)) ([9330e0a](9330e0a)) * **poiesis:** truth cluster — content-drop + XLSX/ODT/PDF drift + lint + intake doc (closes [#45](#45) [#65](#65) [#66](#66) [#67](#67)) ([#107](#107)) ([b69f8ae](b69f8ae)) * **proskenion:** converge on canonical design tokens (theatron-lint clean) ([#22](#22)) ([1a61e8a](1a61e8a)) * **proskenion:** gate meta agent perf + quality charts + system journal on capability discovery (closes [#171](https://github.com/forkwright/aletheia/issues/171), [#172](https://github.com/forkwright/aletheia/issues/172), [#173](https://github.com/forkwright/aletheia/issues/173)) ([#70](https://github.com/forkwright/aletheia/issues/70)) ([8257529](8257529)) * **proskenion:** post-extraction cleanup batch (QA swarm follow-ups) ([#24](#24)) ([b2d8173](b2d8173)) * **pylon,aletheia-memory-mcp,dianoia:** mcp-api-drift cluster — error canonical + planning 501 + memory tools docs (closes [#7](https://github.com/forkwright/aletheia/issues/7) [#16](#16) [#44](#44)) ([#146](https://github.com/forkwright/aletheia/issues/146)) ([d371663](d371663)) * **pylon:** coherence cluster — auth + OpenAPI + SSE/handler/docs drift (closes [#42](https://github.com/forkwright/aletheia/issues/42) [#55](#55) [#56](#56) [#57](https://github.com/forkwright/aletheia/issues/57) [#58](#58) [#59](#59) [#60](#60)) ([#102](#102)) ([639f725](639f725)) * **rand:** migrate 0.9 → 0.10 API (RngExt, SysRng, TryRng renames) ([#3770](https://github.com/forkwright/aletheia/issues/3770)) ([246d46d](246d46d)), closes [#3767](https://github.com/forkwright/aletheia/issues/3767) * **runtime:** close NousGenerationConfig main breakage from [#3775](#3775) ([#3776](https://github.com/forkwright/aletheia/issues/3776)) ([80d7d2b](80d7d2b)) * **symbolon,pylon:** auth cluster — remove terminal UX from library + wire admin auth facade (closes [#19](https://github.com/forkwright/aletheia/issues/19) [#43](#43)) ([#115](#115)) ([85d8931](85d8931)) * **taxis,aletheia:** misc-config-claim cluster — provider/deployment enums + provider verify + self-prompting (closes [#1](https://github.com/forkwright/aletheia/issues/1) [#2](https://github.com/forkwright/aletheia/issues/2) [#4](https://github.com/forkwright/aletheia/issues/4)) ([#147](https://github.com/forkwright/aletheia/issues/147)) ([18b59bf](18b59bf)) * **taxis,pylon,daemon:** coherence cluster — schema preflight + dead config delete + daemonBehavior wire-in (closes [#15](https://github.com/forkwright/aletheia/issues/15) [#47](#47) [#54](#54)) ([#109](#109)) ([a2fa31d](a2fa31d)) ### Documentation * add CLAUDE.md precedence preamble (forge[#153](#153)) ([#11](#11)) ([145f4a0](145f4a0)) * **aletheia,_llm:** refresh after substrate push ([#134](#134)) ([df5b2df](df5b2df)) * **aletheia:** refresh agent-docs + cross-references + stale-link cleanup ([#132](#132)) ([5ce1b90](5ce1b90)) * **aletheia:** replace standards copy with kanon pointer ([#41](#41)) ([12b31ad](12b31ad)) * **aletheia:** retire stale duplicate planning tree (B-026) ([#36](https://github.com/forkwright/aletheia/issues/36)) ([5c95d96](5c95d96)) * **storage:** rebase runbooks + recovery on fjall-backed store ([#3840](https://github.com/forkwright/aletheia/issues/3840)) ([#12](#12)) ([01ed0b0](01ed0b0)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>




Spec 23: Memory Pipeline
The memory system could not form new memories from conversations.
memoryTargetwas defined as an interface in 3 files but wired in zero. Distillation extracted facts then dropped them. After-turn extraction didn't exist. The recall system searched a stale corpus.What this PR does (Phases 1-3, 4 partial, 6 partial):
Phase 1: Wire Extraction Pipeline
/add_directand/add_batchendpoints — embed + store directly in Qdrant, bypass Mem0's noisy LLM re-extractionMemoryFlushTargetimplementation in aletheia.ts → sidecar/add_batchdistillSessioncalls + nightly reflection cronPhase 2: After-Turn Extraction
turn-facts.ts: lightweight Haiku extraction of 0-3 durable facts per turnfinalize.ts— async, non-blocking, fires after each qualifying turnPhase 3: Extraction Quality
Phase 4 (partial): Entity Resolution
entity_resolver.py: canonical registry, alias table, fuzzy matching, stopword filteringPhase 6 (partial): Recall Quality
Tests
Still needed (Phases 4b, 5, 6b)