Context
Follow-up carved out of #14879 / PR #14882 (the honest-states fallback-cause fix). That PR made renderDeclaredIntentFallback attribute the measured cause via classifyFrontierEmptyCause, but wired the caller (GoldenPathSynthesizer.buildDeclaredIntentFallback) with only a verified binary "digested-history-exists" signal — enough to distinguish FRONTIER_UNANCHORED from COLD_START (the operator's 2026-07-06 case), but NOT the exact REM backlog/cycle state.
The Problem
classifyFrontierEmptyCause already accepts undigested + recentCycleCount and returns REM_STALLED when there is undigested work AND no recent cycle. But the caller cannot supply those honestly: V-B-A against the live graph DB (.neo-ai-data/sqlite/memory-core-graph.sqlite) showed the REM pipeline's digested/undigested/recentCycles are not cleanly derivable from the SQLite node table ($.type is null for ~143k nodes; summary% count = 622 ≠ the pipeline's ~1460 digested). A naive count would ship a new wrong number — the exact failure #14879 fixed. So REM_STALLED currently never fires from the live caller (it degrades to FRONTIER_UNANCHORED/UNATTRIBUTED, which is honest but coarse).
The Architectural Reality
The authoritative REM metrics live in the REM/consolidation subsystem (the source behind get_rem_pipeline_state: {undigested, digested, recentCycles}), NOT the graph Nodes table. buildDeclaredIntentFallback is a static, cold-cache SQLite-sourced method — threading pipeline state in must respect ADR-0019 (read AiConfig at the use site; no new pass-along/threading antipattern) and the method's "never blow the scheduling budget" bound.
The Fix
Give the caller a cheap, correct read of the REM pipeline state (undigested + recentCycleCount) and pass them into classifyFrontierEmptyCause, so a genuine REM_STALLED renders honestly instead of collapsing to FRONTIER_UNANCHORED. Identify the in-process accessor the REM subsystem exposes (mirror of the get_rem_pipeline_state MCP surface) rather than re-deriving from SQLite.
Acceptance Criteria
Out of Scope
- The classifier + render themselves (shipped in #14879 / PR #14882 — this only feeds them real metrics).
- The wake-daemon / REM-cycle-firing mechanism (separate lane).
Related
- Blocked-by: #14879 (merge PR #14882 first — this builds on the shipped classifier).
- Parent epic: #14472. Sibling vocabulary: #14565.
Origin Session ID: 9360840f-5d7a-4680-8110-86877722735b
Retrieval Hint: query_raw_memories("goldenPathPickupBridge classifyFrontierEmptyCause REM_STALLED pipeline state plumbing")
Live latest-open sweep: checked 2026-07-06; no equivalent found (only #14879, the parent fix).
Context
Follow-up carved out of #14879 / PR #14882 (the honest-states fallback-cause fix). That PR made
renderDeclaredIntentFallbackattribute the measured cause viaclassifyFrontierEmptyCause, but wired the caller (GoldenPathSynthesizer.buildDeclaredIntentFallback) with only a verified binary "digested-history-exists" signal — enough to distinguishFRONTIER_UNANCHOREDfromCOLD_START(the operator's 2026-07-06 case), but NOT the exact REM backlog/cycle state.The Problem
classifyFrontierEmptyCausealready acceptsundigested+recentCycleCountand returnsREM_STALLEDwhen there is undigested work AND no recent cycle. But the caller cannot supply those honestly: V-B-A against the live graph DB (.neo-ai-data/sqlite/memory-core-graph.sqlite) showed the REM pipeline'sdigested/undigested/recentCyclesare not cleanly derivable from the SQLite node table ($.typeis null for ~143k nodes;summary%count = 622 ≠ the pipeline's ~1460digested). A naive count would ship a new wrong number — the exact failure #14879 fixed. SoREM_STALLEDcurrently never fires from the live caller (it degrades toFRONTIER_UNANCHORED/UNATTRIBUTED, which is honest but coarse).The Architectural Reality
The authoritative REM metrics live in the REM/consolidation subsystem (the source behind
get_rem_pipeline_state:{undigested, digested, recentCycles}), NOT the graphNodestable.buildDeclaredIntentFallbackis a static, cold-cache SQLite-sourced method — threading pipeline state in must respect ADR-0019 (readAiConfigat the use site; no new pass-along/threading antipattern) and the method's "never blow the scheduling budget" bound.The Fix
Give the caller a cheap, correct read of the REM pipeline state (
undigested+recentCycleCount) and pass them intoclassifyFrontierEmptyCause, so a genuineREM_STALLEDrenders honestly instead of collapsing toFRONTIER_UNANCHORED. Identify the in-process accessor the REM subsystem exposes (mirror of theget_rem_pipeline_stateMCP surface) rather than re-deriving from SQLite.Acceptance Criteria
buildDeclaredIntentFallbacksupplies measuredundigested+recentCycleCount(from the authoritative REM subsystem, not a graph-node count) toclassifyFrontierEmptyCause.REM_STALLED; a healthy-digestion-empty-anchor rendersFRONTIER_UNANCHORED— proven by a whitebox/integration test with both fixtures.AiConfigvalues, no re-derivation); the accessor read stays within the method's budget.Out of Scope
Related
Origin Session ID: 9360840f-5d7a-4680-8110-86877722735b Retrieval Hint:
query_raw_memories("goldenPathPickupBridge classifyFrontierEmptyCause REM_STALLED pipeline state plumbing")Live latest-open sweep: checked 2026-07-06; no equivalent found (only #14879, the parent fix).