Choudhary et al. (2025). Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory

Production memory layer whose ADD / UPDATE / DELETE classification informed the distill-time quality gate. ADR-0021 considered and rejected wholesale adoption (external vector DB dependency violating the one-external-adapter principle), keeping only the write-classification idea.


MemoryGraft (2025). Persistent Compromise of LLM Agents via Poisoned Memory

Follow-up to the MINJA attack demonstrating persistent agent compromise through poisoned memory. Cited by ADR-0021 as evidence that a single crafted external post can permanently shape agent behavior, grounding the structural quarantine at the episode → pattern boundary.


Park et al. (2023). Generative Agents: Interactive Simulacra of Human Behavior

Origin of the memory stream with recency × importance × relevance retrieval and the reflection tree. Source of the importance-rating concept adopted by ADR-0009's importance score for pattern retrieval.


Sumers et al. (2023). Cognitive Architectures for Language Agents

CoALA framework providing the four-type memory taxonomy (working, episodic, semantic, procedural) used to position Contemplative Agent's three-layer memory architecture within the language-agent literature.


Beautiful Loop paper

Cited as a related-work entry point for active inference and contemplative loops. PubMed:40750007.


Yogācāra eight-consciousness tradition

Buddhist contemplative tradition providing the conceptual frame for the three-layer memory architecture's grounding (ADR-0017).


MLCommons AILuminate safety benchmark (2024)

Safety benchmark used to evaluate the contemplative-agent-rules implementation of the four axioms. Cross-graph @id reuse: same node appears in shimo4228 hub graph.


Axelrod (1984). The Evolution of Cooperation

Iterated Prisoner's Dilemma cooperation-improvement benchmark used to evaluate the contemplative-agent-rules implementation of the four axioms. Cross-graph @id reuse: same node appears in shimo4228 hub graph.


Singer (2004). Self-defining memories

Autobiographical-memory research on self-defining memories — vivid, affectively charged, repeatedly recalled events that carry identity-relevant meaning. Cited by ADR-0038 as grounding for the moment-of-recognition observation target.


McDonald (2008). Epiphanic memories

Phenomenological account of epiphanic memories — moments of sudden insight or recognition that reshape the self-narrative. Cited by ADR-0038 alongside Singer's self-defining-memory framework.


Topolinski & Reber (2010). Insight (Aha experience)

Cognitive-psychology account of the Aha experience — the affective signature of sudden solution-finding. Cited by ADR-0038 as the third grounding for the moment-of-recognition concept.


OpenClaw (generic agent host runtime)

Representative generic-host agent runtime invoked as a dual reference: (1) the bolt-on-security contrast for Contemplative Agent's security-by-absence stance, and (2) the originator of the soul-folder pattern (SOUL.md) that contemplative-agent-rules' template targets. ADR-0032 articulated and then withdrew a host-category framing built around runtimes like OpenClaw.


Memento-Skills (2026). Skill-as-Memory architecture

Skill-as-memory router and reflective-write loop on the skill layer. Inspired ADR-0023; the mechanism was retired by ADR-0036 after no measurable behavior change was observed, and ADR-0037 records it as one of the paper-borrowed mechanisms that did not survive validation in Contemplative Agent's substrate.


Hermes (agent memory architecture)

Independent agent-memory architecture cited alongside Memento and Contemplative Agent as a convergence point: three independent projects arriving at the same three-layer memory + skill-auto-generation attractor. Treated as evidence of mechanism commoditization rather than a borrowable mechanism.


ADR-0071: Read-Only Pattern-Composition Instruments (View Supply / Diversity / Grounding)

Adds core/view_metrics.py: read-only pattern-composition instruments over Layer 2 (knowledge patterns). Three instruments — consumed-view supply (cosine-to-seed distributions restricted to the two views with real consumers: self_reflection → distill-identity, constitutional → amend-constitution), seed-independent diversity (pairwise-cosine homogeneity + the cluster structure insight would see, gated rows excluded to mirror insight), and grounding composition (provenance source_type + the ADR-0050 epistemic tally with the structural observed==0 caveat). Wired into distill --dry-run (post-dedup would-be-added set), report --patterns (opt-in), and insight's dropped-singleton log (nearest consumed view). Observability only: nothing feeds gates, ranking, retrieval, or promotion (same shape as AKC ADR-0015 Decision 2); output carries a fixed ambiguity note (empty/low supply = missing patterns or a stale seed, undecidable) per AKC ADR-0016. The five orphaned views are deliberately unmeasured — a distribution over an unconsumed seed measures seed staleness, not corpus structure. First baseline: live pool n=1463, view threshold 0.55 sits at the corpus pairwise-cosine floor (mean/p50=0.554) so top_k does all real selection; a control experiment put the unrelated-text floor at 0.33–0.46, showing the homogeneity is a genuine echo signature, not embedding-model anisotropy.


ADR-0072: Echo-Chamber Interventions — Register Instruction, Corpus-Grown Seed, Extraction-Failure Guard

