Workstream 05: Guardian Intelligence
Status On develop
- Workstream 05 is only partially shipped on
develop.
Paired Research
- primary design docs: 01. Guardian Thesis, 02. Human Model And Memory, 11. Superiority Program, and 14. Seraph Memory SOTA Roadmap
- synthesis context: 00. Research Synthesis
Shipped On develop
- soul-backed persistent identity
- long-term vector memory and consolidation
- hierarchical goals and progress tracking
- strategist agent with restricted guardian tool set
- daily briefing, evening review, activity digest, and weekly activity review foundations
- end-of-day goal report generation now compares local screen-derived activity summaries with active/completed goals, stores the redacted report as an observer memory episode, defaults to deterministic local drafting unless
END_OF_DAY_REPORT_LLM_ENABLED=true, and can deliver by SMTP only when email is explicitly configured, preview is deliberately acknowledged/disabled, and the recipient is allowlisted - Settings exposes report delivery readiness, manual preview/send/test-email actions, private report receipts, and screen-analysis budget posture so operators can verify the loop without editing env files blindly
- preserved screen artifacts and end-of-day report artifacts give the learning loop a durable local evidence trail: allowed images, redacted provider output, normalized analysis JSON, report text, and report JSON stay under configured archive roots for future re-analysis
- observer-driven user-state and attention-budget modeling
- observer salience, confidence, and interruption-cost scoring that feeds guardian state and proactive policy
- explicit guardian-state synthesis that unifies observer context, memory, current session, recent sessions, confidence, and observer salience signals for downstream agent paths
- explicit intervention policy that distinguishes act, bundle, defer, request-approval, and stay-silent outcomes for proactive guardian messages, including low-salience suppression and high-interruption bundling
- persisted guardian intervention records and explicit user-feedback capture that flow back into guardian-state summaries
- first multi-signal outcome-learning loop that uses recent outcomes on the same intervention type to reduce interruptions after negative feedback and prefer direct delivery or native reroute after repeated positive/acknowledged outcomes
- second-layer salience calibration that promotes aligned active-work signals and allows grounded high-salience nudges to bypass generic high-interruption bundling outside focus mode
- deeper guardian behavioral eval coverage that proves grounded high-salience delivery versus degraded-confidence defer behavior at the delivery gate
- deeper guardian behavioral eval coverage that proves strategist tick can combine learned delivery bias, native delivery, and continuity-state visibility in one deterministic contract
- guardian world model that now carries current focus, focus provenance, active commitments, active projects, active constraints, recurring patterns, active routines, collaborators, recurring obligations, project timelines, memory signals, corroboration sources, continuity threads, open loops or pressure, recent execution pressure, focus alignment, intervention receptivity, and explicit judgment-risk notes inside guardian state
- guardian state now also carries learned communication guidance derived from recent intervention outcomes, including timing, suppression, blocked-state, and thread-preference bias, instead of only raw outcome history
- guardian world-model receptivity and intervention policy can now learn blocked-state async handling instead of only direct/native/timing bias
- contradiction-aware world-model confidence now carries focus provenance and explicit judgment risks, and live guardian learning now resolves the strongest global, thread, project, or thread-plus-project signal before policy-time arbitration against durable procedural memory
- long-horizon guardian learning now prioritizes live-project supporting context across collaborators, obligations, routines, and timeline recall, treats stale execution pressure as a first-class contradiction against the live project anchor, lowers receptivity further when negative intervention trends line up with that conflicting execution evidence, and tracks multi-day plus scheduled outcome spread so guardian state can surface explicit goal-alignment, routine, and collaborator watchpoints instead of only short-window bias summaries
- long-horizon guardian learning now also feeds abstention policy directly: repeated multi-day negative outcomes can bundle or defer low-urgency guidance, unstable scheduled outcomes can defer routine check-ins, and guardian state surfaces those abstention and scheduled-deferral pressures explicitly instead of leaving them implicit in raw outcome counts
- guardian state now also prioritizes live-project cross-thread continuity from recent sessions, carries matching recent-thread commitments into
next_upand dominant-thread synthesis, and surfaces explicit follow-through risk when open project commitments and recent execution setbacks line up on the same live project - guardian world-model synthesis now also ranks competing projects from observer, recent-session, memory, and execution evidence, prefers stronger cross-source project anchors over passive list order, preserves richer canonical project labels, and surfaces explicit ambiguity or drift risks when project evidence stays split
- guardian learning diagnostics now also surface explicit live-versus-procedural bias conflicts, tell the operator when fresh live outcomes are overruling older procedural guidance versus when procedural memory is still overriding live outcomes, and add a conservative judgment guardrail when competing project anchors line up with negative intervention trends
- additive memory-provider extensibility now exposes extension-backed provider inventory, lifecycle-managed provider config/toggle surfaces, additive retrieval integration, provider-backed user/project modeling augmentation, query-matched canonical project hints that can selectively activate provider-backed user modeling even when live recent-project state is sparse, a provider-neutral adapter model with per-capability sync-policy contracts, a machine-readable canonical-memory contract, explicit provenance taxonomy, stale-provider-evidence suppression, usefulness-ranked provider diagnostics with explicit provenance lines inside guardian state, explicit canonical-memory reconciliation diagnostics through the memory API and guardian state, post-canonical provider writeback guardrails that suppress duplicate, low-quality, or project-anchorless project-scoped canonical memories before additive mirroring, capability-state governance, canonical-memory ownership rules, and clean fallback when an external provider is unavailable
- guardian world-model synthesis now also exposes an explicit user-model substrate with inferred interruption, communication, thread, and cadence preferences, grounded-versus-partial user-model confidence, and evidence-backed preference-inference diagnostics instead of leaving those judgments implicit inside raw constraints plus learning bias text
- guardian state now also exposes explicit intent-uncertainty diagnostics with
clarify,proceed_with_caution, ordefer_or_clarifyrecommendations when ambiguous referents, split project anchors, split preference evidence, or degraded observer confidence make a confident intervention unsafe - guardian state now also emits explicit judgment-proof lines for split project evidence, split interaction-style evidence, ambiguous referents, and degraded observer grounding so the canonical guardian prompt carries compact proof of why a judgment is conservative
- the operator guardian-state surface now exposes that same confidence, intent-resolution, judgment-proof, judgment-risk, and next-step contract directly in the cockpit instead of leaving operators on raw observer fields while the canonical guardian state stays internal
- guardian memory now also ships a named
guardian_memory_qualitybenchmark suite with reasoning-heavy engineering-memory retrieval checks over workflow, approval, audit, artifact, and repo or PR continuity bundles, contradiction-aware recall ranking, explicit selective-forgetting diagnostics, operator-visible memory benchmark and failure-report surfaces, and CI-gated regression proof instead of leaving memory quality implied by flat runtime scenario lists - additive memory providers now also pass a stricter pre-context quality gate: provider manifests expose declaration policy, provider hits must carry evidence IDs, confidence, provenance, privacy boundary, freshness, conflict behavior, and suppression rules before entering guardian context, noisy/stale/unsafe/authority-drifting evidence is suppressed with receipts, provider-returned secret echoes are redacted, and
/api/operator/memory-provider-quality-gateplus thememory_provider_quality_gatesuite pin the deterministic proof without claiming live external-memory superiority - guardian judgment now also ships an explicit restraint contract: the canonical guardian state carries action posture, restraint reasons, inspectable user-model evidence facets, and user-model benchmark diagnostics; the operator guardian-state surface exposes those fields directly; and a named
guardian_user_model_restraintbenchmark suite proves continuity-aware user modeling, ambiguity-aware clarification, and restraint-before-action behavior instead of leaving clarify-first judgment as prompt folklore - guardian-safe voice/media now also has a named deterministic proof gate:
guardian_safe_multimodal_voicerequires any voice, speech, browser-vision, image/media-analysis, or media-delivery capability family to justify timing, accessibility, situational-awareness, or intervention-quality value before it can be treated as useful guardian reach - guardian learning arbitration now also has a named v2 proof gate:
guardian_learning_arbitration_v2and/api/operator/guardian-learning-arbitrationcover act, defer, bundle, clarify, approval, and stay-silent outcomes across stale memory, conflicting provider/workflow evidence, ambiguous referents, degraded observer confidence, unsafe capability context, and repeated negative outcomes without claiming guardian-intelligence superiority - live guardian-learning and memory-provider outcome proof now has Batch BZ receipts:
live_guardian_learning_quality,guardian_intervention_outcome_cohorts,memory_provider_ecosystem_maturity_v1,canonical_memory_reconciliation_v2,provider_usefulness_regression, and/api/operator/live-guardian-learning-qualityexpose typed outcome cohorts, false-positive and false-negative learning receipts, stale-evidence decay, provider usefulness/degradation/quarantine, canonical precedence, advisory writeback, deletion/export receipts, and provider usefulness regressions without claiming guardian-intelligence superiority, solved learning, memory superiority, memory-provider parity, or live human-outcome superiority - live human-outcome and causal guardian-learning proof now has Batch CF receipts:
live_human_outcome_quality_study,guardian_learning_causal_attribution,memory_provider_live_regression_monitor, and/api/operator/live-human-outcome-learning-proofexpose consent-aware anonymized recorded-live outcome cohorts, correction/harm/follow-through receipts, residual bias and coverage limits, counterfactual causal-attribution receipts, reversible learning changes, stale-evidence decay, provider usefulness deltas, privacy regression monitoring, and quarantine without claiming guardian-intelligence superiority, solved live learning, memory superiority, memory-provider parity, or live human-outcome superiority - independent guardian-learning outcome and memory-provider parity-matrix proof now has Batch CM receipts:
independent_outcome_cohort_review,task_scoped_causal_learning,memory_provider_parity_matrix, and/api/operator/independent-learning-memory-parityexpose independent evaluator metadata, sample and power rationale, consent/anonymization, adverse-event review, bounded outcome claims, task-scoped counterfactual causal receipts, confounder boundaries, rollback authority, canonical/advisory memory-provider comparison, privacy-regression quarantine, delete/export receipts, and blocked claims without claiming guardian-intelligence superiority, solved live learning, live human-outcome superiority, memory superiority, full memory-provider parity, production readiness, or full parity - longitudinal guardian-learning and memory-provider outcome operations now have Batch CV receipts:
longitudinal_guardian_outcome_study,named_baseline_memory_comparison,learning_safety_monitor_v2, and/api/operator/longitudinal-guardian-outcomesexpose 60-plus day outcome windows, task families, named baseline source/version/limitations, pressure-only baseline comparison, independent evaluator protocol, consent/withdrawal/anonymization, adverse-event review, reversible learning-policy deltas, rollback authority, canonical/advisory provider comparison, stale-behavior blocking, privacy-regression quarantine, delete/export propagation, provider reinstatement review, safe redacted receipts, and blocked claims without claiming guardian-intelligence superiority, solved live learning, solved long-term learning, live human-outcome superiority, memory superiority, full memory-provider parity, named baseline wins, production readiness, or full parity - generalized guardian-outcome and memory-provider parity proof now has Batch DD receipts:
generalized_guardian_outcome_study_v1,full_memory_provider_parity_matrix_v1,causal_learning_outcome_thresholds_v1,memory_baseline_comparison_v1, and/api/operator/generalized-guardian-outcomesexpose predeclared act/defer/bundle/clarify/approval/stay-silent/recovery/follow-through outcome protocols, independent evaluator metadata, consent/withdrawal/anonymization, fairness and adverse-event review, expanded provider dimension matrices, causal threshold gates, current-source-limited pressure baselines, redacted receipts, and blocked claims without claiming guardian-intelligence superiority, solved learning, live-human-outcome superiority, memory superiority, full memory-provider parity, named baseline wins, production readiness, or full parity - governed self-evolution can now turn explicit objectives and trace observations into eval-scored review candidates for declarative capability assets while keeping the resulting changes bounded, reviewable, and outside live guardian self-modification, with explicit change-summary and review-risk receipts carried into the saved proposal and PR draft
- governed self-improvement now also blocks obvious preference-collapse drift, carries explicit canary-only and rollback-ready benchmark-gate receipts on saved candidates, and exposes a dedicated operator-visible governed-improvement benchmark surface with recent saved proposal receipts instead of treating candidate generation alone as proof
Working On Now
- this workstream remains central in the repo-wide horizon through stronger learning quality after the corroboration-aware world-model and scoped procedural-guidance pass shipped
- the
observer-salience-and-confidence-modelfoundation is now shipped ondevelop - the current evidence-weighted learning layer and first salience-calibration pass are now shipped, and the next major gap is deeper modeling plus richer long-horizon learning rather than more missing observer fields
-
world-model-memory-fusion-v9,guardian-learning-policy-v9, andguardian-behavioral-evals-v9are now represented in the shipped batch, so the next gap shifts to project-graph quality, longer-horizon learning, and stronger cross-thread policy adaptation rather than more missing first-pass structure
Still To Do On develop
- richer human world modeling that goes beyond the new project/routine/collaborator/obligation/timeline-aware world-model layer plus active blockers, next-up, dominant-thread synthesis, memory buckets, focus-provenance tracking, the explicit user-model confidence or preference-inference or evidence-facet substrate, the new action-posture plus restraint-reason contract, stale-support and stale-execution contradiction grounding, and contradiction-aware confidence downgrades
- stronger learning loops based on generalized outcome-superiority evidence beyond Batch DD bounded generalized outcome/provider-matrix receipts, plus broader provider/live-workload coverage beyond the current evidence-weighted scoped delivery/channel/escalation/timing/suppression/blocked-state layer, global-thread-project live-signal resolution pass, live-versus-durable arbitration pass, live-versus-procedural conflict diagnostics, negative-trend-plus-execution-and-anchor ambiguity guardrail, multi-day and scheduled-outcome watchpoint pass, cross-thread commitment carryover pass, guardian user-model/restraint benchmark proof, first multi-project arbitration pass, Batch BZ typed outcome/provider regression receipts, Batch CF live-regression monitors, Batch CM independent outcome/memory-provider parity-matrix receipts, Batch CV longitudinal outcome/safety receipts, and Batch DD generalized outcome/provider-matrix receipts
- stronger salience calibration and confidence quality beyond the first aligned-work/high-salience pass
- stronger linkage between guardian state, execution choices, and feedback-driven policy adaptation
- deeper memory-provider use beyond the shipped additive retrieval, additive user/project modeling, stale/usefulness-ranked provider diagnostics, guarded post-canonical writeback, inventory/governance layer, the pre-context provider quality gate, the guardian-memory benchmark plus contradiction-aware selective-forgetting proof, Batch BZ provider usefulness/degradation/quarantine plus canonical reconciliation receipts, Batch CF live-regression monitors, Batch CM dimension-scoped provider parity matrix, Batch CV longitudinal provider-operation receipts, and Batch DD expanded provider-matrix receipts, especially broader ecosystem coverage, live-provider attestation, richer provider-specific quality tuning, stronger long-horizon usefulness diagnostics, and exact claim-ledger permission before any memory-superiority or full provider-parity wording
Memory Upgrade Program Record
The upgraded memory system is now complete through Batches A, B, and C. This section preserves the delivered batch structure as implementation history and architecture context. Active follow-up work should be tracked through GitHub issues, PRs, and the GitHub Project rather than extending this file into a task tracker.
The delivered shape stayed split into three implementation batches:
Batch A: Structured memory foundation
memory-eval-harness-v1typed-memory-schema-v1memory-kinds-and-provenance-v1entity-and-project-linking-v1bounded-memory-snapshots-v1
Batch B: Episodic and observer-driven retrieval
episodic-memory-events-v1observer-episodic-fusion-v1session-search-fts-and-event-index-v1hybrid-memory-retrieval-v1guardian-state-retrieval-planner-v1
Batch C: Learning, consolidation, and decay
memory-flush-lifecycle-hooks-v1multi-stage-memory-consolidation-v1soul-projection-and-structured-profile-v1procedural-memory-from-outcomes-v1memory-decay-contradiction-and-archive-v1guardian-memory-behavioral-evals-v1
The batch split is the right implementation shape because the dependencies are real:
- Batch A creates the durable typed substrate
- Batch B turns sessions and observer signals into usable episodic recall
- Batch C makes that memory updateable, policy-relevant, and behaviorally testable
Each internal slice in these memory batches should close with:
- targeted validation commands
- a subagent review pass for bugs, regressions, and misleading claims
- a short implementation log entry in this document before the slice is treated as complete
Batch A Review Log
This section records the landed Batch A slices and their validation and review receipts.
memory-eval-harness-v1
- scope:
- expanded
backend/src/evals/harness.pywith deterministic memory-behavior scenarios for linked commitment recall, linked collaborator recall, bounded snapshot stability, and supersession filtering - taught the eval harness DB patch helpers about
src.memory.repository.get_sessionand cleared the bounded snapshot cache around each scenario so the new memory scenarios run in isolated process state - updated the existing session-consolidation evals so they still represent a clean successful dual-write path under the new structured-memory substrate instead of tripping
partially_succeededfrom mock-only vector writes - extended
backend/tests/test_eval_harness.pyso the aggregate runtime-eval contracts now assert the new memory scenario details directly
- expanded
- validation:
backend/.venv/bin/python -m py_compile backend/src/evals/harness.py backend/tests/test_eval_harness.pybackend/.venv/bin/python -m pytest backend/tests/test_eval_harness.py::test_run_runtime_evals_passes_all_scenarios -qbackend/.venv/bin/python -m pytest backend/tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_details -qbackend/.venv/bin/python -m pytest backend/tests/test_eval_harness.py -qbackend/.venv/bin/python -m pytest backend/tests/test_memory_repository.py backend/tests/test_consolidator.py backend/tests/test_guardian_state.py backend/tests/test_memory_snapshots.py backend/tests/test_eval_harness.py -q
- subagent review:
- reviewer:
Zeno(019d24e8-51f8-7402-b24e-82c872dd4813) - initial findings:
- the first linked commitment and collaborator scenarios could still pass from the generic structured kind buckets, so they did not actually prove the project-linked recall path
- the first bounded snapshot scenario built the “new session” state without creating that session first, so it could fall back to the global snapshot lane instead of exercising real per-session freezing
- the compactness check was measured before later memory changes landed, which made the “remains compact after later changes” claim weaker than intended
- fixed before the slice stayed marked complete:
- seeded higher-importance unrelated commitments and collaborators, captured a baseline no-active-project structured bundle, and then asserted the Atlas-linked items only reappear once active-project linking is in play
- created a real
snapshot-newsession before the fresh-state build and asserted that the scenario uses an actual session record - moved the bounded line-count check to the post-change states so the compactness contract is evaluated after later memory writes land
- narrowed the commitment-continuity scenario description so it now claims only the memory-context recall behavior that the eval actually proves
- final recheck:
- no remaining material issue was found after the linked-recall proof, real-session snapshot path, and wording fixes
- reviewer:
typed-memory-schema-v1
- scope:
- added structured SQLite memory tables and enums for typed memories, entities, sources, edges, and snapshots
- added
memory_repositoryCRUD helpers for entities, memories, edges, and snapshots - made session consolidation dual-write into the structured store while keeping the existing vector path alive
- enabled SQLite foreign-key enforcement in both runtime and test engines
- validation:
python3 -m py_compile backend/src/db/engine.py backend/src/db/models.py backend/src/memory/repository.py backend/src/memory/consolidator.py backend/tests/conftest.py backend/tests/test_db_engine.py backend/tests/test_memory_repository.py backend/tests/test_consolidator.py backend/tests/test_consolidation_reliability.pybackend/.venv/bin/python -m pytest backend/tests/test_consolidator.py -qbackend/.venv/bin/python -m pytest backend/tests/test_db_engine.py backend/tests/test_memory_repository.py backend/tests/test_consolidation_reliability.py backend/tests/test_vector_store.py backend/tests/test_guardian_state.py -q
- subagent review:
- reviewer:
Euclid(019d24ad-f0fa-7700-af86-85b79cd7aea5) - initial findings:
- legacy
category -> kindbackfill was missing - dual-write inconsistencies were reported as clean success
- SQLite foreign keys were declared but not enforced
- the substrate exposed richer kinds and links than the live writer actually used
- legacy
- fixed before commit:
- backfilled legacy
kindvalues fromcategory - changed consolidation audit outcome to
background_task_partially_succeededwhen vector and structured writes diverge - added foreign-key PRAGMA setup to runtime and tests
- added tests for legacy backfill, partial-success audit reporting, and enforced entity links
- backfilled legacy
- deferred to later Batch A slices:
- richer memory kinds beyond the legacy four writer buckets
- entity and project linking in the live writer
- bounded snapshot generation and consumption
- reviewer:
memory-kinds-and-provenance-v1
- scope:
- added
backend/src/memory/types.pyto normalize kind/category mapping, bucket mapping, and consolidation payload parsing - upgraded session consolidation to accept typed memory objects with kind, summary, confidence, importance, and last-confirmed provenance while remaining backward-compatible with the legacy four lists
- started feeding richer structured memory kinds into guardian state and the world model instead of dropping everything back to coarse vector categories
- added
- validation:
python3 -m py_compile backend/src/memory/types.py backend/src/memory/consolidator.py backend/src/memory/repository.py backend/src/guardian/state.py backend/src/guardian/world_model.py backend/tests/test_memory_repository.py backend/tests/test_consolidator.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_memory_repository.py backend/tests/test_consolidator.py backend/tests/test_guardian_state.py -qbackend/.venv/bin/python -m pytest backend/tests/test_consolidation_reliability.py -qbackend/.venv/bin/python -m pytest backend/tests/test_guardian_state.py backend/tests/test_consolidator.py backend/tests/test_memory_repository.py backend/tests/test_consolidation_reliability.py -q
- review notes:
- local regression caught and fixed before commit:
- project-memory enrichment briefly leaked execution-pressure lines into
world_model.active_projects; the final slice keeps those lines inproject_statewhileactive_projectsstays project-only
- project-memory enrichment briefly leaked execution-pressure lines into
- subagent review:
- reviewer:
Ptolemy(019d24c7-9a43-7860-8b66-b5d77adc3187) - findings:
- caught the
project_state/active_projectsregression while guardian-state tests were failing - flagged that richer kinds still collapse through the vector category lane, so only the structured bundle preserves typed recall behavior for now
- caught the
- fixed before the slice stayed marked complete:
- separated
active_project_signalsfromproject_stateso execution-pressure lines stay out ofactive_projects
- separated
- reviewer:
- secondary review:
- reviewer:
Dalton(019d24c9-0610-7102-a84f-a58874fb38f9) - findings:
- confirmed the structured-memory bundle does change runtime behavior even though the vector layer still uses coarse categories
- requested a typed
commitment/projecttest to prove structured kinds survive while vector writes keep coarse categories
- fixed after review:
- added a typed project-and-commitment consolidation test that proves structured
kindvalues are preserved while vector writes still route through coarsefact/goalcategories
- added a typed project-and-commitment consolidation test that proves structured
- reviewer:
- deferred to later Batch A slices:
- project/entity linking still relies on names embedded in metadata, not real entity ids
- the vector recall path still uses coarse categories until Batch B hybrid retrieval work lands
- bounded snapshot generation still does not project this richer memory into a stable session-start snapshot
- local regression caught and fixed before commit:
entity-and-project-linking-v1
- scope:
- added
backend/src/memory/linking.pyto resolve conservative subject and project entity links during typed consolidation writes - extended
memory_repositorywith exact or alias entity resolution plus linked-memory lookup by project or subject ids - upgraded guardian-state synthesis to boost project-linked structured memories for currently active projects instead of relying only on global importance ordering
- kept the runtime claim narrow: subject-side entity ids are now stored, but live guardian recall currently consumes project-linked recall only
- added
- validation:
python3 -m py_compile backend/src/memory/linking.py backend/src/memory/repository.py backend/src/memory/consolidator.py backend/src/guardian/state.py backend/tests/test_memory_repository.py backend/tests/test_consolidator.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_memory_repository.py backend/tests/test_consolidator.py backend/tests/test_guardian_state.py -q
- subagent review:
- reviewer:
Volta(019d24d3-df48-7700-9943-e69cbfaf93aa) - initial findings:
- entity linking had moved outside the per-item structured-write fault boundary, which would have let a single link failure abort the whole consolidation run
- the runtime slice only consumes project-linked recall today, so broader “entity recall” wording would overstate what is live
- the original tests proved project-link persistence but not the collaborator subject-link path
- project-linked recall could still fragment when a project memory landed without an explicit
projectfield and the observer only exposed a shorter active-project token
- fixed before the slice stayed marked complete:
- moved
resolve_memory_links(...)back inside the per-item structured-writetryblock so link failures are counted as partial writes instead of aborting the whole run - added a consolidation test that forces entity-link failure and proves the audit outcome stays
background_task_partially_succeeded - added collaborator-link assertions so subject and project ids are both exercised in tests
- added a conservative unique project-token fallback in entity resolution plus a guardian-state test that proves
Atlascan still resolve linked memories stored underAtlas launchwhen that match is unique
- moved
- final recheck:
- no remaining high-severity issue was found after the fault-isolation and unique-project-fallback fixes
- deferred to later slices:
- guardian-state recall still does not consume subject-side entity ids, only project-linked ids
- richer entity-driven retrieval beyond active-project boosts belongs in the later hybrid retrieval planner work
- aggregate PR follow-up:
- external PR review caught that project linking was also injecting memory summaries as project aliases, which could merge unrelated projects that share generic summaries
- fixed on the branch by removing summary-derived aliases from project entity creation and adding a regression test that proves two projects with the same summary still resolve to different project entities
- subagent recheck:
- reviewer:
Volta(019d24d3-df48-7700-9943-e69cbfaf93aa) - result: no material findings after the alias-removal fix, regression test, and wording update
- reviewer:
- reviewer:
bounded-memory-snapshots-v1
- scope:
- added
backend/src/memory/snapshots.pyto build a compact bounded guardian snapshot from soul identity plus structured memory kinds - refreshes that snapshot after consolidation so durable semantic changes project into the always-on guardian prefix without rebuilding it ad hoc every call
- changed guardian-state synthesis to use a session-frozen bounded snapshot plus a live todo overlay, with structured-memory recomputation as the degraded fallback if snapshot load or save fails
- keyed the in-process session freeze by session lifecycle (
session_id + created_at) so reused session ids do not keep serving stale bounded recall
- added
- validation:
python3 -m py_compile backend/src/memory/snapshots.py backend/src/memory/consolidator.py backend/src/guardian/state.py backend/tests/conftest.py backend/tests/test_memory_snapshots.py backend/tests/test_consolidator.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_memory_snapshots.py backend/tests/test_consolidator.py backend/tests/test_guardian_state.py -q
- subagent review:
- reviewer:
Laplace(019d24da-bfeb-7360-8b3a-6e9bcef8fcb7) - initial findings:
- the first cut reused one global snapshot blindly, so it was neither fresh on new sessions nor stable against unrelated snapshot churn
- the degraded path fell back to soul-only lines and silently dropped structured bounded recall when snapshot IO failed
- the first session-freeze pass still needed an invalidation path for reused session ids in the same process
- fixed before the slice stayed marked complete:
get_or_create_bounded_guardian_snapshot(...)now recomputes source hash against live soul and structured memory, refreshes stale global snapshots, and freezes the resolved content per session- guardian-state fallback now recomputes bounded recall from structured memory via
render_bounded_guardian_snapshot(...)instead of collapsing to soul-only memory - the session cache key now includes the session lifecycle (
created_at) so deleting and recreating the same session id invalidates the frozen snapshot - added tests for stale snapshot refresh, per-session freeze, session-id reuse invalidation, consolidation-triggered snapshot refresh, and structured-memory fallback when snapshot load fails
- final recheck:
- no remaining high-severity or medium-severity issue was reported after the session-lifecycle and degraded-fallback fixes
- deferred to later slices:
- snapshot promotion still runs from consolidation boundaries; broader lifecycle hooks near compaction and workflow boundaries belong in the later flush-lifecycle slice
- bounded snapshot contents are still semantic-only and do not yet include procedural memory or episodic recall planning
- reviewer:
Batch B Review Log
This section records the landed Batch B slices and their validation and review receipts.
episodic-memory-events-v1
- scope:
- added a structured
memory_episodestable plusMemoryEpisodeTypeso Seraph now has a first-class episodic substrate alongside semantic memory - extended
memory_repositorywith typed episode create and list helpers, including filtering by session, entity links, and episode type - added a first live automatic writer path in
SessionManager.add_message(...)that mirrors sessionuserandassistantmessages intoconversationepisodes and sessionstepmessages intotoolorworkflowepisodes - made episodic capture fail open so message persistence does not regress if the episodic write path fails
- updated session deletion to remove episodes linked either by
session_idorsource_message_idbefore message teardown so the new foreign keys do not block cleanup
- added a structured
- validation:
backend/.venv/bin/python -m py_compile backend/src/db/models.py backend/src/memory/repository.py backend/src/memory/episodes.py backend/src/agent/session.py backend/tests/test_memory_episodes.pybackend/.venv/bin/python -m pytest backend/tests/test_memory_episodes.py backend/tests/test_session.py backend/tests/test_memory_repository.py backend/tests/test_session_search_tool.py backend/tests/test_db_engine.py -q
- subagent review:
- reviewer:
Euclid(019d24ad-f0fa-7700-af86-85b79cd7aea5) - initial findings:
- episodic capture was inside the same message transaction without a fail-open boundary, so an episode-write failure could roll back chat message persistence
- session deletion only cleaned up episodes by
session_id, so episodes linked only bysource_message_idcould block message or session deletion under foreign-key enforcement - the first implementation wording needed to stay narrow because the live automatic writer path only emitted
conversation,tool, andworkflowepisodes, whiledecisionandobserverremained substrate-ready types
- fixed before the slice stayed marked complete:
- wrapped episodic writes in a nested fail-open boundary so message persistence survives episodic-write failures
- updated session deletion to remove episodes matched either by
session_idor by the deleted messages'source_message_id - added regression tests for fail-open episode writes and for deletion of episodes linked only through
source_message_id - narrowed the scope wording so the live writer claims match the implemented automatic event types
- final recheck:
- reviewer:
Kierkegaard(019d2516-65dc-7820-8201-8123b496dcc9) - result: no material findings after the fail-open fix, the deletion fix, and the narrowed scope wording
- reviewer:
- deferred to later Batch B slices:
- automatic observer-transition writes still belong to
observer-episodic-fusion-v1 - decision-oriented episodic writes still need dedicated runtime hooks rather than only schema or repository support
- episodic retrieval and ranking still belong to the later FTS, hybrid-retrieval, and retrieval-planner slices
- automatic observer-transition writes still belong to
- reviewer:
observer-episodic-fusion-v1
- scope:
- extended
CurrentContextwithactive_projectso observer state carries explicit project focus instead of only goals plus window text - added
backend/src/memory/observer_episodes.pyto derive conservativeobserverepisodes for project, focus, and activity transitions with explicit provenance metadata - taught
ContextManager.refresh()to load the most recent screen-derived project, persist observer transitions into episodic memory, and fail open if that write path breaks - added runtime-audit details for
active_projectandobserver_transition_countso the observer refresh log shows what transition memory was produced - added regression coverage for duplicate-suppression, write-failure survival, project-clear transitions, and audit details
- extended
- validation:
backend/.venv/bin/python -m py_compile backend/src/observer/context.py backend/src/memory/observer_episodes.py backend/src/observer/manager.py backend/tests/test_observer_manager.pybackend/.venv/bin/python -m pytest backend/tests/test_observer_manager.py backend/tests/test_memory_episodes.py -q
- subagent review:
- reviewer:
Volta(019d24d3-df48-7700-9943-e69cbfaf93aa) - initial findings:
- observer transition writes were not atomic, so a refresh could partially commit episodes while still reporting
observer_transition_count == 0after an exception path - project exit transitions were skipped because the first cut only wrote project episodes when the new project name was non-empty
- observer transition writes were not atomic, so a refresh could partially commit episodes while still reporting
- fixed before the slice stayed marked complete:
- changed observer transition persistence to build one payload list and write it through
memory_repository.create_episode_batch(...)so each refresh commits observer episodes atomically - added project-clear transition support, including reuse of the prior project entity when focus drops from a project to none
- added regression tests for observer write failure, project-clear transitions, and runtime-audit transition details
- changed observer transition persistence to build one payload list and write it through
- recheck attempts:
- follow-up reviewer threads were started with
Arendt,Fermat, andKierkegaard, but those tool runs stalled before returning findings, so the completion record relies on the fixedVoltafindings plus targeted regression validation instead of claiming an unreturned clean review
- follow-up reviewer threads were started with
- deferred to later Batch B slices:
- observer episodes are still lexical/temporal substrate only; retrieval and ranking belong to the later FTS and hybrid retrieval slices
- richer observer-event semantics beyond project, focus, and blocked-state activity transitions still belong to later observer-memory refinement work
- aggregate PR follow-up:
- external PR review caught that observer transition writes could create a new project entity from a shortened observer label like
Atlas, which would fragment recall away from existing memories linked toAtlas launch - fixed on the branch by resolving observer project labels through existing project-entity lookup first, including the unique-token project fallback, before creating a new entity only when nothing resolves
- added a regression test that proves observer episodes reuse the existing
Atlas launchproject entity when the observer reportsAtlas
- external PR review caught that observer transition writes could create a new project entity from a shortened observer label like
- reviewer:
session-search-fts-and-event-index-v1
- scope:
- added a SQLite
session_recall_ftsindex that backfills existing rows and stays updated through triggers on sessions, user or assistant messages, and non-conversation episodic events - upgraded
SessionManager.search_sessions(...)to use the FTS index for normal text queries while keeping a bounded LIKE fallback for punctuation-heavy queries such as%and_ - expanded session recall so workflow and tool episodes can appear as
eventmatches instead of limiting recall to titles plus user-facing chat messages - kept session result ordering stable by ranking candidate hits with FTS first and then ordering sessions by conversation recency rather than todo churn or title-update timestamps
- added regression coverage for FTS backfill, episodic event hits, and title-update refresh behavior
- added a SQLite
- validation:
backend/.venv/bin/python -m py_compile backend/src/db/engine.py backend/src/agent/session.py backend/tests/conftest.py backend/tests/test_db_engine.py backend/tests/test_session.pybackend/.venv/bin/python -m pytest backend/tests/test_db_engine.py backend/tests/test_session.py backend/tests/test_session_search_tool.py backend/tests/test_memory_episodes.py -q
- review notes:
- local regression caught and fixed before the slice stayed complete:
- the first FTS cut indexed conversation episodes alongside the original chat messages, which let ordinary message searches surface
eventhits for the same text - fixed by keeping the FTS event lane limited to non-conversation episodic rows, so chat recall stays on the message lane while workflow or tool recall still uses the event lane
- the first FTS cut indexed conversation episodes alongside the original chat messages, which let ordinary message searches surface
- subagent review:
- reviewer thread:
Dalton(019d24c9-0610-7102-a84f-a58874fb38f9) - result: the review thread stalled before returning findings, so the completion record relies on targeted regression validation plus the locally caught conversation-versus-event indexing regression above
- reviewer thread:
- deferred to later Batch B slices:
- session recall is now lexical plus episodic, but cross-store hybrid ranking with semantic memory still belongs to
hybrid-memory-retrieval-v1 - query-type routing between session recall and guardian-state memory assembly still belongs to
guardian-state-retrieval-planner-v1
- session recall is now lexical plus episodic, but cross-store hybrid ranking with semantic memory still belongs to
- aggregate PR follow-up:
- external PR review caught that the LIKE fallback path only searched user and assistant messages, so episodic event recall disappeared for punctuation-heavy or FTS-ineligible queries
- fixed on the branch by teaching the fallback path to search non-conversation episodic events as well, mirroring the event coverage of the FTS path
- added a regression test that proves
upload?still returns the workflow event match through the fallback lane
- local regression caught and fixed before the slice stayed complete:
hybrid-memory-retrieval-v1
- scope:
- added
backend/src/memory/hybrid_retrieval.pyas a reusable retriever that combines lexical structured-memory hits, project-linked boosts, episodic hits, vector-store hits, dedupe, and reranking into one bounded memory bundle - kept the retriever independent from guardian-state wiring so the later planner slice can consume one tested retrieval backbone instead of reimplementing ranking logic in multiple places
- added regression coverage for mixed semantic plus episodic plus vector recall, project-linked surfacing without lexical query overlap, and degraded-vector fallback behavior
- added
- validation:
backend/.venv/bin/python -m py_compile backend/src/memory/hybrid_retrieval.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/conftest.pybackend/.venv/bin/python -m pytest backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_memory_repository.py backend/tests/test_memory_episodes.py -q
- review notes:
- local regressions caught and fixed before the slice stayed complete:
- the new retriever module initially bypassed the in-memory test DB because
src.memory.hybrid_retrieval.get_sessionwas not included in the shared test patch targets - recency scoring initially mixed naive and timezone-aware datetimes, which broke ranking during test execution
- fixed by patching the new module into
backend/tests/conftest.pyand normalizing datetimes inside the hybrid recency scorer before ranking
- the new retriever module initially bypassed the in-memory test DB because
- subagent review:
- reviewer thread:
Laplace(019d24da-bfeb-7360-8b3a-6e9bcef8fcb7) - result: the review thread stalled before returning findings, so the completion record relies on the targeted hybrid-retrieval test suite plus the local regressions above
- reviewer thread:
- deferred to later Batch B slices:
- the retriever exists, but guardian-state still uses the older memory assembly path until
guardian-state-retrieval-planner-v1lands - procedural-memory routing still belongs to Batch C because outcome-derived procedural memory is not a live retrieval lane yet
- the retriever exists, but guardian-state still uses the older memory assembly path until
- local regressions caught and fixed before the slice stayed complete:
guardian-state-retrieval-planner-v1
- scope:
- added
backend/src/memory/retrieval_planner.pyso guardian-state memory assembly now flows through one planner instead of splitting structured-memory assembly and query-time recall across separate code paths - moved structured semantic bundle assembly out of
backend/src/guardian/state.pyand into the planner, keeping project-linked semantic boosts while layering hybrid semantic plus episodic recall on top - updated guardian-state synthesis to split semantic recall into
Relevant memories:and episodic recall intoRelevant episodes:so temporal questions can surface event history without flattening everything into one generic memory block - added a temporal-query guardian-state regression test that proves Atlas project memory still lands in semantic recall while a linked workflow failure lands in episodic recall for a
yesterday-style question
- added
- validation:
backend/.venv/bin/python -m py_compile backend/src/memory/retrieval_planner.py backend/src/guardian/state.py backend/tests/test_guardian_state.py backend/tests/test_strategist_tick.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_state.py backend/tests/test_hybrid_memory_retrieval.py -q
- review notes:
- broader validation boundary observed:
backend/tests/test_strategist_tick.py::test_strategist_tick_binds_runtime_context_for_tool_auditstill fails because the expectedtool_callaudit event withsession_id == "scheduler:strategist_tick"is not emitted- that check does not exercise the new retrieval-planner path directly, so it is recorded as an unrelated runtime-audit seam rather than treated as a blocker for this Batch B slice
- subagent review:
- reviewer thread:
Kuhn(019d2539-795c-7f02-ab99-8db767c17dd2) - result: the review thread stalled before returning findings, so the completion record relies on the targeted guardian-state plus hybrid-retrieval validation above and the explicit strategist-audit boundary note instead of claiming an unreturned clean review
- reviewer thread:
- deferred to Batch C:
- procedural-memory routing still belongs to the outcome-learning batch because outcome-derived procedural memory is not a live retrieval lane yet
- planner-driven routing into policy- or delivery-specific memory lanes should wait until procedural memory exists instead of inventing a hollow extra lane now
- broader validation boundary observed:
Batch B Aggregate Validation
- targeted validation:
backend/.venv/bin/python -m py_compile backend/src/db/engine.py backend/src/agent/session.py backend/src/observer/context.py backend/src/memory/observer_episodes.py backend/src/observer/manager.py backend/src/memory/hybrid_retrieval.py backend/src/memory/retrieval_planner.py backend/src/guardian/state.py backend/tests/test_db_engine.py backend/tests/test_session.py backend/tests/test_session_search_tool.py backend/tests/test_memory_episodes.py backend/tests/test_observer_manager.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_db_engine.py backend/tests/test_session.py backend/tests/test_session_search_tool.py backend/tests/test_memory_episodes.py backend/tests/test_observer_manager.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_guardian_state.py backend/tests/test_memory_repository.py -qcd docs && npm run build
- broader validation boundary:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test UV_CACHE_DIR=/tmp/uv-cache uv run pytest -v- result:
1209 passed, 35 failed - the failures cluster in approval wrapping, runtime audit or session-context propagation, eval harness, goal-tree orphan handling, scheduled-job runtime context, and tool-audit paths; the Batch B memory suite above remained green
- the already observed strategist audit-context failure remains part of that broader unrelated failure set rather than a new retrieval-planner regression
- PR review follow-up:
- a follow-up subagent review was started with
Einstein(019d2695-7bf7-7b10-97dd-6145a50ea090) for the observer-entity reuse and fallback-event-search fixes - that review thread stalled before returning findings, so the review record relies on the targeted regression tests for
backend/tests/test_observer_manager.pyandbackend/tests/test_session.pyinstead of claiming an unreturned clean review
- a follow-up subagent review was started with
Batch C Review Log
This section records the landed Batch C slices and their validation and review receipts.
memory-flush-lifecycle-hooks-v1
- scope:
- added
backend/src/memory/flush.pyas the centralized lifecycle flush entrypoint with session-fingerprint dedupe plus in-flight overlap protection so repeated triggers do not double-write the same unchanged session - routed chat-response flushes, websocket-response flushes, and scheduler catch-up flushes through that helper instead of scheduling raw consolidation calls directly
- added pre-compaction flushing from the session history path when the context window is about to require a middle summary, while explicitly preventing recursive self-triggering from the consolidator history read
- added session-end flushing before session teardown and workflow-completion flushing on successful workflow completion, and carried trigger plus workflow metadata into consolidation audit records
- tightened scheduler telemetry so it records visited sessions separately from sessions that actually flushed instead of overstating consolidation count
- follow-up hardening on the same branch now caches flush fingerprints only after clean or skipped consolidation outcomes, so non-primary lifecycle hooks can perform a real first flush or retry after a failed or partial consolidation instead of silently treating the session state as already flushed
- added
- validation:
backend/.venv/bin/python -m py_compile backend/src/memory/flush.py backend/src/memory/consolidator.py backend/src/agent/context_window.py backend/src/agent/session.py backend/src/api/chat.py backend/src/api/ws.py backend/src/scheduler/jobs/memory_consolidation.py backend/src/workflows/manager.py backend/tests/conftest.py backend/tests/test_memory_flush.py backend/tests/test_context_window.py backend/tests/test_workflows.pybackend/.venv/bin/python -m pytest backend/tests/test_memory_flush.py backend/tests/test_context_window.py backend/tests/test_workflows.py backend/tests/test_consolidator.py backend/tests/test_consolidation_reliability.py -q
- subagent review:
- reviewer:
Hooke(019d26ad-c3f0-7b50-9557-62a094b50b6c) - review target: bug risk, regression risk, concurrency issues, and misleading implementation claims around the new lifecycle flush path
- initial findings:
- non-primary lifecycle hooks could not perform the first flush for a session because the helper currently arms
session_end,pre_compaction, andworkflow_completedonly after a primarypost_responseorscheduled_catchupflush has happened - overlapping flushes could race and double-write the same session state
- wording needed to stay explicit that workflow completion currently performs a synchronous full consolidation call, not a lightweight background flush
- scheduler telemetry originally counted visited sessions as consolidated sessions even when no flush happened
- non-primary lifecycle hooks could not perform the first flush for a session because the helper currently arms
- fixed before the slice stayed marked complete:
- added in-flight fingerprint dedupe in
backend/src/memory/flush.pyso overlapping flush requests for the same session state collapse to one consolidation run - changed scheduler telemetry to distinguish visited-session count from actual flush count
- narrowed the implementation wording in docs and review notes so the workflow hook is described as the synchronous consolidation path it actually is
- added in-flight fingerprint dedupe in
- follow-up review fixes after the next slice landed:
Pasteur(019d26bf-0675-7a91-bc77-de2ba2ef7e00) returned concrete findings showing that failed or partial consolidations were still poisoning the flush fingerprint cache and that the helper still blocked non-primary hooks when no earlier flush had armed the session- the branch now propagates consolidation outcome back into
backend/src/memory/flush.py, caches fingerprints only forsucceededorskippedruns, and allowssession_end,pre_compaction, andworkflow_completedto act as genuine first-flush or retry hooks for unchanged session state - the same follow-up exposed a delete-path regression once
session_endflushes became real, sobackend/src/agent/session.pynow deletes session-scoped audit events, approval requests, queued insights, and guardian interventions before removing the session row
- reviewer:
- deferred to later Batch C slices:
- this slice still runs the existing full consolidation path rather than a lighter staged capture or merge pipeline
- higher-salience capture, staged extraction, and merge-strengthen logic belong in
multi-stage-memory-consolidation-v1
multi-stage-memory-consolidation-v1
- scope:
- refactored
backend/src/memory/consolidator.pyinto an explicit pipeline withcapture,extract,merge, andstrengthenstages underbackend/src/memory/pipeline/instead of keeping all extraction and write behavior in one monolithic function - added capture-stage source-message collection so structured memories can keep concrete message provenance instead of only a coarse session id
- added duplicate-aware structured memory merging in
backend/src/memory/repository.pyandbackend/src/memory/pipeline/merge.pyso exact repeated memories update confidence, importance, reinforcement, and source links instead of creating a new structured row and a new vector row every time - kept the vector backend active for newly created semantic memories while intentionally skipping redundant vector writes when the pipeline merges into an existing durable memory row
- extended consolidation audit details with captured-source count plus created, merged, and source-link counts so the runtime can distinguish append-heavy runs from update-heavy runs
- refactored
- validation:
backend/.venv/bin/python -m py_compile backend/src/memory/repository.py backend/src/memory/consolidator.py backend/src/memory/pipeline/__init__.py backend/src/memory/pipeline/capture.py backend/src/memory/pipeline/extract.py backend/src/memory/pipeline/merge.py backend/src/memory/pipeline/strengthen.py backend/src/agent/session.py backend/tests/test_memory_repository.py backend/tests/test_consolidator.py backend/tests/test_consolidation_reliability.py backend/tests/test_memory_flush.py backend/tests/test_session.pybackend/.venv/bin/python -m pytest backend/tests/test_memory_flush.py backend/tests/test_consolidator.py backend/tests/test_memory_repository.py backend/tests/test_consolidation_reliability.py backend/tests/test_memory_snapshots.py backend/tests/test_session.py -q
- review notes:
- local regressions caught and fixed before the slice stayed complete:
- exact-duplicate merge initially failed on offset-naive vs offset-aware
last_confirmed_atcomparisons, which left reinforcement unchanged and prevented merge-path audit counts from moving - moving link resolution into the pipeline briefly broke the existing
src.memory.consolidator.resolve_memory_linkspatch seam used by tests, so the consolidator now passes the resolver into the pipeline explicitly instead of silently changing that surface - the entity-link-failure path originally kept the old test expectation of a dangling vector write, but the staged pipeline now resolves links before vector writes on new memories; the final tests document that narrower and safer behavior honestly instead of claiming the old partial-write shape is still live
- exact-duplicate merge initially failed on offset-naive vs offset-aware
- subagent review:
- returned findings from
Nash(019d26bd-dbac-7c50-a42e-166ed2a9760c) andPasteur(019d26bf-0675-7a91-bc77-de2ba2ef7e00) showed that the first pipeline cut still had real correctness gaps:- snapshot refresh failures crashed into the hard-failure path instead of staying partial because the old
partial_write_countlocal was gone and the aggregate audit outcome ignored snapshot-only failures - source capture pulled the oldest session window rather than the newest one, so long sessions could attribute new memories to stale early messages
- duplicate merge matching was entity-asymmetric, which blocked later backfill of missing links and also let an unlinked repeat merge into a linked row
- the primary source row for newly created memories used synthesized summary text under
source_type="session", which overstated true message provenance and suppressed later real message-source rows for the samesource_message_id - merge-path confirmations could not repair a previously missing
embedding_id, so exact repeats would strengthen the structured row forever without restoring vector coverage
- snapshot refresh failures crashed into the hard-failure path instead of staying partial because the old
- fixed before this slice stayed marked complete on the branch:
backend/src/memory/consolidator.pynow returns explicit outcome metadata to the flush layer, counts snapshot refresh failure as a partial write, and keeps snapshot-only failure runs inpartially_succeededinstead of crashing intofailedbackend/src/memory/pipeline/capture.pynow reads the newest message window, not the oldest ascending pagebackend/src/memory/repository.pynow matches linked extractions against same-id or unlinked rows while refusing to merge a later unlinked extraction into an already linked row, and it accepts explicit message-backed source snippets for primary provenance rowsbackend/src/memory/pipeline/merge.pynow drops the zero-overlap fallback, counts source links only when a real message match exists, stores the primary source as an actualmessagesource row with the message snippet, repairs missing embeddings on the merge path when a repeated confirmation can restore vector coverage, falls back to session-level provenance plus zero reinforcement delta when a same-session retry has no newly recorded evidence, and applies merge-strengthening plus merge-path provenance in one repository transaction so a failed provenance write cannot leave behind a stronger row that will strengthen again on retrybackend/src/agent/session.pynow deletes the extra session-scoped rows that became reachable oncesession_endflushes were allowed to run for real before session teardown
- review status still open:
- the follow-up request to
Hooke(019d26ad-c3f0-7b50-9557-62a094b50b6c) did not return before this log update, so the recorded review evidence is the two returned subagent findings plus the targeted regressions above rather than an invented clean review
- the follow-up request to
- final recheck:
Aquinas(019d26ce-8df7-73e2-9586-b3a068c6883c) reviewed the finished follow-up changes after the retry-idempotence, session-provenance fallback, and atomic merge-provenance fixes landed and reported no material findings
- returned findings from
- remaining limitation carried forward intentionally:
- this slice merges exact duplicate memories by kind plus normalized summary/content and strengthens them with added provenance, but it does not yet perform contradiction detection, semantic dedupe, or supersession across near-duplicate statements
- local regressions caught and fixed before the slice stayed complete:
- deferred to later Batch C slices:
- contradiction cleanup and supersession-aware archival still belong to
memory-decay-contradiction-and-archive-v1 - the soul surface is still file-backed rather than a projection from structured identity state
- outcome-derived procedural memory is still separate from this consolidation pipeline until the later procedural-memory slice lands
- contradiction cleanup and supersession-aware archival still belong to
soul-projection-and-structured-profile-v1
- scope:
- added
backend/src/profile/service.pyas the structured profile service for the singleton user record instead of leaving profile creation, onboarding state, and soul access split across API handlers and file utilities - moved
soul.mdrendering and parsing intobackend/src/memory/soul.pyso the file becomes a readable projection surface whileuser_profiles.preferences_jsonplususer_profiles.soul_texthold the structured durable state underneath it - updated
/api/user/profileto returnsoul_sectionsandsoul_textfrom the structured profile snapshot instead of exposing only a thin onboarding payload - routed guardian-state synthesis and session consolidation through
sync_soul_file_to_profile()before they read soul context, and routed consolidation soul writes throughupdate_profile_soul_section()so durable soul updates land in the structured profile before they are projected back out to disk - hardened projection sync so a missing
soul.mdis recreated from the stored profile state, and the stale-file guard now uses a soul-specific timestamp stored inside the structured payload instead of the generic profileupdated_at - changed section updates to optimistic compare-and-swap writes on
preferences_json, so concurrent soul updates retry against the latest structured state instead of silently dropping one section change
- added
- validation:
backend/.venv/bin/python -m py_compile backend/src/memory/soul.py backend/src/profile/service.py backend/src/api/profile.py backend/src/memory/consolidator.py backend/src/guardian/state.py backend/tests/test_soul.py backend/tests/test_profile.py backend/tests/test_consolidator.pybackend/.venv/bin/python -m pytest backend/tests/test_soul.py backend/tests/test_profile.py backend/tests/test_consolidator.py backend/tests/test_guardian_state.py backend/tests/test_memory_snapshots.py -q
- local regressions fixed before subagent review:
- the first structured-profile cut left
read_soul()falling back to defaults whensoul.mdwas missing even if the profile already had stored identity state, so sync now re-projects the file from the structured profile before guardian-state or consolidation reads it again - the first sync cut could also let an older projection file overwrite newer structured state, so the profile now tracks the last projected hash plus a soul-specific update timestamp and restores structured state only when the on-disk file is older than that soul-specific marker
- the first structured-profile cut left
- subagent review:
Chandrasekhar(019d26f3-954e-7db0-b9a8-9bae53a629e9) returned two concrete findings after the initial slice commit:- the stale-file guard was incorrectly using
profile.updated_at, so unrelated onboarding writes could mark a newer manualsoul.mdedit as stale and overwrite it from older structured state - concurrent
update_profile_soul_section()calls could lose updates because each writer rewrote the full structured blob from one stale base state
- the stale-file guard was incorrectly using
- fixed before the slice stayed marked complete on the branch:
- the structured soul payload now stores its own soul-specific update timestamp, and sync compares file age against that marker instead of the generic profile row timestamp
backend/src/profile/service.pynow updates soul sections through an optimistic compare-and-swap loop keyed on the currentpreferences_json, so conflicting writers retry from the latest structured state instead of erasing each other
- deferred to later Batch C slices:
- outcome-derived procedural memory still needs its own explicit durable representation and retrieval lane instead of living only inside guardian feedback heuristics
- contradiction cleanup and archival still belong to
memory-decay-contradiction-and-archive-v1
procedural-memory-from-outcomes-v1
- scope:
- added
MemoryKind.proceduralinbackend/src/db/models.pyand mapped it throughbackend/src/memory/types.pyso outcome-derived lessons become first-class durable memory instead of piggybacking on generic preference text - added
backend/src/memory/procedural.pyto materializeGuardianLearningSignalinto scoped procedural memories for delivery, phrasing, cadence, channel, escalation, timing, blocked-state, suppression, and thread lessons - added
memory_repository.sync_scoped_memory()inbackend/src/memory/repository.pyso one lesson scope updates in place across retries, reactivates archived rows when the lesson returns, archives the row when the signal goes neutral, and now persists a deterministicscope_keywith a unique SQLite index plus retry path so same-scope procedural writes stay deduplicated across workers instead of only inside one event loop - refreshed procedural memories from guardian feedback writes in
backend/src/guardian/feedback.py, so explicit user feedback and failed outcomes update the durable lesson lane instead of leaving that learning only in ephemeral scoring logic; the follow-up fix now also recomputes lessons when an intervention moves fromfailedback to a non-failed outcome - surfaced procedural memories through the retrieval planner, hybrid retrieval, bounded snapshot, and guardian world model in
backend/src/memory/retrieval_planner.py,backend/src/memory/hybrid_retrieval.py,backend/src/memory/snapshots.py, andbackend/src/guardian/world_model.py, with procedural guidance now rendering the actual rule text instead of opaque lesson labels and allowing more than two active lessons to surface at once - feedback-driven procedural refresh now invalidates the per-session bounded snapshot cache and rebuilds the shared bounded snapshot so the same session does not keep stale delivery guidance after new feedback lands
- added
- validation:
- initial slice boundary:
backend/.venv/bin/python -m py_compile backend/src/db/models.py backend/src/memory/types.py backend/src/memory/repository.py backend/src/memory/procedural.py backend/src/guardian/feedback.py backend/src/memory/retrieval_planner.py backend/src/memory/snapshots.py backend/src/guardian/world_model.py backend/tests/test_guardian_feedback.py backend/tests/test_memory_snapshots.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_feedback.py backend/tests/test_delivery.py backend/tests/test_intervention_policy.py backend/tests/test_memory_snapshots.py backend/tests/test_guardian_state.py -q
- review-fix boundary:
backend/.venv/bin/python -m py_compile backend/src/memory/repository.py backend/src/memory/procedural.py backend/src/memory/hybrid_retrieval.py backend/src/memory/retrieval_planner.py backend/src/memory/snapshots.py backend/src/guardian/feedback.py backend/src/guardian/world_model.py backend/tests/test_guardian_feedback.py backend/tests/test_guardian_state.py backend/tests/test_memory_snapshots.py backend/tests/test_hybrid_memory_retrieval.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_feedback.py backend/tests/test_guardian_state.py backend/tests/test_memory_snapshots.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_delivery.py backend/tests/test_intervention_policy.py -q
- initial slice boundary:
- local regressions fixed before the slice stayed complete:
- the new guardian-feedback tests initially created interventions against missing sessions, which violated the
guardian_interventions.session_idforeign key and masked the actual procedural-memory assertions until the fixture setup was corrected
- the new guardian-feedback tests initially created interventions against missing sessions, which violated the
- subagent review:
Galileo(019d2703-6e72-7b83-9d77-11c2bbc72165) andRawls(019d2704-d877-7511-a4d5-ddcc014569d1) returned concrete findings after the first slice commit:- procedural memories were surfaced through summary-first rendering, so prompts saw labels like
Advisory timing lessoninstead of the actual learned rule text - procedural refresh wrote the durable row but did not invalidate or rebuild the session-bounded snapshot cache, so an ongoing session could keep stale delivery guidance after feedback landed
- lesson text in
backend/src/memory/procedural.pywas hardcoded to advisory wording even when the intervention type was something else sync_scoped_memory()could create duplicate same-scope procedural rows under concurrent refreshes because it did not serialize the read-then-insert path- outcome corrections from
failedback to a non-failed state could leave stale failure-driven lessons behind because only newfailedoutcomes triggered recomputation - procedural surfacing was capped too tightly, so only two lessons could reach retrieval and world-model context even when more were active
- procedural memories were surfaced through summary-first rendering, so prompts saw labels like
- fixed before the slice stayed marked complete on the branch:
- procedural memories now store and surface actionable rule text, and procedural rendering paths prefer the rule content over opaque labels
- guardian feedback refresh now invalidates the bounded snapshot cache and refreshes the shared bounded snapshot after procedural-memory updates
- lesson builders now render intervention-type-specific wording instead of hardcoding advisory-only language
backend/src/memory/repository.pynow serializes same-scope procedural writes with a per-scope async lock to keep concurrent refreshes from duplicating rowsupdate_outcome()now recomputes procedural lessons when an intervention enters or exits thefailedstate- structured memory and bounded snapshot surfacing now allow more than two procedural lessons without crowding out the whole bundle
- recheck status:
Kepler(019d2710-ba96-73b1-948f-1df447c3e1e5) returned one remaining medium finding after the first repair pass: the same-scope dedupe was still only process-local because it relied on an in-memory async lock- the branch now adds a durable
scope_keycolumn plus a unique partial index on(kind, scope_key)inbackend/src/db/models.pyandbackend/src/db/engine.py, andbackend/src/memory/repository.pynow retries scoped inserts afterIntegrityErrorby loading the winning row and updating it in place - a final recheck request was sent back to
Keplerafter the DB-backed dedupe path landed; if that response does not return before the aggregate Batch C PR is opened, the PR notes should say exactly that instead of implying a clean reply that never arrived
- deferred to later Batch C slices:
- delivery planning still reads the live
GuardianLearningSignalheuristics directly; this slice makes those lessons durable and prompt-visible, but direct policy-time retrieval from procedural memory should land with the later learning-quality follow-through - contradiction cleanup and supersession-aware archival still belong to
memory-decay-contradiction-and-archive-v1
- delivery planning still reads the live
memory-decay-contradiction-and-archive-v1
- scope:
- added
backend/src/memory/decay.pyso Batch C now has an explicit decay-maintenance pass that detects conservative contradiction pairs for comparable active memories, marks losing rowssuperseded, writes contradiction plus supersession metadata, and materializescontradictsandsupersedesedges instead of leaving stale memories active forever - added staleness decay windows by memory kind, with confidence and reinforcement step-downs plus archival metadata once a memory becomes both old enough and weak enough to stop competing with fresher context
- wired decay maintenance into
backend/src/memory/consolidator.pyafter memory persistence and soul updates, so each consolidation pass now refreshes long-term memory health before bounded snapshots are rebuilt; the consolidation audit payload now records contradiction, superseded, decayed, and archived counts plus whether maintenance itself failed - extended
backend/src/memory/repository.pywith edge dedupe andlist_edges(), so repeated maintenance runs do not fan out duplicate relationship rows when the same contradiction or supersession is seen again - tightened
backend/src/memory/hybrid_retrieval.pyso vector hits are filtered back through active structured-memory status before they can reach guardian context, which prevents archived or superseded embeddings from reappearing after decay has already invalidated the source row
- added
- validation:
- initial slice boundary:
backend/.venv/bin/python -m py_compile backend/src/memory/decay.py backend/src/memory/hybrid_retrieval.py backend/src/memory/consolidator.py backend/src/memory/repository.py backend/tests/test_memory_decay.py backend/tests/test_memory_repository.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_consolidator.py backend/tests/conftest.pybackend/.venv/bin/python -m pytest backend/tests/test_memory_decay.py backend/tests/test_memory_repository.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_consolidator.py -q
- broader adjacent-memory boundary:
backend/.venv/bin/python -m pytest backend/tests/test_memory_decay.py backend/tests/test_memory_repository.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_consolidator.py backend/tests/test_consolidation_reliability.py backend/tests/test_memory_snapshots.py backend/tests/test_guardian_state.py backend/tests/test_delivery.py backend/tests/test_intervention_policy.py -q
- initial slice boundary:
- local regressions fixed before the slice stayed complete:
- the first decay test run was accidentally using the real SQLite file instead of the in-memory test database because
src.memory.decay.get_sessionwas missing from the shared patch target list inbackend/tests/conftest.py - the first vector-status filter only dropped stale vector hits when at least one active structured-memory ID was still present, so a vector-only result set made entirely of archived or superseded rows could still leak stale text back into hybrid retrieval until the filter was tightened and regression-covered
- the first contradiction-polarity pass treated
helpfulas present insidenot helpful, and it relied on summary-first text that could drop the polarity cue entirely, so contradictory communication-preference memories could survive decay until cue matching was hardened and the heuristic started reading combined summary-plus-content text - subagent review:
Bernoulli(019d29d3-98b5-7692-a69f-8bcca1c760f3) was asked to review the completed slice for bugs, regressions, and false claims, but that review thread stalled before returning findingsAmpere(019d29d6-6450-7203-ac60-87ba7e9bfd83) returned two concrete findings after the initial slice commit:- terminal stale memories could get stuck active forever because
decay_step = 4saturated the step function while confidence and reinforcement only decayed when the step number increased - same-entity contradictions with short wording, like
Prefers SlackversusAvoid Slack, could stay active together because contradiction detection still required two overlapping anchor tokens even when the memories already shared a linked entity
- terminal stale memories could get stuck active forever because
- fixed before the slice stayed marked complete on the branch:
- terminal stale rows now continue decaying one pass at a time after step 4 until archival thresholds are reached, and
backend/tests/test_memory_decay.pynow covers repeated maintenance runs so high-confidence stale rows cannot remain active indefinitely - same-entity contradictions now require only one anchor overlap instead of two, so short linked preference reversals are superseded correctly without loosening the broader non-entity contradiction check
- terminal stale rows now continue decaying one pass at a time after step 4 until archival thresholds are reached, and
- recheck status:
Ampererechecked the follow-up patch and reported no material findings after the step-4 and short-contradiction fixes landed
- late follow-up review:
Bernoullilater returned four additional findings against the already-committed slice:- the structured-status filter in
backend/src/memory/hybrid_retrieval.pywas matching vector hit IDs againstMemory.idinstead ofMemory.embedding_id, which meant valid active vector hits could be dropped while the tests were still faking vector IDs with structured memory IDs - even after the ID fix, stale vector text could still leak when an active memory and a superseded memory shared the same deduplicated
embedding_id, because the vector filter was not checking whether the returned text still matched any active structured row for that embedding - the contradiction heuristic in
backend/src/memory/decay.pywas too broad for same-entity project state because generic polarity cues likeactivecould make coexisting project memories look contradictory once the overlap threshold had been relaxed - the first polarity narrowing over-corrected by dropping
activeandavailableglobally, which hid concise state reversals likeAtlas service is activeversusAtlas service is paused - refreshed memories could keep stale
decay_stepmetadata aftermerge_memory(...), which let reconfirmed rows skip normal future decay stages source_link_countwas underreporting session-level provenance because the merge pipeline only counted message-linked sources, not fallback session sources
- the structured-status filter in
- fixed after the late review:
- vector-status filtering now matches active structured rows primarily through
embedding_idand keeps anidfallback only for tolerance, and it now drops hits whose returned text no longer matches any active structured row for that embedding so stale shared-embedding text cannot resurface through vector search - contradiction matching now limits one-token shared-entity reversals to
preferencepluscommunication_preference, while ambiguous cues likeactiveandavailableonly count for very short anchor sets so concise state reversals are still caught without turning broader project-state updates into false contradictions merge_memory(...)now clears stale decay metadata when a newerlast_confirmed_atreconfirms a memory, and the decay regression suite now proves that refreshed memories re-enter normal decay stages later instead of getting stuck with stale step metadata- the merge pipeline now counts both message and session provenance rows in
source_link_count, and the consolidator coverage was updated so session-only provenance no longer looks like zero linked sources
- vector-status filtering now matches active structured rows primarily through
- residual risk:
Amperealso noted that concurrent decay workers can still race on direct edge creation insidebackend/src/memory/decay.py; that remains recorded as residual risk for this slice rather than a blocker because the shipped contract here is stale-memory suppression and contradiction cleanup, not cross-worker edge-uniqueness hardening
- the first decay test run was accidentally using the real SQLite file instead of the in-memory test database because
- deferred to later Batch C slices:
- behavioral proof that the new decay and adaptation rules change end-to-end guardian behavior still belongs to
guardian-memory-behavioral-evals-v1
- behavioral proof that the new decay and adaptation rules change end-to-end guardian behavior still belongs to
guardian-memory-behavioral-evals-v1
- scope:
- expanded
backend/src/evals/harness.pywithmemory_decay_contradiction_cleanup_behavior, which proves the decay-maintenance path supersedes contradictory project memory, keeps superseded embeddings out of hybrid retrieval, and keeps guardian-state memory context focused on the winning project state - added
procedural_memory_adaptation_behaviorso the eval harness now proves feedback-derived procedural memory can refresh same-session bounded context and surface the learned rule text back into guardian memory context without waiting for a new session - registered both scenarios in the runtime-eval catalog so they show up in
--listoutput and can be run independently from the rest of the harness instead of being trapped inside one monolithic all-scenarios contract - extended
backend/tests/test_eval_harness.pywith scenario-list assertions and a focusedtest_memory_runtime_eval_scenarios_expose_expected_details()boundary that validates just the two new memory scenarios and their returned details
- expanded
- validation:
backend/.venv/bin/python -m py_compile backend/src/evals/harness.py backend/tests/test_eval_harness.pybackend/.venv/bin/python -m pytest backend/tests/test_eval_harness.py::test_main_lists_available_scenarios backend/tests/test_eval_harness.py::test_memory_runtime_eval_scenarios_expose_expected_details -qcd backend && PYTHONPATH=. .venv/bin/python -m src.evals.harness --scenario memory_decay_contradiction_cleanup_behavior --scenario procedural_memory_adaptation_behavior --indent 2
- follow-up validation after late review findings:
backend/.venv/bin/python -m py_compile backend/src/memory/hybrid_retrieval.py backend/src/memory/decay.py backend/src/memory/repository.py backend/src/memory/pipeline/merge.py backend/src/evals/harness.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_memory_decay.py backend/tests/test_consolidator.py backend/tests/test_eval_harness.pybackend/.venv/bin/python -m pytest backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_memory_decay.py backend/tests/test_consolidator.py backend/tests/test_eval_harness.py::test_main_lists_available_scenarios backend/tests/test_eval_harness.py::test_memory_runtime_eval_scenarios_expose_expected_details -qbackend/.venv/bin/python -m pytest backend/tests/test_memory_flush.py backend/tests/test_consolidator.py backend/tests/test_consolidation_reliability.py backend/tests/test_memory_repository.py backend/tests/test_memory_decay.py backend/tests/test_guardian_feedback.py backend/tests/test_guardian_state.py backend/tests/test_memory_snapshots.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_soul.py backend/tests/test_profile.py backend/tests/test_eval_harness.py::test_main_lists_available_scenarios backend/tests/test_eval_harness.py::test_memory_runtime_eval_scenarios_expose_expected_details backend/tests/test_delivery.py backend/tests/test_intervention_policy.py -q
- local validation notes:
- the broader
backend/tests/test_eval_harness.py::test_run_runtime_evals_passes_all_scenarioscontract is green again after patching the remaining DB seam imports used by flush helpers - the broader
backend/tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_detailscontract now also passes after aligning the legacy guardian-state and world-model expectations with the current harness output, where theguardian_state_synthesisscenario runs with degraded memory confidence and zero memory signals while the world-model scenario now reflects the dominant continuity thread rather than a seeded memory-signal list
- the broader
- subagent review:
Archimedes(019d29e0-d2a1-7a61-ab08-37edf377f6bf) andGodel(019d29e2-fc4a-7510-9acb-e24b85f5d8fe) both later returned concrete findings against the initial slice:- the contradiction-cleanup eval could still pass if stale vector text leaked into guardian memory context because the first negative assertion only checked the shortened summary text rather than the rendered retrieval line
- the scenarios were still patching
src.memory.soul.read_soul, even thoughbuild_guardian_state()now usessrc.profile.service.sync_soul_file_to_profile(), so the first CLI run was not actually isolated from live profile projection - the procedural-memory scenario was using
active_procedural_memory_count >= 5, which tolerated duplicate procedural writes instead of guarding against them
- fixed after review:
- the contradiction-cleanup scenario now rejects both the summary form and the rendered vector-text form of the stale Atlas memory from guardian context, and its vector-hit fixture now uses embedding IDs that match the real vector-store contract
- both scenarios now patch
sync_soul_file_to_profile()at the real profile seam instead of the obsolete soul reader seam, and the procedural-memory assertion now requires the exact expected active procedural count (== 5) instead of a permissive lower bound - the older aggregate eval-harness boundaries were then rerun against the repaired seams, and the stale guardian-state plus world-model expectations were updated to match the current memory-degraded and continuity-thread-driven harness output instead of preserving older assumptions that no longer held
- final follow-up recheck:
- a fresh post-fix review request was sent to
Bernoulli(019d29d3-98b5-7692-a69f-8bcca1c760f3) andGodel(019d29e2-fc4a-7510-9acb-e24b85f5d8fe) against the current uncommitted Batch C follow-up diff; neither response returned before the aggregate Batch C PR preparation, so the branch records the attempt and validation results rather than claiming a final clean reply that never arrived
- a fresh post-fix review request was sent to
- residual eval noise:
- the filtered CLI harness run no longer hits real soul projection, but this environment still emits unrelated
tiktokenfallback tracebacks before the final JSON summary; that warning is recorded as residual harness noise rather than a blocker because the focused pytest boundary stays clean and the CLI JSON result still reports both scenarios passing
- the filtered CLI harness run no longer hits real soul projection, but this environment still emits unrelated
Batch C Aggregate Validation
- targeted Batch C backend boundary:
- result:
178 passed - command:
backend/.venv/bin/python -m pytest backend/tests/test_memory_flush.py backend/tests/test_consolidator.py backend/tests/test_consolidation_reliability.py backend/tests/test_memory_repository.py backend/tests/test_memory_decay.py backend/tests/test_guardian_feedback.py backend/tests/test_guardian_state.py backend/tests/test_memory_snapshots.py backend/tests/test_hybrid_memory_retrieval.py backend/tests/test_soul.py backend/tests/test_profile.py backend/tests/test_eval_harness.py::test_main_lists_available_scenarios backend/tests/test_eval_harness.py::test_memory_runtime_eval_scenarios_expose_expected_details backend/tests/test_eval_harness.py::test_run_runtime_evals_passes_all_scenarios backend/tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_details backend/tests/test_delivery.py backend/tests/test_intervention_policy.py -q
- result:
- docs build:
- result: clean
- command:
cd docs && npm run build
- fresh full backend sweep:
- result:
1244 passed, 50 failed - command:
cd backend && .venv/bin/python -m pytest -q
- failure clusters outside the Batch C memory path:
- approval request foreign-key setup and approval-wrapped tool flows
- audit and runtime-context propagation through chat, tool audit, and process-tool surfaces
- goal-tree orphan and cascade deletion integrity
- scheduled-job execution, consolidation job wiring, and scheduler sync behavior
- websocket contract regressions
- onboarding continuity edge cases
- result:
- aggregate PR follow-up after review and full-suite root-cause sweep:
- external PR review findings fixed on the branch:
backend/src/memory/flush.pynow fingerprints flush state from message-level state only, so title-only session edits do not retrigger consolidation or decay workbackend/src/memory/pipeline/extract.pynow strips and drops blanksoul_updateskeys before consolidation, so malformed LLM payloads cannot fail the whole run on empty section names
- full-suite root causes fixed before the branch was ready again:
- added
backend/src/db/session_refs.pyand wired session-ref backfill through approval, audit, guardian-feedback, todo, and scheduled-job write paths, then extended that fix to scheduled-job updates too - restored the
src.scheduler.jobs.memory_consolidation.consolidate_sessionseam so scheduler tests can still isolate scheduled consolidation behavior - repaired the websocket test seam after the profile split by patching
src.profile.service.get_dbin the shared sync websocket client helper, which stopped onboarding state from leaking through the real profile store across tests - fixed goal-tree deletion under enforced foreign keys by flushing descendant deletes in dependency order instead of relying on one batched self-referential delete
- kept exact caller session IDs in
ensure_sessions_exist(...)instead of trimming them, which prevents phantom placeholder sessions like"s1"from being synthesized alongside a real" s1 "session row - hardened the websocket drain-timeout path and the eval-harness timeout shim together so the runtime cleanup stays warning-free while the harness still emulates real
asyncio.wait_for(...)behavior for both coroutines and task-like awaitables
- added
- subagent review:
Zeno(019d24e8-51f8-7402-b24e-82c872dd4813) andKepler(019d2710-ba96-73b1-948f-1df447c3e1e5) both caught the remaining goal-delete dependency bug before the final full-suite rerunZenoalso flagged the exact-session-id placeholder drift risk, andKeplerflagged the missing scheduled-job update backfill; both were fixed on the branch and regression-covered- follow-up recheck:
Keplerreported no further material findings after the goal-delete, exact-session-id, and scheduled-job-update fixes landed
- external PR review findings fixed on the branch:
- final full backend sweep on the shipped implementation state:
- result:
1298 passed, 27 warnings - command:
backend/.venv/bin/python -m pytest backend/tests -q
- residual warnings outside the Batch C follow-up diff:
backend/tests/test_activity_digest.py::TestActivityDigest::test_llm_timeoutstill emits an unawaited local mock coroutine warning from the test’s timeout helperbackend/src/memory/vector_store.pystill emits existingtable_names()deprecation warnings through the eval-harness and timeout boundaries
- result:
- CI follow-up after GitHub Actions run
23605588259:- root cause:
- the remaining PR failures were not new memory-behavior regressions; they were teardown races where background
flush_session_memory(...)tasks outlived test-scoped in-memory SQLite engines and hit closed databases during the eval-harness phase
- the remaining PR failures were not new memory-behavior regressions; they were teardown races where background
- fixes:
- added
drain_tracked_tasks(...)inbackend/src/utils/background.pyso tracked background work can be awaited explicitly at shutdown - updated
backend/src/app.py,backend/tests/conftest.py, andbackend/src/evals/harness.pyso shutdown and teardown now drain tracked background work before resource disposal, but still complete cleanup even if the drain path reports a stubborn-task failure - added
backend/tests/test_background.pyto cover the normal pending-task path, cancellation of stalled work, and the explicit failure path for cancellation-resistant tasks
- added
- subagent review:
Kepler(019d2710-ba96-73b1-948f-1df447c3e1e5) caught two real issues during the first implementation attempt:- the initial bounded drain still had an unbounded post-cancel wait
- the follow-up raise path aborted teardown before cleanup finished
- both issues were fixed before the final validation pass
Zeno(019d24e8-51f8-7402-b24e-82c872dd4813) reviewed the final diff and reported no current concrete findings
- final CI-parity validation on the shipped implementation state:
- targeted subset:
- result:
11 passed, 24 warnings - command:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_background.py tests/test_eval_harness.py -q
- result:
- full backend suite:
- result:
1301 passed, 27 warnings - command:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q
- result:
- targeted subset:
- root cause:
- CI follow-up after GitHub Actions run
23608084293:- root cause:
- the remaining failure in
tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_detailscame from deterministic harness drift, not a live memory regression - the guardian-state and guardian-world-model eval scenarios had been patching
src.memory.vector_store.search_with_status, but the real retrieval path used bybuild_guardian_state(...)resolvessearch_with_statusfromsrc.memory.hybrid_retrieval - once the real retrieval seam was patched, the world-model scenario fixture was exposed as inconsistent with its assertions because it still injected semantic goal and pattern hits even though the asserted contract expected an observer, project, execution, and recent-session driven world model with no extra memory-signal section
- the remaining failure in
- fixes:
- updated
backend/src/evals/harness.pyso the guardian-state and related scenarios patchsrc.memory.hybrid_retrieval.search_with_status, which is the seam actually exercised by the retrieval planner - reset vector-store state at the scenario boundary in
backend/src/evals/harness.pyso eval runs do not inherit stale in-process vector state from earlier scenarios likevector_store_runtime_audit - aligned the
guardian_world_model_behaviorfixture with its asserted contract by returning no semantic memories for that scenario, leaving the world model grounded by observer context, recent projects, recent execution, and recent sessions instead of duplicated memory-signal lines - added a regression in
backend/tests/test_eval_harness.pythat runsvector_store_runtime_auditbeforeguardian_state_synthesisand proves the guardian-state confidence remainsoverall=partialandmemory=degraded
- updated
- subagent review:
Zeno(019d24e8-51f8-7402-b24e-82c872dd4813) reviewed the current harness and test diff and reported no material findings
- final validation on the shipped implementation state:
- targeted subset:
- result:
3 passed - command:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test UV_CACHE_DIR=/tmp/uv-cache uv run pytest tests/test_eval_harness.py::test_guardian_state_synthesis_is_stable_after_vector_store_runtime_audit tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_details tests/test_eval_harness.py::test_run_runtime_evals_passes_all_scenarios -q
- result:
- full backend suite:
- result:
1302 passed, 3 warnings - command:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test UV_CACHE_DIR=/tmp/uv-cache uv run pytest -q
- result:
- targeted subset:
- root cause:
procedural-memory-policy-routing-v1
- root cause addressed:
- Batch C made intervention lessons durable and prompt-visible, but the delivery gate and guardian-state learning guidance still read only the live
GuardianLearningSignalheuristic window - when that live window was neutral, the system could ignore durable procedural lessons that had already been written from prior outcomes and feedback
- Batch C made intervention lessons durable and prompt-visible, but the delivery gate and guardian-state learning guidance still read only the live
- scope:
- added
memory_repository.list_memories_for_scope()inbackend/src/memory/repository.pyso policy-time code can read active procedural memories by exact scope without reopening the broader retrieval planner - added
backend/src/memory/procedural_guidance.pyto turn scoped procedural memories back intoGuardianLearningSignal-compatible bias overrides for delivery, phrasing, cadence, channel, escalation, timing, blocked-state, suppression, and thread guidance, with the resolver now matching the storedwriter=guardian_feedbackscope instead of assuming the procedural-learning lane stays single-writer forever - updated
backend/src/observer/delivery.pysodecide_intervention(...)and async-native surface selection use an effective learning signal that overlays durable procedural lessons onto the live heuristic signal, while audit details now record whether the decision came from heuristics alone or heuristics plus procedural memory - updated
backend/src/guardian/state.pyso guardian-state synthesis and rendered learning guidance use the same effective guidance surface instead of dropping back to a neutral live signal when durable procedural lessons already exist - added regression coverage in
backend/tests/test_memory_repository.py,backend/tests/test_delivery.py,backend/tests/test_guardian_state.py, andbackend/tests/test_guardian_feedback.pyproving neutral live heuristics still pick up stored procedural channel, escalation, and blocked-state lessons and that foreign writers cannot contaminate the guidance resolver
- added
- validation:
python3 -m py_compile backend/src/memory/procedural_guidance.py backend/src/memory/repository.py backend/src/observer/delivery.py backend/src/guardian/state.py backend/tests/test_memory_repository.py backend/tests/test_delivery.py backend/tests/test_guardian_state.py backend/tests/test_guardian_feedback.pycd backend && uv run pytest tests/test_memory_repository.py tests/test_delivery.py tests/test_guardian_state.py -q- result:
68 passed, 2 warnings
- result:
cd backend && uv run pytest tests/test_guardian_feedback.py tests/test_intervention_policy.py -q- result:
32 passed
- result:
- hardening fix before the slice stayed complete:
- the first policy-time resolver only matched
memory_scope + intervention_type, but the stored procedural lessons are scoped bywriteras well - that assumption would let a future non-guardian procedural writer override delivery or guardian-state guidance for the same lesson lane
- the resolver now requires
writer=guardian_feedback, andtest_load_procedural_memory_guidance_ignores_other_writers()proves the guidance lane stays isolated
- the first policy-time resolver only matched
- follow-up validation after the writer-scope hardening:
python3 -m py_compile backend/src/memory/procedural_guidance.py backend/tests/test_guardian_feedback.pycd backend && uv run pytest tests/test_memory_repository.py tests/test_delivery.py tests/test_guardian_state.py tests/test_guardian_feedback.py tests/test_intervention_policy.py -q- result:
101 passed
- result:
- subagent review:
Kant(019d2bab-005c-74f3-a0fb-b68895b5f6ab) returned two concrete findings against the first complete slice:- procedural memory could flip a blocked-state or async-native advisory from
bundletoact, but the live delivery path still followed the default live-route transport order and could deliver over websocket/browser even when the learned channel bias wasprefer_native_notification - scoped procedural-memory reads and the legacy-scope fallback inside
sync_scoped_memory()both assumedmetadata_jsondecoded to a dict, so a malformed legacy payload could raiseAttributeErrorand silently disable the new guidance lane behind the existing exception swallow
- procedural memory could flip a blocked-state or async-native advisory from
- fixed after review:
backend/src/observer/delivery.pynow reorders the active transport order to putnative_notificationfirst when the effective learned channel bias isprefer_native_notification, and audit details recordtransport_order_adjustment=learned_native_channel_preferencebackend/src/memory/repository.pynow skips non-dict decoded metadata payloads in bothlist_memories_for_scope()and the scoped-memory fallback matcher used bysync_scoped_memory()- added
test_deliver_prefers_native_transport_when_procedural_memory_promotes_async_delivery()andtest_list_memories_for_scope_skips_non_dict_metadata_payloads()so both regressions stay covered
Tesla(019d2ba7-08b8-79f3-a4b2-e3e30cf2c8b4) andJames(019d2ba9-a4dc-75a3-88df-2374f6bf93f0) did not return findings before the follow-up validation finished
- follow-up validation after subagent fixes:
python3 -m py_compile backend/src/memory/procedural_guidance.py backend/src/memory/repository.py backend/src/observer/delivery.py backend/src/guardian/state.py backend/tests/test_memory_repository.py backend/tests/test_delivery.py backend/tests/test_guardian_state.py backend/tests/test_guardian_feedback.pycd backend && uv run pytest tests/test_memory_repository.py tests/test_delivery.py tests/test_guardian_state.py tests/test_guardian_feedback.py tests/test_intervention_policy.py -q- result:
103 passed
- result:
cd docs && npm run build- result: clean
- PR follow-up after review comment and CI failure:
- root cause addressed:
memory_repository.list_memories_for_scope()still filtered onlykindandstatusin SQL, then parsed and matchedmetadata_jsonin Python, so policy-time scoped reads could walk every active procedural memory before finding the requested scope- learned native-notification rerouting changed
strategist_tick_learning_continuity_behaviorfrom a websocket delivery path to a native-notification delivery path, but the delivered audit payload did not expose transport counts for that native path and the eval still used the stale websocket-orientedbroadcast_delivered_connectionsexpectation
- fixes:
backend/src/memory/repository.pynow pushes scoped procedural-memory filtering into SQLite withjson_valid(...), object-shape checks, per-keyjson_extract(...), and SQL-side limiting before the defensive Python metadata verification runsbackend/src/observer/delivery.pynow recordsattempted_connections,delivered_connections, andfailed_connectionsfor successful native-notification deliveries in both direct and queued-bundle paths so the delivery audit contract stays stable when routing moves off websocketbackend/src/evals/harness.pyandbackend/tests/test_eval_harness.pynow use the transport-agnosticdelivered_connectionsfield and expect1for the strategist continuity scenario because that learned path now delivers successfully over native notificationbackend/tests/test_memory_repository.pynow also covers invalid JSON metadata payloads, not just non-dict decoded payloads, on the scoped-read path
- validation:
python3 -m py_compile backend/src/memory/repository.py backend/src/observer/delivery.py backend/src/evals/harness.py backend/tests/test_memory_repository.py backend/tests/test_delivery.py backend/tests/test_eval_harness.pycd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test uv run pytest tests/test_memory_repository.py tests/test_delivery.py tests/test_eval_harness.py::test_run_runtime_evals_passes_all_scenarios tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_details -q- result:
58 passed
- result:
- review:
- GitHub review comment on PR
#232correctly identified the policy-time Python scan in scoped procedural-memory reads - internal subagent review sessions were started for this follow-up (
Schrodinger019d2e2b-1b27-7f32-bd1a-1cf3afab0ce8,Socrates019d2e2d-e8e5-7d12-9d88-31d6ae469539,Franklin019d2e2f-5dba-75e2-8026-5e4226ba6ca4,Mill019d2e30-7a86-7772-9078-54c6c7737d4a) but they did not return findings before follow-up validation completed
- GitHub review comment on PR
- root cause addressed:
- residual risk:
- the overlay-order limitation recorded here was resolved later in Batch D by
guardian-learning-arbitration; the remaining gap is stronger long-horizon intervention learning beyond the now evidence-weighted live-versus-durable arbitration layer
- the overlay-order limitation recorded here was resolved later in Batch D by
guardian-learning-evidence-foundation
- root cause addressed:
- the codebase now has two policy-time learning sources: fresh
GuardianLearningSignalheuristics and durable procedural memories - before this slice, those sources exposed only bias labels, not a comparable evidence surface, so the next arbitration layer would have had to compare incomparable inputs
- the codebase now has two policy-time learning sources: fresh
- scope:
- added
backend/src/guardian/learning_evidence.pyas the shared evidence model for guardian-learning axes, including axis ordering, field mapping, confidence/data-quality normalization, recency scoring, and neutral evidence defaults - extended
GuardianLearningSignalinbackend/src/guardian/feedback.pywith per-axis evidence so live learning now exposessupport_count,recency_score,confidence_score,quality_score, andlast_confirmed_atalongside the existing bias fields - extended
ProceduralMemoryGuidanceinbackend/src/memory/procedural_guidance.pywith the same per-axis evidence surface so durable procedural lessons can be compared without re-parsing ad hoc metadata at every policy call site - updated
backend/src/memory/procedural.pyso persisted procedural lesson metadata follows the active-bias support counts already exposed on the live signal instead of drifting back to broader axis-level totals during memory sync - added regressions in
backend/tests/test_guardian_feedback.pycovering live evidence exposure, partial stale procedural metadata, and live-versus-durable support-count parity for active guidance axes
- added
- local regression fixed before the slice was recorded:
- the first pass left the live and durable evidence surfaces internally inconsistent in the opposite direction: live
axis_evidence.support_countcorrectly counted only rows that supported the currently selected bias, butsync_learning_signal_memories()still persisted broader axis-levelevidence_counttotals that could include newer rows pushing the other bias - that meant the same learned delivery bias could report
support_count=2in the live signal and then reappear asevidence_count=3after persistence, which would skew the next arbitration layer toward stale contradictory evidence - fixed by teaching
backend/src/memory/procedural.pyto reuse the live axis-evidence support count when it exists, persisting bothsupport_countandevidence_countfrom that active-bias surface, and havingbackend/src/memory/procedural_guidance.pyprefer explicitsupport_countmetadata while still tolerating older memories that only carryevidence_count
- the first pass left the live and durable evidence surfaces internally inconsistent in the opposite direction: live
- validation:
python3 -m py_compile backend/src/guardian/learning_evidence.py backend/src/guardian/feedback.py backend/src/memory/procedural.py backend/src/memory/procedural_guidance.py backend/tests/test_guardian_feedback.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_feedback.py backend/tests/test_memory_repository.py::test_list_memories_for_scope_filters_procedural_memories backend/tests/test_memory_repository.py::test_list_memories_for_scope_skips_non_object_or_invalid_metadata_payloads -q- result:
21 passed
- result:
- subagent review:
Locke,Harvey, andHypatiareturned concrete issues against the first#240implementation pass:- native-channel and async-native evidence could still be inferred from generic positive rows without proof that the winning intervention actually used native delivery
- missing
transportcould still be treated as direct/browser evidence, which made delivery-axis lessons stronger than the runtime data justified - procedural reads did not reliably fall back from malformed
support_countmetadata to legacyevidence_count - durable procedural evidence was still reconstructing confidence, quality, and recency from memory-level fields instead of loading the same comparable evidence metadata the live signal exposed
- fixed by requiring explicit direct/native transport evidence, falling back from malformed
support_countto legacyevidence_count, and persisting/loading explicit confidence, quality, weighted-support, and last-confirmed evidence metadata on procedural lessons
guardian-learning-arbitration
- root cause addressed:
- policy-time learning guidance was still resolving conflicts by overlay order: if procedural memory had any non-neutral lesson, it could overwrite fresher live guardian-learning heuristics without proving that the durable lesson still had stronger evidence
- that kept delivery and guardian-state synthesis shallow even after the memory substrate could expose comparable evidence across live and durable sources
- scope:
- added
backend/src/guardian/learning_arbitration.pyas the shared policy-time resolver that scores live and procedural evidence per axis using support count, confidence, quality, and recency, then resolves conflicts with explicit tie rules instead of simple overwrite order - updated
backend/src/observer/delivery.pyso proactive delivery uses the arbitration result, records the effective learned biases after arbitration, and logs per-axis arbitration source, reason, and winning weight in the delivery audit details - updated
backend/src/guardian/state.pyso guardian-state synthesis uses the same arbitration surface as delivery instead of a separate procedural overlay path - hardened the
#240evidence substrate underneath the resolver so livesupport_countremains directional to the currently selected bias, while durable procedural memories persist bothsupport_countand broaderevidence_countwithout collapsing them into the same field - added unit coverage in
backend/tests/test_learning_arbitration.pyfor stale-conflict, live-gap-fill, and missing-evidence paths, plus integration regressions inbackend/tests/test_delivery.pyandbackend/tests/test_guardian_state.pyproving stale procedural memory no longer overrides a stronger live signal at real policy call sites
- added
- local regression fixed while landing the slice:
- the first
#240pass still let persistence flatten directionalsupport_countinto broader axis-level totals, which would have biased the new arbitration weights toward contradictory rows that supported the other bias - fixed by teaching
backend/src/memory/procedural.pyto persist the live signal's directionalsupport_countwhen available, while keepingevidence_countas the broader durability/confidence measure and havingbackend/src/memory/procedural_guidance.pyprefer the explicitsupport_counton read
- the first
- validation:
python3 -m py_compile backend/src/guardian/learning_arbitration.py backend/src/guardian/feedback.py backend/src/guardian/learning_evidence.py backend/src/guardian/state.py backend/src/memory/procedural.py backend/src/observer/delivery.py backend/tests/test_learning_arbitration.py backend/tests/test_guardian_feedback.py backend/tests/test_delivery.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_feedback.py backend/tests/test_learning_arbitration.py backend/tests/test_delivery.py::test_deliver_uses_procedural_memory_guidance_when_heuristic_signal_is_neutral backend/tests/test_delivery.py::test_deliver_prefers_native_transport_when_procedural_memory_promotes_async_delivery backend/tests/test_delivery.py::test_deliver_uses_live_signal_when_conflicting_procedural_memory_is_stale backend/tests/test_guardian_state.py::test_build_guardian_state_uses_procedural_memory_guidance_when_live_signal_is_neutral backend/tests/test_guardian_state.py::test_build_guardian_state_prefers_live_learning_when_stale_memory_conflicts backend/tests/test_memory_repository.py::test_list_memories_for_scope_filters_procedural_memories backend/tests/test_memory_repository.py::test_list_memories_for_scope_skips_non_object_or_invalid_metadata_payloads -q- result:
30 passed
- result:
- subagent review:
Huygensreturned three concrete issues against the first#237implementation pass:- guardian-state learning guidance still omitted the delivery axis even when arbitration had selected a non-neutral delivery bias
- guardian-state arbitration still hardcoded
intervention_type="advisory"instead of using the requested intervention type - arbitration provenance still labeled the effective guidance
heuristic_plus_procedural_memorywhenever procedural memory was active, even if live evidence won every contested axis
- fixed by rendering delivery guidance in guardian state, threading the requested intervention type through the guardian-state arbitration path, and deriving provenance from the actual selected-axis sources instead of from procedural-memory presence alone
scoped-procedural-guidance-resolution
- root cause addressed:
- procedural guidance had already grown the ability to persist thread- and project-scoped lessons, but policy-time resolution was still unsafe in two ways:
- the candidate scope order could let broader project guidance resolve before tighter continuity-thread guidance when both existed
- the repository fallback path treated memories with extra scope keys as compatible with narrower scope queries, so a project-scoped memory could leak back into the global fallback lane
- procedural guidance had already grown the ability to persist thread- and project-scoped lessons, but policy-time resolution was still unsafe in two ways:
- scope:
- updated
backend/src/memory/procedural_guidance.pyso candidate scope order is nowthread+project -> thread -> project -> global, and the resolver selects the first tier with any active lessons before resolving the full guidance bundle from that tier - updated
backend/src/memory/repository.pysolist_memories_for_scope()resolves by exactscope_keyfirst and only uses the JSON field matcher as a legacy fallback, preventing project- or thread-scoped lessons from satisfying unrelated global fallback lookups - added deterministic resolver coverage in
backend/tests/test_guardian_feedback.pyfor thread-over-project precedence, first-nonempty-tier selection, and bundle isolation from broader scope tiers - added delivery and guardian-state regressions in
backend/tests/test_delivery.pyandbackend/tests/test_guardian_state.pyproving the scoped resolver now changes real policy outcomes and learning guidance at the runtime call sites that already passsession_idandactive_project
- updated
- local regression fixed while landing the slice:
- the first fallback-order patch exposed that
list_memories_for_scope()was only checking whether the requested keys were present, not whether the memory belonged to the exact same scoped lane - that meant a request for global advisory timing guidance could still pick an
active_project=Atlasmemory during the global fallback step if the base keys matched - fixed by querying exact
scope_keymatches first and only using the broader JSON matcher for older entries that do not carry the newer exact scope key
- the first fallback-order patch exposed that
- aggregate-PR follow-up before the slice can be treated as stable:
- root cause addressed:
- the first context-aware resolver still selected lessons per axis across every fallback tier, so a thread+project guidance bundle could inherit unrelated broader-scope lessons even after the resolver had found narrower Atlas-specific delivery and timing guidance
- that hybrid bundle was strong enough to change real policy outcomes back to
bundle, which meant the scoped resolver was still leaking broader negative guidance into a narrower active context
- fixes:
backend/src/memory/procedural_guidance.pynow selects the first scope tier that has any active non-neutral lessons, then resolves the full guidance bundle only from that tier instead of mixing lessons across thread+project, thread, project, and global fallbacksbackend/src/memory/repository.pynow acceptsexact_scope_keysfor scoped reads so context fallback steps can require exact matches forcontinuity_thread_idandactive_projectbefore falling back to legacy JSON matchingbackend/src/observer/delivery.pynow passessession_idandctx.active_projectintoload_procedural_memory_guidance(...), andbackend/src/db/models.pyplusbackend/src/guardian/feedback.pynow persistactive_projectonGuardianInterventionso scoped learning refreshes can actually write thread/project memories from live runtime contextbackend/tests/test_guardian_feedback.pynow addstest_load_procedural_memory_guidance_does_not_mix_broader_lessons_into_selected_scope()so the resolver must keep scoped bundles isolated from broader fallback lessons
- validation:
python3 -m py_compile backend/src/memory/procedural_guidance.py backend/tests/test_guardian_feedback.py backend/tests/test_delivery.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_feedback.py::test_load_procedural_memory_guidance_prefers_thread_and_project_scope backend/tests/test_guardian_feedback.py::test_load_procedural_memory_guidance_prefers_thread_scope_over_project_scope backend/tests/test_guardian_feedback.py::test_load_procedural_memory_guidance_selects_first_nonempty_scope_tier backend/tests/test_guardian_feedback.py::test_load_procedural_memory_guidance_prefers_scoped_thread_and_project_context backend/tests/test_guardian_feedback.py::test_load_procedural_memory_guidance_does_not_mix_broader_lessons_into_selected_scope backend/tests/test_guardian_feedback.py::test_feedback_refresh_writes_thread_and_project_scoped_memories backend/tests/test_delivery.py::test_deliver_prefers_scoped_project_and_thread_guidance_over_global_memory backend/tests/test_delivery.py::test_deliver_prefers_thread_scoped_procedural_guidance_over_project_or_global_scope backend/tests/test_delivery.py::test_deliver_prefers_context_scoped_guidance_over_conflicting_global_memory backend/tests/test_guardian_state.py::test_build_guardian_state_prefers_scoped_project_guidance_over_global_memory backend/tests/test_guardian_state.py::test_build_guardian_state_prefers_thread_scoped_procedural_guidance_over_project_scope backend/tests/test_guardian_state.py::test_build_guardian_state_prefers_context_scoped_guidance_over_global_memory -q- result:
12 passed
- result:
- root cause addressed:
- validation:
python3 -m py_compile backend/src/memory/procedural_guidance.py backend/src/memory/repository.py backend/tests/test_guardian_feedback.py backend/tests/test_delivery.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_feedback.py::test_load_procedural_memory_guidance_prefers_thread_scope_over_project_scope backend/tests/test_guardian_feedback.py::test_load_procedural_memory_guidance_selects_first_nonempty_scope_tier backend/tests/test_delivery.py::test_deliver_prefers_thread_scoped_procedural_guidance_over_project_or_global_scope backend/tests/test_guardian_state.py::test_build_guardian_state_prefers_thread_scoped_procedural_guidance_over_project_scope backend/tests/test_memory_repository.py::test_list_memories_for_scope_filters_procedural_memories backend/tests/test_memory_repository.py::test_list_memories_for_scope_skips_non_object_or_invalid_metadata_payloads -q- result:
6 passed
- result:
- subagent review:
Lorentzflagged one hallucinated assumption in the first#242write-up: some test names and prose still read like per-axis hierarchical fallback, but the implemented resolver intentionally selects one scope tier and keeps that bundle isolated from broader lessons- fixed by making the contract explicit in the tests and docs: candidate tiers are ordered
thread+project -> thread -> project -> global, the first non-empty tier wins, and broader tiers do not backfill missing axes once a narrower tier is active
- aggregate-PR follow-up after review:
- existing SQLite databases created before this slice did not have
guardian_interventions.active_project, so upgraded runtimes could fail at first ORM read/write against that model - fixed by teaching
backend/src/db/engine.py::_ensure_legacy_columns()to add the missingactive_projectcolumn and index on upgrade, with a regression inbackend/tests/test_db_engine.py
- existing SQLite databases created before this slice did not have
weighted-guardian-learning-support
- root cause addressed:
- even after
#240and#237, live bias selection still relied too heavily on flat counts, and arbitration could still overvalue noisier higher-volume evidence over stronger lower-volume evidence - the live outcome loop was also still manufacturing unsupported phrasing, cadence, and thread lessons from generic success/failure rows even though runtime never records which of those variants was actually used
- delivery-axis negatives were still transport-agnostic, so failed native notifications could be misread as evidence that browser/direct interruptions should be reduced
- even after
- scope:
- updated
backend/src/guardian/feedback.pyso live bias selection now weights supporting interventions by guardian confidence, data quality, and outcome quality instead of comparing raw counts alone - extended
GuardianLearningAxisEvidenceand procedural memory metadata with explicitweighted_support, then updatedbackend/src/memory/procedural.py,backend/src/memory/procedural_guidance.py, andbackend/src/guardian/learning_arbitration.pyso durable memories persist that value and policy-time arbitration actually scores conflicts from it - strengthened timing, channel, and blocked-state evidence from successful direct/native delivery outcomes where the runtime data actually proves the chosen route worked
- stopped deriving phrasing, cadence, and thread biases from generic outcomes and stopped persisting those unsupported axes into procedural memory until runtime records the actual intervention variants explicitly
- tightened delivery negative evidence so only explicit direct-transport failures can teach
reduce_interruptions; failed native notifications no longer create a fake direct-interruption lesson
- updated
- local regressions fixed while landing the slice:
- procedural
weighted_supportwas initially loaded correctly but still ignored by the arbitration scorer, so the final conflict resolver kept behaving like a raw-count system - the first weighted branch still let generic outcomes manufacture phrasing/cadence/thread lessons and still counted native failures against the direct-delivery axis
- fixed by scoring support from
weighted_supportwhen present, neutralizing unsupported axes in the live loop and procedural sync path, and requiring explicit direct transport on the negative delivery side
- procedural
- aggregate-PR follow-up after review:
update_outcome(...)was still refreshing procedural learning memories only for failure transitions, even though weighted positive evidence now also comes from successfuldeliveredandfeedback_receivedoutcomes- that meant live learning could strengthen from outcome-only async/direct successes while durable scoped procedural guidance stayed stale until explicit feedback arrived
- fixed by refreshing learning memories whenever outcome transitions enter or leave the success/failure states that the weighted evidence model actually uses, with a regression in
backend/tests/test_guardian_feedback.py
- aggregate-PR CI follow-up:
- the eval harness was still asserting the pre-Batch D policy contract in three places:
strategist_tick_learning_continuity_behaviorstill expected learned generic feedback to force a delivered native-notification pathguardian_outcome_learningstill expected generic helpful/not-helpful rows to manufacture direct-delivery lessons instead of the now-supported suppression-only behaviorprocedural_memory_adaptation_behaviorstill expected the pre-Batch D active procedural-memory count
- the shipped policy is stricter now: generic feedback without explicit transport/runtime evidence no longer invents delivery/channel lessons, learned suppression can defer or queue instead of forcing live delivery, and the Batch D procedural layer leaves eight active procedural memories in the adaptation eval
- fixed by updating
backend/src/evals/harness.pyandbackend/tests/test_eval_harness.pyto assert the current evidence-weighted queue/defer behavior and the new procedural-memory count instead of the stale pre-Batch D contract
- the eval harness was still asserting the pre-Batch D policy contract in three places:
- validation:
python3 -m py_compile backend/src/guardian/feedback.py backend/src/guardian/learning_arbitration.py backend/src/memory/procedural.py backend/tests/test_guardian_feedback.py backend/tests/test_learning_arbitration.py backend/tests/test_delivery.py backend/tests/test_guardian_state.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_feedback.py backend/tests/test_learning_arbitration.py backend/tests/test_delivery.py backend/tests/test_guardian_state.py -q- result:
95 passed
- result:
- subagent review:
Lorentzreturned the main arbitration bug:backend/src/guardian/learning_arbitration.pywas still deriving conflict weight from rawsupport_count, which made the newlearning_arbitration_mode=evidence_weightedclaim false for real disagreementsEpicurusreturned two grounded review findings:- phrasing, cadence, and thread biases were still being inferred from generic outcomes even though the runtime never records which phrasing style, cadence strategy, or thread mode was used
- the negative side of the delivery axis still counted transport-agnostic failures, so bad native-notification outcomes could teach
reduce_interruptionsand then be persisted as a lesson about direct interruptions
- fixed by routing arbitration through
weighted_support, neutralizing unsupported axes until explicit runtime instrumentation exists, and scoping negative delivery evidence to explicit direct transports only
Batch J Branch Review Log
durable-world-model-evidence-fusion-v1
- scope:
- extended
backend/src/guardian/world_model.pysoGuardianWorldModelnow recordsfocus_sourceandjudgment_risks, making guardian-state synthesis distinguish live observer focus from session or memory fallback and surface when the world model should stay cautious - updated
backend/src/guardian/state.pyso world-model confidence no longer stays artificially grounded when focus is only inferred from history, when durable project recall conflicts with the live observer project, or when recent intervention outcomes skew negative enough to keep receptivity selective
- extended
Batch P Branch Review Log
long-horizon-guardian-learning-v2
- root causes addressed:
- guardian-state synthesis was still treating collaborator, obligation, and timeline recall as flat memory buckets, so stale durable support context could stay ahead of live-project-aligned context and weaken judgment only after the operator manually inferred the mismatch
- recent execution pressure was visible in the world model, but it was not treated as a contradiction source against the live project anchor and it did not combine with negative intervention trends to make the guardian more selective
- scope:
- updated
backend/src/guardian/world_model.pyso live-project anchors reprioritize collaborators, obligations, routines, and timeline recall before they feed project timelines, memory signals, and continuity threads - taught
GuardianWorldModelto surface explicit stale-support and stale-execution contradiction risks against the live project anchor instead of only project-label mismatch - promoted recent execution pressure into open-loop and active-blocker synthesis so longer-horizon follow-through pressure stays visible in the world model instead of only the execution sidebar
- tightened intervention receptivity so negative outcome trends can drop from
mediumtolowwhen the live project anchor is also contradicted by stale support or execution evidence - extended
backend/src/evals/harness.pyand guardian-state tests so the deterministic guardian judgment contract now proves supporting-context mismatch, execution-context mismatch, and low-confidence defer behavior together
- updated
- focused review:
- first real bug:
- the initial live-topic reprioritization only changed the raw memory lists, but
project_timeline,memory_signals, andcontinuity_threadswere still being built earlier from the pre-reordered lists, so the “prioritized live-project context” claim was false for timeline output - fixed by moving those derived structures after the reprioritization step and pinning the regression in
backend/tests/test_guardian_state.py
- the initial live-topic reprioritization only changed the raw memory lists, but
- second real bug:
- the first long-horizon execution-pressure patch pulled
execution_pressureintoopen_loopsandactive_blockersbefore that variable was initialized, which broke the guardian-state suite immediately - fixed by moving
execution_pressure = _extract_lines(recent_execution_summary, limit=3)earlier so blocker/pressure synthesis and contradiction scoring share the same initialized evidence
- the first long-horizon execution-pressure patch pulled
- first real bug:
- validation:
python3 -m py_compile backend/src/guardian/world_model.py backend/src/guardian/state.py backend/src/evals/harness.py backend/tests/test_guardian_state.py backend/tests/test_eval_harness.pycd backend && .venv/bin/python -m pytest tests/test_guardian_state.py -q -x- result:
28 passed
- result:
cd backend && .venv/bin/python -m pytest tests/test_eval_harness.py::test_main_lists_available_scenarios -q- result:
1 passed
- result:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test uv run python -m src.evals.harness --scenario guardian_judgment_behavior --indent 0- result:
passed, withincludes_supporting_context_mismatch=true,includes_execution_context_mismatch=true, anddecision_reason=low_guardian_confidence
- result:
Batch Q Branch Review Log
cross-thread-project-state-and-follow-through-v1
- root causes addressed:
- the world model already carried recent session summaries, but it was not treating live-project-matching recent threads as stronger continuity evidence than unrelated recent-session carryover
- execution setbacks were already visible in
execution_pressure, but they were still detached from open project commitments, so the guardian could not surface explicit follow-through risk on the same live project
- scope:
- updated
backend/src/guardian/world_model.pyso recent-session continuity is reprioritized by the live project anchor just like collaborator, obligation, and timeline context - carried live-project-matching recent-thread continuity into commitments,
next_up, dominant-thread selection, and project-state synthesis instead of leaving it buried in the recent-session summary block - surfaced explicit follow-through risk when cross-thread commitments and recent execution setbacks both match the live project, and promoted that pressure into the world-model open-loop surface
- extended
backend/src/evals/harness.pyand guardian-state tests so the deterministic guardian world-model contract now proves cross-thread continuity matching and follow-through-risk exposure together
- updated
- focused review:
- real bug found:
- the first Batch Q pass still treated
Investor briefandinvestor-briefas different topics, so execution-pressure rows could fail to link back to the live project even when they were clearly about the same workflow - fixed by normalizing non-alphanumeric separators in
backend/src/guardian/world_model.py::_normalize_topic(...)before topic matching, which made the eval contract truthful instead of depending on punctuation luck
- the first Batch Q pass still treated
- real bug found:
- validation:
python3 -m py_compile backend/src/guardian/world_model.py backend/src/evals/harness.py backend/tests/test_guardian_state.py backend/tests/test_eval_harness.pycd backend && .venv/bin/python -m pytest tests/test_guardian_state.py -q -x- result:
30 passed
- result:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test uv run python -m src.evals.harness --scenario guardian_world_model_behavior --indent 0- result:
passed, withcontinuity_thread_matches_live_project=trueandincludes_follow_through_risk=true
- result:
- added
guardian_judgment_behaviortobackend/src/evals/harness.pyplus targeted regressions inbackend/tests/test_guardian_state.py,backend/tests/test_eval_harness.py, andbackend/tests/test_strategist_tick.pyproving the new focus provenance, contradiction-aware confidence downgrade, and medium-urgency defer path
- validation:
python3 -m py_compile backend/src/guardian/world_model.py backend/src/guardian/state.py backend/src/evals/harness.py backend/tests/test_guardian_state.py backend/tests/test_strategist_tick.py backend/tests/test_eval_harness.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_state.py backend/tests/test_strategist_tick.py backend/tests/test_eval_harness.py::test_main_lists_available_scenarios backend/tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_details backend/tests/test_eval_harness.py::test_guardian_judgment_runtime_eval_exposes_conflict_suppression -q- result:
28 passed
- result:
- subagent review:
- reviewer:
Singer(019d3a5f-aefc-79f1-86fe-3f8ff454d587) - initial finding:
- the first project-mismatch downgrade was treating
get_recent_projects()screen recency as recalled project context, so it could lower confidence from observer-timing skew instead of a true durable-recall conflict
- the first project-mismatch downgrade was treating
- fixed before the slice stayed marked complete:
- split durable project recall from recent screen-project signals in
backend/src/guardian/world_model.py, so only durableproject_memorycan trigger the recalled-project mismatch risk while recent project activity stays visible context - updated the project-mismatch unit test and runtime eval fixture so the degraded path is now proven by durable project memory alone instead of
get_recent_projects()noise
- split durable project recall from recent screen-project signals in
- final recheck:
Singerreported no remaining material findings after the durable-recall mismatch fix and updated proof path
- reviewer:
Batch R Branch Review Log
multi-project-arbitration-and-cross-source-project-carryover-v1
- root causes addressed:
- project arbitration was still effectively list-ordered, so observer aliases, recent screen-project ordering, or whichever project string appeared first could outweigh broader cross-source evidence
- semantically identical labels like
AtlasandAtlas launchwere still treated as separate project competitors in ambiguity and active-project output, which made both risk reporting and project-state synthesis noisier than the actual evidence
- scope:
- added scored project-candidate arbitration in
backend/src/guardian/world_model.pyso observer anchors, durable project memory, recent-session continuity, execution pressure, and supporting collaborator or obligation or timeline context all contribute to one ranked project view - switched dominant-thread, project-state, and active-project synthesis to follow the preferred project anchor instead of raw list order, while still preserving explicit mismatch checks against the live observer project when that anchor loses
- added explicit project-anchor ambiguity and diverted-attention risks when evidence remains split across projects instead of pretending the top list item is grounded
- deduped semantic project aliases in active-project output so richer canonical project labels survive while short observer aliases stop showing up as fake extra projects
- extended guardian-state and eval-harness coverage to prove stronger cross-source project preference, ambiguity downgrades, and drift-risk exposure
- added scored project-candidate arbitration in
- focused review:
- first real bug:
- the initial ambiguity pass was too eager and downgraded otherwise grounded state from the passive
get_recent_projects()list alone, without any corroborating recent-session, execution, or memory evidence - fixed by requiring grounded cross-source competition before the ambiguity risk is emitted
- the initial ambiguity pass was too eager and downgraded otherwise grounded state from the passive
- second real bug:
- the first semantic-dedup pass collapsed richer canonical labels like
Atlas launchback to the shorter observer aliasAtlas, which regressed project-linked memory surfaces and caused old tests to pass for the wrong representation - fixed by preferring durable project-memory labels ahead of observer aliases in active-project synthesis while still deduping semantically overlapping variants
- the first semantic-dedup pass collapsed richer canonical labels like
- first real bug:
- validation:
python3 -m py_compile backend/src/guardian/world_model.py backend/src/evals/harness.py backend/tests/test_guardian_state.py backend/tests/test_eval_harness.pycd backend && .venv/bin/python -m pytest tests/test_guardian_state.py -q -x- result:
32 passed
- result:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test uv run python -m src.evals.harness --scenario guardian_judgment_behavior --indent 0- result:
passed, withdominant_thread_prefers_hermes=true,project_state_includes_hermes_execution=true, andincludes_project_anchor_drift=true
- result:
- validation intentionally not claimed:
cd backend && .venv/bin/python -m pytest tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_details -q -x- reason: this broad eval-harness assertion path continued to stall in this environment’s long-run path instead of returning a reliable pass/fail signal
intervention-quality-learning-from-longer-horizon-outcomes-v1
-
scope:
- added scoped live-learning resolution in
backend/src/guardian/feedback.py, so guardian policy now resolves per-axis live evidence acrossglobal,thread,project, andthread_projectlanes before comparing that guidance against durable procedural memory - updated
backend/src/guardian/state.pyandbackend/src/observer/delivery.pyso both guardian-state synthesis and live delivery use the same scoped live-learning result instead of flattening everything through the global recent-feedback window - aligned guardian-state recent-feedback summaries with the same dominant scope that is actually driving interruption behavior, and exposed
live_learning_scopeplus per-axislive_learning_scope_axesin delivery audit details - added targeted regressions in
backend/tests/test_guardian_feedback.py,backend/tests/test_guardian_state.py, andbackend/tests/test_delivery.pyproving project-specific negative outcome history can override unrelated global direct-delivery success
- added scoped live-learning resolution in
-
validation:
python3 -m py_compile backend/src/guardian/learning_evidence.py backend/src/guardian/learning_arbitration.py backend/src/guardian/feedback.py backend/src/guardian/state.py backend/src/observer/delivery.py backend/tests/test_guardian_feedback.py backend/tests/test_guardian_state.py backend/tests/test_delivery.pybackend/.venv/bin/python -m pytest backend/tests/test_guardian_feedback.py backend/tests/test_guardian_state.py backend/tests/test_delivery.py backend/tests/test_eval_harness.py::test_main_lists_available_scenarios backend/tests/test_eval_harness.py::test_runtime_eval_scenarios_expose_expected_details backend/tests/test_eval_harness.py::test_guardian_judgment_runtime_eval_exposes_conflict_suppression -q- result:
103 passed
- result:
-
subagent review:
- reviewer:
Singer(019d3a5f-aefc-79f1-86fe-3f8ff454d587) - initial findings:
- the first scoped resolver could still report
effective_signalcounts from a scope that was not actually driving the selected behavioral axes, so audit and guardian-state summaries could misstate the evidence behind an interruption decision - guardian-state recent-feedback summaries were still intersecting strictly on
session_id + active_project, which could hide the broader project-only or thread-only outcomes that the new scoped resolver was already using
- the first scoped resolver could still report
- fixed before the slice stayed marked complete:
- made dominant-scope selection prioritize the actual interruption-driving axes (
delivery,suppression,blocked_state, andtiming) before falling back to aggregate scope weight - changed guardian-state feedback summarization to follow the same dominant scope that is actually driving the resolved live learning signal
- pinned both fixes in the state and delivery regression tests so the surfaced counts, friction wording, and audit scope stay aligned
- made dominant-scope selection prioritize the actual interruption-driving axes (
- final recheck:
Singerreported no remaining material findings after the dominant-scope and scoped-summary fixes
- reviewer:
-
aggregate PR follow-up on
#254:- GitHub review caught that
backend/src/guardian/world_model.pywas still treatingcurrent_eventas a non-observer focus source in the competing-project risk gate, which could incorrectly downgrade world-model confidence when multiple project signals were present withoutactive_project - the same review also caught that
backend/src/guardian/feedback.pysorted live scope candidates by raw weight before the tie-tolerance branch ran, so_SCOPE_WEIGHT_TIE_TOLERANCEonly changed the explanation string and never the selected scope - fixed by treating
current_eventas a live observer focus anchor in the project-risk gate, and by applying tie selection across all near-tied live scope candidates before choosing the winning scope - added regressions in
backend/tests/test_guardian_state.pyandbackend/tests/test_guardian_feedback.pyso current-event anchoring and near-tie scope specificity stay pinned
- GitHub review caught that
Batch N Branch Review Log
additive-memory-provider-extensibility-v1
- scope:
- added manifest, layout, and registry support for extension-defined
memory_providers, including canonical-memory ownership rules that keep guardian memory authoritative - added
backend/src/memory/providers.pyso Seraph can inventory configured memory-provider adapters, expose runtime state to operators, and merge additive provider retrieval into memory planning without making providers canonical - added
backend/src/api/memory.pyplus/api/memory/providersrouting so operator surfaces can inspect configured provider state directly - extended
backend/src/memory/retrieval_planner.pyso additive provider hits join semantic context and memory buckets on a separateexternal_memorylane, while provider failures degrade cleanly back to guardian-owned retrieval - added regression coverage in
backend/tests/test_memory_providers.pyfor provider inventory, route registration, additive retrieval merge, and provider-failure fallback
- added manifest, layout, and registry support for extension-defined
- validation:
python3 -m py_compile backend/src/extensions/capability_contributions.py backend/src/extensions/layout.py backend/src/extensions/manifest.py backend/src/extensions/registry.py backend/src/memory/providers.py backend/src/memory/retrieval_planner.py backend/src/api/memory.py backend/src/api/router.py backend/tests/test_memory_providers.pycd backend && .venv/bin/python -m pytest tests/test_memory_providers.py -q- result:
4 passed
- result:
- local review notes:
- the first test helper wrote an extension manifest that required a future Seraph version (
>=2026.4.5), so the registry correctly skipped the provider and the retrieval-lane assertions failed even though the runtime path itself was sound - fixed by generating the test fixture manifest against the current backend runtime version instead of a future compatibility floor
- the first inventory endpoint test depended on the full shared app client fixture, which stalled in this environment before reaching the simple route contract under test
- fixed by validating the route contract directly through
src.api.memory.list_memory_providers()and pinning separate router-registration coverage inbackend/tests/test_memory_providers.py
- the first test helper wrote an extension manifest that required a future Seraph version (
- subagent review:
- reviewer:
Parfit(019d5e35-6f63-72b0-84a1-9ab090b27da2) - findings:
memory_providerswere wired into manifest discovery but not into the normal extension lifecycle control plane, so list/configure/enable flows would have ignored them outside hand-edited state- provider
health()exceptions could still bubble out of inventory building and break normal memory retrieval instead of degrading cleanly to guardian-owned memory - the parser was advertising
canonical_write_mode=read_througheven though the runtime only implemented additive retrieval
- fixed before the slice stayed marked complete:
- wired
memory_providersthroughbackend/src/extensions/lifecycle.pyso extension connector listing, config writes, enable/disable flows, and package toggle targets now include memory providers - wrapped provider health checks in
backend/src/memory/providers.pyand kept provider availability in diagnostics instead of globally downgrading canonical memory retrieval state - narrowed the supported write mode to
additive_onlyuntil a real read-through path exists
- wired
- reviewer:
Batch AI Branch Review Log
long-horizon-guardian-learning-and-intervention-quality-v3
- scope:
- extended
backend/src/guardian/feedback.pyandbackend/src/guardian/learning_evidence.pyso guardian learning tracks multi-day and scheduled outcome spread instead of only short-window outcome counts - carried that spread into
backend/src/memory/procedural.pyandbackend/src/memory/procedural_guidance.pyso procedural recall can preserve the same longer-horizon evidence lanes - upgraded
backend/src/guardian/world_model.pyso guardian state now surfaces explicit goal-alignment, routine, and collaborator watchpoints and lowers receptivity further when multi-day negative trends line up with conflicting support or execution evidence - added targeted regressions in
backend/tests/test_guardian_feedback.pyandbackend/tests/test_guardian_state.py, plus the newguardian_long_horizon_learning_behaviorscenario inbackend/src/evals/harness.py
- extended
- validation:
python3 -m py_compile backend/src/guardian/learning_evidence.py backend/src/guardian/feedback.py backend/src/guardian/world_model.py backend/src/memory/procedural.py backend/src/memory/procedural_guidance.py backend/src/evals/harness.py backend/tests/test_guardian_feedback.py backend/tests/test_guardian_state.py backend/tests/test_eval_harness.py backend/tests/test_capabilities_api.pycd backend && .venv/bin/python -m pytest tests/test_guardian_feedback.py tests/test_guardian_state.py -q -k "multi_day or long_horizon or watchpoint or guardian_world_model_behavior"- result:
4 passed
- result:
cd backend && .venv/bin/python -m pytest tests/test_eval_harness.py -q -k "test_main_lists_available_scenarios"- result:
1 passed
- result:
cd backend && .venv/bin/python -m pytest tests/test_capabilities_api.py -q -k "test_capabilities_overview_includes_catalog_extension_packs"- result:
1 passed
- result:
cd docs && npm run build
- subagent review:
- reviewer:
Epicurus(019d6840-e266-72c2-85c2-0f73e114ba85) - findings:
- the first routine-watchpoint selector could let an unrelated obligation dominate the live project watchpoint just because it was listed first
- the new long-horizon eval seeded March 2026 interventions but still used wall-clock
nowduring guardian-state synthesis, so the runtime scenario would have aged out over time
- fixed before the slice stayed marked complete:
- narrowed obligation priority in
backend/src/guardian/world_model.pyso obligations only outrank anchor timeline context when they are actually relevant to the live project/goals/event context - patched
src.guardian.feedback._nowinside the eval scenario so the new long-horizon proof stays time-stable instead of drifting with wall-clock time
- narrowed obligation priority in
- local CI follow-up:
- fixed the live
developbackend failure by updatingbackend/tests/test_capabilities_api.pyto the current extension-pack payload shape; the failing shard was asserting the pre-AJ catalog contract and then cancelling sibling backend shards through fail-fast
- fixed the live
- reviewer:
Batch AQ Branch Review Log
guardian-judgment-and-long-horizon-learning-quality-v4
- scope:
- extended
backend/src/guardian/world_model.pyso guardian state now ranks competing project context explicitly, exposes inspectable project-ranking diagnostics, and surfaces conservative stale-signal arbitration when recent execution/support evidence stays split across nearby anchors - extended
backend/src/guardian/state.pyso guardian state now carries inspectable learning diagnostics summarizing the dominant live-learning scope, observed outcome spread, long-horizon day spread, and any procedural-memory overrides - strengthened the guardian runtime eval details in
backend/src/evals/harness.pyso the world-model, judgment, and long-horizon scenarios expose the new ranking, stale-signal, and learning-diagnostic contracts directly - folded the pre-batch backend CI repairs into the same branch by hardening observer continuity timestamp serialization and isolating the threaded operator runtime eval from unrelated continuity rows
- added targeted regressions in
backend/tests/test_guardian_state.py,backend/tests/test_eval_harness.py, andbackend/tests/test_observer_api.py
- extended
- validation:
python3 -m py_compile backend/src/api/observer.py backend/src/evals/harness.py backend/src/guardian/state.py backend/src/guardian/world_model.py backend/tests/test_eval_harness.py backend/tests/test_guardian_state.py backend/tests/test_observer_api.pycd backend && .venv/bin/python -m pytest tests/test_observer_api.py -q -k "partial_namespace_items"- result:
1 passed
- result:
cd backend && .venv/bin/python -m pytest tests/test_guardian_state.py -q -k "prompt_block_exposes_confidence or project_anchor_ambiguity or ranks_anchor_context or learning_diagnostics"- result:
4 passed
- result:
cd backend && .venv/bin/python -m pytest tests/test_eval_harness.py -q -k "test_main_lists_available_scenarios or test_run_runtime_evals_passes_group_1"- result:
2 passed
- result:
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test uv run python -m src.evals.harness --scenario guardian_judgment_behavior --indent 0- result: passed
cd backend && OPENROUTER_API_KEY=test-key WORKSPACE_DIR=/tmp/seraph-test uv run python -m src.evals.harness --scenario guardian_long_horizon_learning_behavior --indent 0- result: passed
- broad
tests/test_eval_harness.pyruntime-detail selection and broader guardian subsets still stalled in this environment, so they are intentionally not claimed here
- subagent review:
- reviewer:
Peirce(019d6892-49a9-7f73-a231-1c24e90da129) - findings:
- the first stale-signal arbitration pass classified supporting context, execution pressure, and recent threads against the live observer project while phrasing the diagnostics as if they were classified against the preferred project anchor, which could produce self-contradictory arbitration output
- the observer continuity hardening only protected a subset of optional namespace attributes; recent intervention payloads still crashed or failed response validation when older items omitted several string fields
- fixed before the slice stayed marked complete:
- recomputed the stale-signal arbitration inputs in
backend/src/guardian/world_model.pyagainst the preferred anchor used by the arbitration layer, while leaving live-project conflict checks intact for judgment-risk scoring - finished the observer continuity fail-soft conversion in
backend/src/api/observer.pyso partial intervention rows normalize missing string fields safely and still satisfy the response contract
- recomputed the stale-signal arbitration inputs in
- additional local fixes already folded into the slice:
- widened the competing-anchor arbitration threshold in
backend/src/guardian/world_model.pyso near-split project evidence now surfaces conservative arbitration instead of looking falsely settled - corrected the new guardian-learning regression to use the real feedback repository API and durable timestamps instead of an over-simplified fixture shape
- aligned the long-horizon eval detail expectation in
backend/tests/test_eval_harness.pywith the shipped stronger receptivity downgrade (low) once multi-day negative trends and contradictory support line up
- widened the competing-anchor arbitration threshold in
- reviewer:
Non-Goals
- marketing “guardian intelligence” before the learning loop is real
- confusing retrieval volume with understanding quality
Acceptance Checklist
- Seraph can retain identity, memory, and goals across sessions
- Seraph can generate proactive guardian outputs from that context
- Seraph has an explicit guardian-state object rather than spreading that reasoning across call sites
- Seraph has an explicit intervention policy rather than only deliver-versus-queue heuristics
- Seraph records intervention outcomes and explicit user feedback in durable guardian state
- Seraph learns at least one policy-relevant lesson from intervention outcomes and explicit user feedback
- Seraph scores observer state by salience, confidence, and interruption cost before guardian strategy and delivery
- Seraph uses calibrated high-salience observer signals to change real delivery outcomes instead of only logging them
- Seraph has deterministic behavioral proof that the calibrated high-salience deliver path and degraded-confidence defer path stay distinct at the delivery gate
- Seraph has deterministic behavioral proof that strategist nudges can follow learned native-delivery bias and still remain visible through continuity surfaces
- Seraph has a first explicit world model inside guardian state instead of relying only on retrieval plus prompt prose
- Seraph's world model now reflects recent active projects, active constraints, recurring patterns, active routines, collaborators, recurring obligations, project timelines, structured memory signals, corroboration sources, continuity threads, and degraded execution signals instead of only static focus/commitment text
- Seraph now feeds learned communication guidance back into guardian state and intervention policy instead of leaving recent outcomes as passive history
- Seraph reliably learns from intervention outcomes in a way that improves future policy quality beyond the first delivery/channel bias layer
- Seraph reliably models the human well enough to intervene at consistently high quality