Phase-two interventions against the echo chamber ADR-0071's instruments quantified, plus the curation the first readings demanded. (1) The live per-episode distill prompt gains a first-person, moment-indexed register instruction — drafted by the executing model itself (gemma4:e4b, prompt-model-match) — and an explicit prohibition on narrating extraction failure (return the empty list instead). (2) _is_valid_pattern gains a conservative extraction-failure phrase filter (8 episode/task-referential multi-word phrases, rejects logged with reason; validity, not value — bare phrases like 'lack sufficient context' deliberately excluded because genuine first-person recognitions use them). (3) The self_reflection view seed keeps its abstract register description and appends four A-register exemplar patterns drawn from the corpus (corpus-grown anchoring); threshold recalibrated 0.55 → 0.66 from a fresh full-distribution calibration (new off-topic control floor 0.40–0.46; 193/1463 = 13.2% pass, ≥ 3×top_k). (4) Deletions by signal-first: the grounding instrument (constant under the per-episode source_type mapping), the dead batch-distill prompts distill.md / distill_refine.md with their loader wiring (dead since ADR-0060), and the promptfoo evals harness that rode them. Same-window paired measurement: first-person moment-indexed register confirmed qualitatively; would-soft-invalidate collapsed (24–31 → 5–9: the new register diverges from the legacy pool); new-batch view supply reaches the seed's exemplar band (compounding mechanism confirmed — future A-register patterns rank into the view's top band); intra-batch pairwise mean unchanged at 0.60 — the register axis rotated rather than diversified, recorded as the next observation target.


ADR-0073: Prune the Five Orphaned View Seeds

Deletes the five view seeds whose consumers were retired long ago (the insight batch axes communication/reasoning/social/technical by ADR-0050, the ingest noise gate by ADR-0060) from both the repo templates and the live home (live copies soft-deleted to a dated backup). Only the two consumed views ship: self_reflection (distill-identity) and constitutional (amend-constitution). Encodes the standing policy the deletion implies: a view exists only together with a consumer that queries it — future views are grown from stable corpus clusters with their consumer wired in the same change (the route ADR-0072's exemplar appendix validated), never pre-authored against anticipated categories. The reference sweep also rewrote the stale CONFIGURATION.md view-seed section (it still described pre-ADR-0031 episode classification and view tagging) and corrected the facing-doc counts (30 loaded prompts / 2 view seeds) that had gone stale with ADR-0072's prompt deletion. No behavior change: nothing queried the five seeds. Closes the disposition ADR-0072 deferred.


ADR-0074: Weekly Staged Insight — Theme Detection, Pending Guard, Marker-on-Stage, LLM Novelty Gate, Exact Fast Clustering

Redefines insight's role from proportional knowledge-to-skill conversion to detecting when a new stable theme has accumulated, sized for the post-ADR-0060 inflow (~100 patterns/day). Five mechanisms: (1) exact fast average-linkage clustering via the Lance-Williams update (identical partitions to the retired naive merge, N=1798 in under a second); (2) a marker guard — a missing .last_insight marker refuses instead of silently reclustering the whole pool; (3) a pending guard — staging holds at most one unreviewed batch, so scheduled runs can never wipe candidates awaiting human review; (4) the run marker advances when candidates reach review (staging or the interactive loop), not at adoption; (5) an LLM novelty gate (one grouping call, fail-open) that skips clusters whose theme an adopted skill or previously staged candidate (logs/insight-staged.jsonl) already covers — calibration showed embedding similarity cannot separate same-theme from distinct-theme clusters on the echo-homogeneous corpus, the same lesson as ADR-0036 and ADR-0046. Weekly cadence via install-schedule --weekly-insight (default Mon 08:00); the singleton-rescue question is closed as no-rescue because measured theme recurrence (32/32 across adjacent windows) re-surfaces real themes.


ADR-0075: Observability by Default — Replayable Audit Logs Ship With the Feature

Elevates an implicit practice to a standing principle: any feature that performs external I/O, calls an LLM, or makes non-deterministic or heuristic decisions must ship its audit instrumentation in the same PR as the feature — append-only JSONL under MOLTBOOK_HOME/logs/, records sufficient for offline replay (untrusted raw input as base64 + sha256, decision path, categorical reason codes for every abstain/fallback/failure, outcome), and no silent fallbacks. Motivated by the ADR-0062 round-6/7 parser repairs, which were possible only because verification-audit.jsonl predated the investigation: server-accepted answers gave positive ground truth, server-rejected answers gave negative ground truth, and a deterministic replay harness with a zero-wrong hard gate made grammar changes testable against every historical challenge. The principle is layered across three artifacts: a one-line rule in CLAUDE.md (always loaded), design know-how in the project skill .claude/skills/replayable-audit-logs (record-schema checklist, replay-harness pattern, ground-truth discipline, corpus-driven repair loop), and this ADR as canonical rationale. Rejected alternatives: ad-hoc logging at investigation time (the corpus must predate the failure), an observability framework like OpenTelemetry (single-process local agent, dependency floor stays requests+numpy), a global harness rule (project-specific practice), and a CLAUDE.md bullet without an ADR (evidence and criteria would drift).


ADR-0076: Skill-Selection Shadow Instrument — Pass-1 LLM Applicability Observed, Not Enforced

Introduces a shadow-mode pass-1 LLM skill selection: before each content generation (comment / reply / cooperation post), one extra identity-prompt LLM call receives the situation plus the learned-skill catalog (name — description lines) and picks applicable skills; the result is recorded to logs/skill-selection-YYYY-MM-DD.jsonl (ADR-0075 schema: verdict judged/fail_open_llm/fail_open_parse/empty_catalog/no_template, hallucinated names rejected and logged, prompt/output as base64+sha256, full vs would-be skill token estimates baked in at record time) and changes nothing about injection. Motivated by the corpus reaching ≈20.3K tok ≈62% of the 32,768-token window and the 2026-07-09 C2-guard incident. Closes both ADR-0036 doors on corpus evidence: skill trigger conditions are situational-semantic, so neither cosine routing (similarity ≠ applicability) nor typed frontmatter predicates (triggers do not project onto an action-type axis) can carry the judgment — it belongs to the LLM per mechanism-vs-value-split. Shadow-first because no published reliability evidence exists for description-only selection on 4-9B local models; enforcement criteria (hallucination rate, fail-open rate, never-selected stability, realized token reduction via report --skill-selection) are reserved for a follow-up ADR after 2-4 weeks of data. Deliberate exclusions: post_title (same pipeline pass and seeds as cooperation_post), the learned corpus itself from the judge's prompt (audit H5 vocabulary feedback).


ADR-0077: Chaos-TDD Fault Injection — Seeded Fault Schedules as Test-First Specification (Pilot: distill)

Establishes deterministic chaos-TDD fault injection for the distill pipeline: faults are injected only at the two existing seams (a test-side ChaosBackend implementing the LLMBackend Protocol with explicit or seed-derived fault schedules, and the requests HTTP layer via the responses library), never via a production hook. Five previously-untested fault classes are pinned — F1 mid-generation read-timeout, F2 direct /api/embed HTTP faults, F3 syntactically-valid JSON violating the {"patterns": [str]} structured-output shape, F4 HTTP 429 from the Ollama endpoint itself (fail-fast pinned: no retry, no Retry-After sleep — the circuit breaker is the recovery mechanism for a local daemon), F5 flapping success/failure sequences. The TDD contract: a chaos test states the desired guarded behavior first, and the minimal guard lands in the same PR. Production deltas shipped: distill abstain reason codes (llm_none / empty_render / shape_violation; valid-JSON-wrong-shape abstains instead of silently bullet-scanning, while the bullet fallback is retained for non-JSON bodies; per-reason summary tally; reason=embed_failed token) and a sparse error_kind telemetry field (timeout / connection / http_<status> / bad_json / bad_url / request_error / backend_exception) so llm-calls JSONL distinguishes fault kinds offline. Determinism discipline: hypothesis under a derandomized ci profile with pinned @example regressions, no real sleeps (latency = injected ReadTimeout), storage relocated to the pytest sandbox. Rejected altitudes: chaostoolkit/toxiproxy (infra-level), production random chaos (episode logs are irreplaceable research material). Know-how captured as project skill chaos-tdd-fault-injection.


ADR-0078: OTel Connection via Vocabulary Mapping and Offline Export — Not Runtime Adoption

Maintains ADR-0075's rejection of runtime OpenTelemetry adoption (single local process, requests+numpy dependency floor, research-grade offline replay as the primary observability requirement) while connecting to the OTel ecosystem through two zero-runtime-impact routes: (1) a zero-dependency vocabulary mapping documenting how the audit-log schemas correspond to the OTel GenAI semantic conventions (Development status; gen_ai.* attributes map near 1:1 onto the llm-calls telemetry fields) in docs/otel-semconv-mapping.md, with the canonical field-by-field table living next to the implementing code in the sibling repository; and (2) an offline exporter, sibling repo contemplative-agent-otel, that converts existing JSONL audit logs (llm-calls, verification-audit, api-audit) to OTLP traces after the fact with zero code dependency on the main package. Untrusted raw text (challenge bodies, server response bodies) is dropped at parse time — only hashes and categorical error classes reach span attributes, enforced by a regression test. The retention-policy contrast is deliberate: GenAI conventions redact content by default (ops consumers), ADR-0075 logs store raw untrusted inputs base64+sha256 (replay consumers) — same events, opposite retention choices. Empirically validated by converting real logs into Jaeger v2: 1,031 spans (2026-07-15 normal day), 31,524 spans (2026-07-12 incident day, one 30,442-span trace with 33 errors). Trace grouping is a time-gap reconstruction marked via ca.convert.* attributes; duration-less audit records become zero-width spans rather than fabricated estimates.


ADR-0079: Module Reorganization — Package Splits, Permanent Facades, and Documented Size-Cap Exceptions

Resolves seven violations of the 800-line file-size cap by splitting on responsibility rather than line count, under a two-option shim rule: a compatibility layer is either a permanent facade (the import path is public API) or absent (clean break with tests migrated in the same commit) — never a temporary shim, because mock.patch fails loudly on missing attributes but silently no-ops through a shim, and the no-op'd patches here are tmp-dir redirects guarding real user data. core/llm.py (1,507 lines) becomes the core/llm/ package (backend / prompting / guard) behind a permanent facade preserving all sibling-repo and internal import paths; cli.py (3,037 lines) becomes the cli/ package (nine submodules) with a clean break re-exporting only main, plus __main__.py and a single _repo_root() helper replacing depth-sensitive parents[2] resolution; core/insight.py extracts the novelty filter to core/insight_novelty.py; core/distill.py extracts pattern_dedup and episode_render. Three files stay documented exceptions to the cap — verification_parse.py (one cohesive deterministic parser), agent.py (single class), client.py (44 lines over) — the cap is a ceiling, not a target. Split boundaries and three behavior risks (repo-root depth, module execution, facade patch-incompatibility for moved callers) were confirmed via an independent cross-model design review (OpenAI Codex).


ADR-0080: North Star — Per-Layer End-State Definition, Not a Capability Target

Worldview ADR fixing the program's north star as per-layer completion conditions rather than a capability target. Mechanism layer (code): done = the layer stops moving — changes become repair-only; the ADR-0071/0075/0077 instrument/audit/chaos-TDD line is read as convergence toward 'trustworthy enough not to touch'. Value layer (identity/constitution/skills/rules): no destination by design — the desirable state is that legible evolution continues, with every change traceable in the records and explicable offline (observation-over-steering, ADR-0050/0051/0052). Research layer: done = the founding questions (does a contemplative constitution shape small-local-model behavior; does distillation compound; does identity stay stable) are answered either way and crystallized into papers diffused into the machine-reference sphere, where derivation by others and by LLMs is the success criterion. Security layer: done = absence preserved — one external adapter, no permission growth; this layer succeeds by not changing. Ending design: the 6-hour heartbeat stops when the read-only instruments show metabolic steady state and the remaining questions require a different experiment (e.g. a second agent for framework/worldview separation); the final deliverable is the longitudinal record (contemplative-agent-data plus the papers), not the agent instance. Owner's explicit clause: quality benchmarks may be derived from this definition as per-layer gates, but the definition itself is a value-layer statement not reducible to any benchmark suite (Goodhart guard). One-sentence summary: the desired end state is a finished observation instrument on which only the value layers keep moving — the durable artifact is the record of the run, not the runner.


ADR-0081: Skill-Selection Two-Pass Injection Enforcement

Enforces the ADR-0076 shadow skill selection as two-pass injection on the three observed generation paths (comment / reply / cooperation post): pass 1 = the existing selector call, pass 2 = generation with only the selected skill bodies in the <learned_skills> block (learned rules unchanged; post_title reuses cooperation_post's selection). Decided on the 2026-07-24 first reading of 14 days / 7,930 shadow records, which supported migration on all four ADR-0076 criteria: hallucination 0.5% of judged with zero propagation, fail-open 0% in normal operation (all 6,631 fail_open_llm records traced to a single 2026-07-12 circuit-breaker-open incident during which publish paths proceeded untouched), all 19 skills selected with day-level stability, would-be token reduction p50 78.9% / p90 86.5% (≈15.9K tok/action). Fail-open under enforcement falls back to full-corpus injection; a judged-but-empty selection injects nothing (a judgment, not a failure). Rollout is flag-gated (MOLTBOOK_SKILL_SELECTION_ENFORCE=1, default off) with an attended smoke run before the launchd schedule turns it on; the audit log continues with an enforced/shadow marker, and the next reading window observes the post-enforcement self-referential selection→generation→distill→skills loop. Rejected alternatives: status quo full injection, static tiering, numeric selection caps, immediate default-on. Also closes the ADR-0076 situation-granularity open question (prompt max 6,864 bytes, zero truncation).


ADR-0082: Retire the observed Epistemic Key — Delete the Dead Field, Not the Warning About It

Retires the ADR-0050 observed epistemic kind and its external_reply arm, leaving epistemic_counts as a {generated, unknown} tally. ADR-0060 restricted distillation to activity records (all self → self_reflection → generated), making the only external_reply producer unreachable, so observed had been structurally zero across the full audit history (405 records: insight 0/1,618/0, distill-identity 0/231/119 — unknown is genuinely populated and stays). ADR-0071's M2 review documented in 2026-06-27 that observed == 0 misreads as 'no external grounding' (external content enters as grounding text inside the rich render and was never counted by this tally); ADR-0072 deleted the grounding instrument but kept epistemic_counts_for under signal-first deferral. On 2026-07-25 the predicted misreading occurred: a reviewer aggregated observed=0 / generated=324 over the 78 skills staged by the weekly insight run and concluded every staged skill was ungrounded echo. The docstring warnings failed because nothing guarantees the warning is read before the value — the value lives in the data file, the warning in the code that produced it. Decision: (1) remove the external_reply → observed arm entirely, not just the key, so no unreachable mapping remains to re-investigate; epistemic_kind_for uses .get(), so an external_reply row degrades to unknown rather than raising. (2) Keep _derive_source_type's external_reply return value — a provenance record, a separate layer from the epistemic tally; its retirement needs a provenance-side stocktake and is out of scope. (3) Do not rewrite history: audit.jsonl is append-only and .staged/*.meta.json are pending approval artifacts, both keep three-key records; adopt.py passes epistemic_counts through verbatim so the 78 files staged on 2026-07-25 remain adoptable, and offline readers must use .get(key, 0) rather than assume a fixed key set. Zero production read sites change (nothing indexes ['observed'] or assumes arity). Regression guards: external_reply → None, the key's absence from the emitted shape, and verbatim pass-through of a pre-ADR-0082 meta.json. Rejected alternatives: keep the key with an adjacent disclaimer field (more scaffolding around a dead value — a value needing a shipped disclaimer carries no information), retire epistemic_counts wholesale (unknown does vary, and ADR-0072 explicitly decided to keep the function — overturning that needs its own evidence), and continue the signal-first deferral (the misreading incident is itself the signal). The general form: a structural invariant documented only in prose is enforced only probabilistically; enforcement by absence replaces enforcement by documentation, the same shape as the project's security-by-absence stance. Open observation left unmechanized: the reading habit of taking a value from a data file without reading the code that produced it.


ADR-0083: Episode Logs Enter the Weekly Prompt as Hashes Only

Adds cross_day_duplicate_scan.py as the weekly report's third deterministic intake (after the log-anomaly sweep over the event stream and the state-invariant check over accumulated state) and defines the condition under which an intake may read the episode logs at all. Context: the report's C — Duplicate section asserts identity between published records, and it twice published a cross-entry claim absent from the artifacts — a '6-day consecutive re-reply' that was six distinct interlocutors (2026-06-15, recorded in principles.md only as a rejected mechanism, never as a reporting failure), and 'the first cross-day byte-identical outputs in the record' pairing four real 07-23 entries with an invented 07-21 occurrence (2026-07-25; zero bodies have ever been published on more than one day across 9052 records). Both passed review because each individual quote was real; only the pairing was fabricated, and only a comparison could catch it. Every other entry is single-entry and verifiable by construction against its source daily report. Byte-identity is a structural property (when-code-when-llm), so the pipeline was asking an LLM to perform from recall a computation that takes 0.6 seconds; principles.md Principle 5 requires cross-entry claims to rest on a deterministic input, and none existed. Decision: (1) the boundary is the OUTPUT, not the input — the scan reads logs/YYYY-MM-DD.jsonl day files (bak, audit.jsonl, skill-usage, symlinks excluded by construction) and emits only 12-hex SHA-256 digests, integer counts, filename-derived dates, and the fixed action vocabulary {post, reply, comment}; body text, post_id, target_agent, internal_note and thinking never appear, post id dropped deliberately because a digest names a group uniquely without opening a path for external strings. (2) The vocabulary is a closed character set (RENDER_CHARSET_RE) gated by TestOutputBoundary — per-fragment bait absence plus a hypothesis property over arbitrary content, verified to fail when a leak is injected. (3) Exact identity only, no normalization: near-identical wording stays a semantic reading for the LLM, keeping the scan checkable with sha256sum. (4) Window and lifetime derived in one pass, because a 'first in the record' claim cannot be answered from the window alone; day counts are days that carried published output, the denominator actually compared. (5) Absence is stated as a sentence, not a zero — the 2026-07-25 failure was naming the check undeterminable and then leading with the finding. (6) Every fault is a counted skip with a rendered reason code, never a crash — torn final lines, shape violations, unreadable files, invalid UTF-8 and lone UTF-16 surrogates; the shell discards the scan's stderr and episode logs are never deleted, so one uncaught exception would retire the instrument permanently and invisibly, and lossy decoding (errors=replace) would collide distinct invalid byte sequences into an invented duplicate. The day-file pattern uses explicit [0-9] rather than Unicode-aware \d so the render's self-controlled dates are structurally, not incidentally, true. (7) No state, so nothing to spend and no atomicity machinery needed (unlike the sweep, repaired in the same PR); exit code always 0 because a duplicate is a fact about output, not corruption. (8) Observation, never intervention — the render states that hash-equality dedup as an intervention remains rejected (principles.md appendix) so the table cannot be read back as a case for the gate it is not. Rejected alternatives: grep the comment reports instead (a rendering, window-scoped, cannot answer 'first in the record'), emit post ids (opens an external-provenance field for a convenience C does not need), scan the window only, normalized or embedding-similarity hashing (drifts toward the rejected cosine gate and stops being inspectable), and rely on Principle 5 alone (a rule with no satisfiable input is followed probabilistically — the same enforcement-by-absence reasoning as ADR-0082). Consequence: the CLAUDE.md prohibition becomes precise rather than absolute — a Claude Code session still must not read episode logs; an intake may, if and only if it emits a hash-level projection.


ADR-0084: Post-Distill Durability Gate — Judge the Produced Patterns, Not the Episode

Moves the durability verdict OUT of the distill prompt and AFTER pattern production. Context: distill emitted a near-constant quota — measured 2026-07-26 over 1,700 live episodes, patterns-per-episode median 2.00 (1:313/2:985/3:392) with only 2 episodes (0.1%) returning zero, and per-day pat/ep flat at 1.82-2.18, so output volume was a function of activity rather than of learning. The prompt's own abstain ('return an empty list, do not invent a pattern to fill the space') shared a return line with a count band and a TWO-element example, and the observed median equalled the example's arity. Reached by re-diagnosing T-INSIGHT-NOVELTY: the weekly novelty gate's proposed judgment axes were dropped because the 2026-07-18 batch of 106 candidates was already on the KNOWN side of the 2026-07-25 run (166 known themes, 121 of 238 ledger names in the same lexical family) and the gate still marked only 13/97 covered — a second axis cannot explain a first axis that is not firing, and the weekly gate is downstream of generality manufactured upstream. Decision: leave distill_episode.md byte-unchanged (every rewrite moved abstain rate and register together); add config/prompts/distill_postgate.md, one LLM call receiving the episode PLUS its numbered patterns and returning {keep:[1,3]}; judge PER PATTERN so a two-pattern episode with one grounded pattern keeps one (which also unpins yield from the example's arity, needing no count instruction anywhere); skip the call when the extraction is already empty; fail OPEN with reason=postgate_llm_none/postgate_parse/postgate_shape because this gate can only REMOVE rows the distiller already produced; promote the judged abstain to a first-class reason code ABSTAIN_NOTHING_DURABLE tallied APART from the three fault reasons and excluded from circuit-breaker failures, replacing the summary line 'all N episodes produced output' (which counted a judged abstain as output, and is why the 0.1% rate stayed invisible) with an always-emitted yield line; default the gate ON with MOLTBOOK_DISTILL_POSTGATE=0 as the opt-out, deliberately NOT following ADR-0081's default-off plist route because install-schedule regenerates the plist and re-emits such a flag only if the invoking shell exported it, so a later re-install silently reverts enforcement with no error and no log line (that flag needed manual re-verification against the live plist on 2026-07-25), and the distill plist has no env plumbing at all. Measured by offline A/B replay over one fixed 40-episode set whose baseline arm reproduced production (median 2, zero-rate 0.0%): post-gate gives 74->59 patterns, judged abstain 0%->5%, and register AT OR ABOVE the 890-pattern production baseline (I+perception-verb 86.4% vs 72.8%, median length 358 vs 357) because what it drops is precisely the patterns with no concrete moment. Rejected alternatives, all measured on the same episodes: rewriting the distill prompt as a judgment task gave the largest abstain (15%, reproduced 6/40 twice) but degraded register on every axis (I+perception-verb 54%, length 318.5) against ADR-0072's deliberately installed register; removing only the count band and example kept register but moved abstain to just 2.5%, shifting the distribution peak from 2 to 1 — the same structural defect at a lower constant; asking the SAME durability question BEFORE distilling answered durable 40/40 with zero gate faults, with plumbing and non-degeneracy both verified (a contentless control returned false), because naming a worthwhile moment costs nothing when you never have to write it — producing the pattern IS the evidence requirement, which is why the gate moved after production; reintroducing an LLM importance score was rejected against ADR-0053 Decision 1/4 (value judgment belongs at read time; a stored score is write-once and post-hoc rescoring is the write face of the self-reingestion echo loop) and ADR-0056 (two ablations, Kendall tau 0.851 at n=764 and 0.843 at n=822, identical top-3/top-5 order); adopting mem0/Letta/Zep was rejected against requests+numpy and security-by-absence, though mem0's shape of making abstain an explicit action rather than a degenerate output format WAS adopted, while the converged 'atomic normalized facts' practice was deliberately NOT adopted because it collides with ADR-0072's first-person moment-indexed register. Consequences: patterns written before and after are not homogeneous and cannot be made uniform retroactively (recorded, not smoothed); pre-registered revert criteria for the ~2026-08-01 reading are abstain below 2%, first-person below 90%, or median length below 320; and a measurement error is recorded because it nearly decided the outcome — a register proxy scanning only the first 80 characters reported 40% first-person where whole-text scanning reports 98%, since the required moment-naming clause pushes the first 'I' to a median offset of 102 characters.


ADR-0085: Unattended Weekly Fix Chain with a Single Saturday Gate

Ships scripts/weekly-pipeline.sh (launchd com.moltbook.weekly-pipeline, Sat 09:00, replacing --weekly-analysis) — an unattended chain that runs the existing weekly report as Stage 1, then diagnosis (the weekly-report-diagnosis skill in a separate headless claude -p session), deterministic F1 parsing (scripts/parse_findings.py), per-finding fix implementation in disposable git worktrees with orchestrator-run Verify (uv sync --frozen / ruff / lint-imports / pytest — the LLM's self-report is never the verdict), an advisory fix review in a third session, an advisory read-only insight-staging review, and a decision packet (scripts/build_decision_packet.py). Context: the weekly cycle had three manual touchpoints (invoking diagnosis, implementing F1s, reviewing staged insight) and each was a stall point — the 2026-07-25 0-byte report went unnoticed until read time. Diagnosis quality crossed the threshold for unattended implementation (2026-05-17 F1s wrong 3-of-3 vs 2026-07-24 F1s line-verified with a rendered reproduction). Commitments: (1) the machine never commits, pushes, or adopts — all promotion happens in one Saturday /weekly-gate session (apply, re-Verify, single human commit, adopt-staged), so CYCLES.md promotion edges #5 and #6 survive merged into one sitting; (2) scope splits at parse time — F1s closed over src/scripts/tests are code scope (auto-implemented, Verify-gated, patch exported), any F1 touching behavior-shaping artifacts (config/prompts/, .claude/) is prompt scope (draft diff presented full text at the gate, never Verify-certified), ambiguity classifies as prompt, F2 stays human-only; (3) role separation is preserved ACROSS sessions, not inside one — diagnostician, implementer, and reviewer are separate fresh-context invocations (the ADR-0013 failure mechanism was a shared conversational context), and the reviewer's verdict is advisory input to the human gate with no LLM-only approval path; (4) bounded iteration — at most 2 fix attempts per finding (the retry input includes the Verify failure output, never the same input twice), at most 5 findings per week, per-session timeouts, a 3-hour wall-clock deadline, every bound exhaustion a reason code; (5) fail-forward — every stage failure becomes a reason code and the packet is always attempted (only a missing Stage-1 report aborts), so a missing packet means the chain never ran, which is exactly what the watchdog checks; (6) an independent watchdog (scripts/pipeline_watchdog.sh, com.moltbook.watchdog) in pure bash sharing no PATH dependency (claude, uv, python) with the jobs it verifies — because the 2026-07-25 failure WAS a PATH dependency — checks each job's terminal artifact against a period-anchored expectation table, rewrites reports/PIPELINE-STATUS.md every run, and posts a Notification Center alert only when the failure set changes, with no session-hook surface since most sessions are unrelated to this pipeline; (7) self-measurement with a damped self-modification loop — logs/pipeline-metrics.jsonl gets a phase:auto record per run and a phase:gate record per decision session (F1 precision = adopted / patch-ready, recommendation match rate), and a pipeline-improvement proposal is drafted only when the same reason code recurs in two consecutive runs (the diagnosis skill's P4 shape), itself full-text gated. Rejected alternatives: fully unattended commit (overrides CYCLES.md edge #6, re-creates the ADR-0013 co-authoring loop with no third party left to reject, and the same-PR obligations cannot be self-certified); diagnosis-only automation as the primary mode (leaves the largest stall point, though it ships as the mandated first-week shadow mode via MOLTBOOK_PIPELINE_STAGES without fix); adopt-staged --yes from the chain (ADR-0012's --yes was licensed for supervised coding-agent sessions — the chain only annotates staging read-only, which ADR-0050's containment framing already legitimates); a session-start hook as the failure surface (global injection is noise — the status file, gate Step 0, and Notification Center cover the three read moments). Consequences: weekly involvement compresses to one Saturday session; ADR-0033's no-autonomous-loop observation gains a footnote (the chain is agentic per-stage but its loop is deterministic bash with hard bounds); the new failure mode is a plausible-but-wrong patch surviving Verify plus an APPROVE verdict, mitigated by the metrics loop and the human-gate escalation rule (demand the diff); install-schedule re-runs must pass the full flag set or the declarative reconcile removes the jobs; the diagnosis skill's F1 heading + Code-reference block becomes a machine contract while its own out-of-scope (F1 is a plan, not a code change) is unchanged — the fix happens in a different session, which is the role boundary working.


ADR-0086: Submolt Scope — Instrument the Question Before Handing Over the Answer

Builds a read-only submolt-scope instrument instead of granting the agent autonomy over which submolts it works in. Context: the scope is eight names an operator picked by hand, and three code facts block handing that choice over. (1) subscribed_submolts is one field doing two structurally different jobs — feed source in feed_manager.fetch_feed (read-only, reversible) and trust boundary in _passes_content_gates (a post outside the set is dropped before scoring, so it can never be commented on), the very gate ADR-0044 declined to relax as 'an intentional scope boundary, not a bug'; autonomy over the reading side is therefore silently autonomy over the acting side. (2) There is no evidence to choose on: every score in the production relevance distribution comes from a post that already passed that gate, so the hit rate outside the current eight is unmeasured by construction. (3) Subscription is a one-way ratchet — unsubscribe_submolt was deleted under security by absence (ADR-0007), so an agent with subscribe and no unsubscribe can only widen its own scope. A read-only probe established that GET /submolts lists 20 submolts (12 outside the subscribed eight) and that an unsubscribed submolt's feed returns a full page without subscribing, so observation costs no write capability. Decision: add exactly one read capability (MoltbookClient.list_submolts), split the relevance scorer into score_relevance_detailed() carrying a reason code (scored / empty_input / llm_unavailable / unparseable / out_of_range) with score_relevance() as its unchanged score-only wrapper, and ship adapters/moltbook/submolt_scope.py — a sweep that samples one feed page from every listed submolt AND every subscribed one (the subscribed set is the baseline the rest is read against), scores each with the production scorer, and appends scan_start / score / scan_end records to logs/submolt-scope-{date}.jsonl. Exposed as contemplative-agent submolt-scan (own launchd job, Thu 03:00 JST, takes the run lock) and report --submolt-scope. Constrained by construction: configure_submolt_scope without an audit_dir disables it entirely, scoring runs inside circuit_shield(), a repeating terminal 429 aborts the sweep rather than backing off through it (a rate limit is a policy signal), and nothing sampled enters the episode log, pattern store, or identity. The _passes_content_gates trust boundary is untouched. Rejected alternatives: autonomous subscribe/unsubscribe now (moves the boundary ADR-0044 declined to move, needs a removed write capability back, and runs on no evidence); relaxing the submolt filter and letting the 0.80 relevance threshold do the work (the two gates fail differently — the threshold is a small local model's probabilistic judgment with several documented ways to return a number that is not a judgment, the list is a deterministic boundary that holds when the scorer is degraded); observing inside the session cycle (~400 local LLM calls contending with the session's own generations on one 16 GB Ollama); sampling only unsubscribed submolts (halves cost, produces unreadable numbers with no baseline); doing nothing (viable, but the operator is choosing blind — nobody knows what the other twelve submolts contain). Consequences: the scope question becomes empirical and the reading doubles as a liveness check on the current eight; every future reading of the relevance number can separate a low judgment from a broken scorer, a latent defect in all prior readings; the reading is only as good as the scorer, so identity drift shifts the hit rates under it (same limitation ADR-0044 recorded); first-page sampling biases toward feed order, with post_count and subscriber_count reported alongside so a quiet submolt stays distinguishable from a recency slice. Follow-ups: if the unsubscribed set reads as uninteresting, retire the instrument (signal-first applies to removal too); otherwise split the field into feed-source and trust-boundary keys, then run an ADR-0076 style shadow in which the agent proposes a scope that is recorded but not executed — restoring unsubscribe_submolt belongs to that later step.


ADR-0087: An Optional count_tokens Capability for the Context-Budget Guard

Lets a backend that owns a real tokenizer measure the C2 context-budget pre-flight, while leaving the conservative estimator as the default. Context: ADR-0066 deliberately hardened _estimate_tokens into an upper bound (ASCII ~3 chars/token, non-ASCII/CJK 2 tokens/char) because under-counting was the failure that let a CJK-heavy prompt reach Ollama front-truncation or an MLX KV overrun, and no tokenizer was installable. A 2026-08-01 comparison against Apple's SystemLanguageModel.token_count measured the resulting over-count at 1.73-1.95x on this agent's own corpora (identity.md 232->134, constitution 867->453, rules 516->264, 37 skills 31,009->17,958, 10 patterns 479->263) — the ratio tracks the CJK rule directly, since the value layer is Japanese-dominant. The cost scales inversely with window size: on a 4,096-token backend the guard's skip floor (MIN_CLAMPED_NUM_PREDICT=2048) puts the effective input ceiling at ~1,140 real tokens, 28% of the window. Two independent things consume the rest and the ADR is careful not to conflate them: exact counting raises the ceiling to 4096-2048-64 = 1,984 real tokens (48%), so the estimator's over-count costs about 21 percentage points and the output-floor reservation costs the remaining ~50% — a separate variable this ADR deliberately does not move. Measuring for real roughly doubles usable input, which is less than the raw gap suggests. The measurement evidence was not preserved (the probe ran in a non-durable scratchpad, so there is no docs/evidence/adr-0087/ to link), which the ADR records as a defect in its own record rather than glossing. The built-in Ollama path cannot be fixed directly for two checked reasons: /api/tokenize and /api/detokenize both 404 on the running 0.30.11 (upstream ollama#12030 open since 2025-08-22, unmerged; issue ollama#12031 open), and prompt_eval_count is only returned after the call, so a pre-flight cannot consult it. Decision: add count_tokens(text) -> int as a separate optional Protocol, TokenCountingBackend(LLMBackend, Protocol), NOT as an LLMBackend member — Protocols are structural, so a member declared there is required of every implementer regardless of default body, which would make the sibling contemplative-agent-cloud and -mlx backends non-conformant for a capability neither can honor; a tokenizer is not the one-line property context_window is. Resolve it structurally at runtime via getattr plus a callable check (no isinstance, which would accept a non-callable attribute). Prefer the backend's count but never trust it: reject a value that is not a plain int (bool excluded explicitly as an int subclass), is negative, or is 0 for text with content — over-counting only wastes budget, under-counting sends over-window input into the exact failure the guard prevents. Bound magnitude as well as shape via MAX_CHARS_PER_TOKEN=50: a well-typed positive but wildly-too-small count (5 for a 50,000-character prompt) otherwise passes every shape check and tells the guard the input is nearly free, and the realistic source is a mis-calibrated tokenizer in a sibling backend rather than malice. The bound is tokenization density, not a ratio against the estimator, so an efficient tokenization of repetitive text is not mistaken for a fault. On the counted path the clamp withholds BACKEND_FRAMING_RESERVE=64 tokens for the chat-template role separators and control tokens no caller-side count sees, since clamping to the exact measured remainder would put input plus output flush against the window; the estimator path reserves nothing, its over-count already being one, which is also what leaves the Ollama path's clamp arithmetic unchanged. Measure both halves, validate afterwards, adopt or reject them together (a measured system prompt mixed with an estimated user prompt describes neither), reporting the system-side reason when both fail so a replay can predict it. A counter fault never records a circuit-breaker failure — failing to measure a call is not the call failing. Telemetry gains dense token_count_source and input_tokens plus a sparse token_count_fallback_reason from a fixed vocabulary (counter_exception, counter_none, counter_type, counter_negative, counter_degenerate, counter_implausible); absence of a counter is the default, not a fallback, so it gets no reason code. MIN_CLAMPED_NUM_PREDICT is untouched here — moving the measurement and the floor together would make their contributions indistinguishable (one pipeline variable at a time). Amended 2026-08-01 (separate change, after this one landed): the floor drops from 2048 to 128 and the clamp stays. The floor had been doing two jobs — refusing an absurd remainder (cheap and correct) and predicting that a usable answer needs 2048 tokens (never validated, and measured ~6x over: comment output p50 352 / p90 507 tokens, n=2,366 counted with a real tokenizer over reports/comment-reports/). Only the prediction is retired; 128 is bounded below by generate_for_api's own +50 minimum and Ollama's default output length of 128, and sits under the p90 so a complete comment is never turned away at the door. The question the floor was guessing at — is this generation long enough to be usable — is answered downstream by drop_truncated (audit M2) from the actual done_reason, so the change is from predicting to trying and measuring; a skip suppresses an action outright whereas a truncation drop costs one generation and is visible as truncated_dropped. On Ollama the floor is inert (it fires only above 32,640 tokens of input against a ~20.3K-token high-water system prompt, pinned by TestClampFloorIsInertOnOllama); it bites on a small-window backend, where 2048 was 50% of a 4,096 window. The fault column TestNarrowHeadroomTruncationF7 covers the newly reachable path (a clamped generation cut mid-sentence) at the existing LLMBackend seam. _NOVELTY_OUTPUT_RESERVE keeps 2048 as the judge's own reservation rather than a copy of the floor. Accepted residuals recorded with the amendment: truncation-drops replace skips in the newly opened band and cost a full prompt-eval; internal callers (drop_truncated=False) see short partial text more often; and the M2 gate that now carries the too-short question is conditional on BackendResult.finish_reason, which the Protocol permits to be None — a pre-existing gap independent of the floor, reachable more often now, tracked separately rather than folded in. Explicitly NOT a consequence: the floor was never the backstop against an under-counting backend tokenizer; the clamp spends the remaining budget rather than leaving it as slack, so the window overrun is delta minus BACKEND_FRAMING_RESERVE regardless of the floor, confirmed by a probe measuring an identical 335-token overrun at both 2048 and 128. The chaos fault column ships in the same change via TokenCountingChaosBackend plus a hypothesis fuzz over the return type. Rejected alternatives: putting count_tokens on LLMBackend itself (the symmetric-looking option, but it imposes a tokenizer requirement on backends that cannot have one); giving the Protocol member a default body (structural conformance still requires it, so it breaks external backends while appearing not to); correcting the estimator with a fixed CJK divisor (the factor is model-specific, measured on one corpus, and tightening a safety bound on a sample is how the under-count failure reopens); calling /api/tokenize on Ollama (unavailable, not undesirable); using BackendResult.prompt_tokens (post-hoc, so it is a calibration instrument for the separate clamp-floor question, not a pre-flight). Consequences: a tokenizer-owning backend gets the window it actually has; existing external backends are unchanged by implementing nothing, pinned by regression tests; every rejection path degrades to the prior behavior so the ceiling is never disabled; but two measurement paths for one quantity now disagree by up to 1.95x (hence the dense source field), the backend's tokenizer sits on every guarded call's critical path with no latency bound, and a second capability Protocol sets a precedent a third could abuse. Follow-ups: adopt Ollama real counting only after measuring the extra HTTP round-trip on the unattended schedule if ollama#12030 lands; re-read the estimator's real-world ratio from production rows now that input_tokens sits beside prompt_eval_count (the clamp floor itself was resolved by the 2026-08-01 amendment, from output-side measurement, so it no longer waits on this); leave insight_novelty packing on the estimator, since a relaxed guard makes the packer tighter than the pre-flight, which is the safe direction its own contract names.


pattern-composition instruments

Read-only distributions over Layer 2 knowledge patterns: consumed-view supply and seed-independent diversity (the echo-chamber detector). Instruments are observability, never intervention — they feed the operator, not gates or ranking. Calibrated by a three-point scale: unrelated-text floor cosine, corpus pairwise mean, and consumed-view top band — re-measured whenever the seed geometry changes (introduction values: floor ~0.33–0.46, corpus mean 0.554, top band ~0.68–0.77 under the pre-ADR-0072 seed). A third grounding-composition instrument shipped with ADR-0071 was removed by ADR-0072: under per-episode distillation its source_type reading is constant, and an instrument whose reading changes no action violates signal-first.


Memory in the Age of AI Agents: A Survey (2025)

Survey unifying the fragmented agent-memory literature under a Forms / Functions / Dynamics framework. Cited by ADR-0071 as evidence that memory-distillation pipelines are converging on the same three-layer-plus-distill structure, which motivates instrumenting this repo's differentiator (the views layer) rather than adding more convergent mechanism.


Memory for Autonomous LLM Agents: Mechanisms, Evaluation, and Emerging Frontiers (2026)

Formalizes agent memory as a write–manage–read loop with a three-dimensional taxonomy (temporal scope, representational substrate, control policy). Cited by ADR-0071 for the same convergence argument; supplies field-standard vocabulary the instruments' readings can be anchored to.


AKC ADR-0015: Self-reingestion observability (visibility without intervention)

Sibling-repo decision: make the self-generated share of memory visible as a Measure-phase observability concern while changing no retrieval, ranking, or promotion. ADR-0071 adopts the same shape for all three instruments.


AKC ADR-0016: Measuring thinking-centric phases (a biased instrument is worse than none)

Sibling-repo decision on instrument design: report per-phase character rather than single counts, and treat a systematically biased instrument as worse than no instrument. ADR-0071's fixed ambiguity note (empty/low view supply is undecidable between missing patterns and a stale seed) implements that lesson.