Frontmatter
| title | fix(ai): bound REM parser model calls (#13984) |
| author | neo-gpt |
| state | Merged |
| createdAt | Jun 25, 2026, 3:59 AM |
| updatedAt | Jun 25, 2026, 10:36 AM |
| closedAt | Jun 25, 2026, 10:36 AM |
| mergedAt | Jun 25, 2026, 10:36 AM |
| branches | dev ← codex/13984-rem-parser-budget |
| url | https://github.com/neomjs/neo/pull/13986 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |

[update] Active REM parser diagnostics added on top of the output-budget fix.
What happened in the live failure:
- REM reached the marathon session at 2026-06-24T23:27:55.880Z.
- Raw payload: 500,586 chars, 504,892 bytes, /3 estimate 168,298 tokens, 192 raw turns.
- Tri-Vector chunking activated: 2 chunks, estimated 167,916 tokens.
- Chunk 1 prompt estimate: 99,179 tokens. It completed at 23:30:15.840Z.
- There was no following Graph entities committed line, no Tri-Vector Synthesis line, and no topology line.
- Chunk 2 prompt estimate: 70,549 tokens. This was the active generation while the local LMS token meter kept climbing past 204k.
Change added now:
SemanticGraphExtractorwrites boundedactive-rem-call.jsonbeforeprovider.generate()and clears it infinally.get_rem_pipeline_statenow returnsactiveCallwith session/chunk/turn/output-budget/context fields while a Tri-Vector call is streaming.- The MCP OpenAPI schema exposes
activeCallas a required nullable field.
Validation run locally:
git diff --checknpm run test-unit -- test/playwright/unit/ai/services/memory-core/helpers/remRunStateStore.spec.mjs— 11/11 passednpm run test-unit -- test/playwright/unit/ai/services/rem-observability.spec.mjs— 27/27 passednpm run test-unit -- test/playwright/unit/ai/mcp/server/memory-core/McpServerToolLimits.spec.mjs— 7/7 passednpm run test-unit -- test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs— 14/14 passed
Head is now 1aa67b8619e68e89273c5c38aaa5ee7febaa44e4.

Update after stacked P0 validation:
- Replaced
aiConfig.data.handoffFilePathtest shadow mutations inrem-observability.spec.mjswith the ADR-19 leafaiConfig.handoffFilePathTest. Production code reads the computedaiConfig.handoffFilePath; mutating the shadow path was brittle across same-worker test files. - Temp stacked proof before pushing:
GoldenPathSynthesizer.spec.mjs+rem-observability.spec.mjswent from one failure to63 passed. - Broader stacked proof with the P0 files passed:
94 passed. - Branch proof after commit:
rem-observability.spec.mjs->27 passed;SemanticGraphExtractor.spec.mjs+rem-observability.spec.mjs->41 passed.
CI is rerunning on 38357c0fb0.

PR Review Summary
Status: Request Changes
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Request Changes
- Rationale: The provider adapter direction is materially better than the earlier narrow cap attempt, but the PR does not yet prove or implement the ticket's scheduler-level close target. The live failure was a REM call monopolizing the chat model; closing #13984 requires the cap plus deterministic evidence that the failed session/chunk will not be re-served immediately.
Self-Review Opening: Self-review of #13984. I chose a provider-neutral maxCompletionTokens contract plus active REM call diagnostics because the ticket rejected a deterministic mini-summary bypass. The blind spot is that I scoped most proof around SemanticGraphExtractor and provider adapters while the ticket's acceptance criteria also bind DreamService re-serve behavior and exact-session reproduction.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: Ticket #13984, PR #13986 metadata at head
6a301fe60028d4ccc5c094176f4f2b76da5d852e, changed-file list, KB source-of-authority forSemanticGraphExtractor/ REM state /DreamPipeline.md, and currentDreamServicescheduler state code. - Expected Solution Shape: A correct fix must bound each REM graph provider call before dispatch and during streaming, expose active session/chunk diagnostics while in flight, and persist a scheduler state that prevents the same runaway chunk from being immediately selected again. It must not rely on LMS-only behavior, a wall-clock timeout, or a synthetic test that hardcodes the ticket's numbers without exercising the production chunking path.
- Patch Verdict: Partially matches. The provider adapter now maps
maxCompletionTokenstomax_tokens/num_predict, local streaming breaks at the generated-output cap and closes the response body, and active-call diagnostics are exposed. It contradicts the close target on scheduler exclusion and exact-session evidence. - Premise Coherence: Mixed. The implementation honors Verify-Before-Assert at the provider/diagnostics layer, but the PR body overstates the ticket closure because the re-serve and exact-session ACs are not empirically satisfied.
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #13984
- Related Graph Nodes:
SemanticGraphExtractor,DreamService.processUndigestedSessions,get_rem_pipeline_state,localModels.chat.graphOutputLimitTokens, REM parser output budget
🔬 Depth Floor
Challenge: The PR proves the cap is passed to the provider path, but it does not prove that the exact live failure shape is handled end-to-end. The strongest new test calls extractTriVectorPayload() directly with a one-sentence document and hardcoded ticket metadata, so it bypasses DreamService raw-turn hydration and executeTriVectorExtraction() chunk construction.
Rhetorical-Drift Audit:
- PR description: framing overshoots on “end to end” because scheduler exclusion is still max-attempt-gated.
- Anchor & Echo summaries: mostly precise on provider/diagnostic behavior.
-
[RETROSPECTIVE]tag: N/A. - Linked anchors: ticket #13984 is valid authority, but the PR does not yet meet all of it.
Findings: Rhetorical drift flagged in Required Actions.
🧠 Graph Ingestion Notes
[KB_GAP]: N/A.[TOOLING_GAP]: The expanded scheduler verification command includingtest/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjsfailed before the target assertions with unrelated module-export errors; focused PR tests passed.[RETROSPECTIVE]: Provider-level caps are necessary but not sufficient for REM liveness; the scheduler state is part of the parser safety contract.
🎯 Close-Target Audit
- Close-targets identified: #13984
- For #13984: confirmed not epic-labeled.
Findings: Close target is a valid leaf, but closure is overclaimed until AC1/AC4 are either implemented or explicitly removed from Resolves #13984.
📑 Contract Completeness Audit
- Originating ticket contains a Contract Ledger matrix.
- Implemented PR diff matches the Contract Ledger exactly.
Findings: Contract drift. The ticket requires over-budget/runaway chunks to be terminal for the current cadence and not immediately re-served. DreamService still writes digestState: 'undigested' until digestAttempts >= maxDigestAttempts, and findUndigestedSessions() re-selects rows whose digestState is undigested.
🪜 Evidence Audit
- PR body contains an
Evidence:declaration line. - Achieved evidence ≥ close-target required evidence, OR residuals are explicitly listed.
Findings: Evidence mismatch. The PR honestly says L3 runtime validation is still required, but #13984 AC1 asks for exact session/chunk evidence covered by a test or deterministic harness reproduction. The current synthetic test at test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs:399 does not reproduce the 500,586-char / 192-turn shape; it uses a one-sentence document at line 433 and hardcodes chunkTokens: 70549 at line 447.
📡 MCP-Tool-Description Budget Audit
Findings: Pass. The modified activeCall schema descriptions are terse, usage-focused, and do not add block-literal narrative.
🔗 Cross-Skill Integration Audit
Findings: Pass for the MCP surface; get_rem_pipeline_state schema and tests were updated. No new skill/workflow primitive was introduced.
🧪 Test-Execution & Location Audit
- Branch checked out locally at exact head
6a301fe60028d4ccc5c094176f4f2b76da5d852e. - Canonical Location: new/modified tests are under
test/playwright/unit/ai/...and MCP tests remain undertest/playwright/unit/ai/mcp/server/.... - Related verification run:
npm run test-unit -- test/playwright/unit/ai/provider/KeepAlive.spec.mjs test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs test/playwright/unit/ai/services/memory-core/helpers/remRunStateStore.spec.mjs test/playwright/unit/ai/services/rem-observability.spec.mjs test/playwright/unit/ai/mcp/server/memory-core/McpServerToolLimits.spec.mjs-> 83 passed, 1 skipped. - Expanded scheduler probe: same command plus
test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjs-> 99 passed, 5 failed before target scheduler assertions, 13 did not run. - Static checks:
git diff --check origin/dev...HEADpassed;node ai/scripts/lint/lint-config-template-ssot.mjspassed.
Findings: Focused tests pass, but the scheduler AC remains unverified and mechanically suspect from source review.
📋 Required Actions
To proceed with merging, please address the following:
- Fix the scheduler close-target drift: a
terminalForCadenceREM parser failure starting fromdigestAttemptsunset/0 still persists asdigestState: 'undigested'atai/daemons/orchestrator/services/DreamService.mjs:593-601, andfindUndigestedSessions()re-selectsundigestedrows atai/daemons/orchestrator/services/DreamService.mjs:154-157. Either persist an immediately excluded/resumable state for #13984 terminal parser failures, or stop claimingResolves #13984and split the scheduler AC into a follow-up. - Replace or supplement the synthetic marathon test with a deterministic production-path reproduction: hydrate the raw-turn shape through
DreamServiceorexecuteTriVectorExtraction(), build the chunk plan from actual turn documents, verifymaxCompletionTokenson the provider call, and verify the resulting terminal failure updates scheduler state so the same session is not immediately re-served. - Remove unrelated generated devindex/sitemap churn from the PR.
apps/devindex/resources/data/tracker.json,apps/devindex/resources/data/users.jsonl, andapps/portal/sitemap.xmladd 310 changed lines unrelated to REM parser safety. - Refresh the PR body to match the current head. It still lists test evidence at
1aa67b8and only the first two commits, while live head is6a301fe6with four commits.
📊 Evaluation Metrics
[ARCH_ALIGNMENT]: 70 - Provider-neutral output budgeting and active diagnostics fit the architecture, but scheduler exclusion remains outside the implemented close target.[CONTENT_COMPLETENESS]: 60 - JSDoc/config surfaces are present, but PR-body evidence and commit inventory are stale and the exact-session AC is overclaimed.[EXECUTION_QUALITY]: 55 - Focused tests pass; the main behavioral gap is unverified re-serve semantics plus synthetic evidence for the live failure shape.[PRODUCTIVITY]: 65 - The provider cap and diagnostics are useful progress, but not enough to close #13984 safely.[IMPACT]: 90 - This is P0 Agent OS reliability; a bad merge can leave REM monopolizing the chat model again.[COMPLEXITY]: 75 - Crosses provider adapters, graph extraction, MCP observability, config materialization, and scheduler state.[EFFORT_PROFILE]: Heavy Lift - High-impact reliability fix with multiple runtime boundaries and live-state evidence requirements.
This should not merge as-is. The core adapter work is promising, but #13984 is a scheduler-plus-runtime contract, not only a provider-option plumbing ticket.

Fixup pushed at def553f745.
Self-review response:
- [ADDRESSED] Scheduler close-target drift: provider-size terminal failures (
size-precheck-skip/context-overflow) now becomedigestState: 'undigestible'on the first classified failure instead of waiting formaxDigestAttempts; schema failures still use the retry threshold and ingestion failures remain retryable. - [ADDRESSED] Synthetic marathon test: replaced the one-sentence direct-call shape with a 192 raw-turn
executeTriVectorExtraction()production-path fixture that proves chunk planning, in-flight diagnostics, andmaxCompletionTokenspropagation. - [ADDRESSED] Generated churn: devindex and sitemap changes are removed from the final PR diff.
- [ADDRESSED] Stale PR body: refreshed for head
def553f745and current evidence.
Additional operator-caught corrections:
- Removed the public MCP/openapi/get_rem_pipeline_state active-call expansion. A bug fix here does not warrant a new MCP contract.
- Removed the provider-local
TOKEN_ESTIMATE_BYTES_PER_TOKEN = 3heuristic. Output bounding now uses the native provider request cap (max_tokens/num_predict) plus upstreamfinish_reasonmetadata; the existing shared consumer-friction estimator remains only the prompt-side guardrail. - Corrected the failure theory: the observed 400k+ LM Studio counter is generated-output runaway, not something explained by the two input chunks. Chunking is still tested because it must preserve the exact raw-turn path, but the close target is the provider output cap plus scheduler non-re-serve semantics.
Verification after the fixup:
node --checkpassed for the affected provider/parser/run-state/DreamService files and their focused tests.node ai/scripts/lint/lint-config-template-ssot.mjs-> OK.git diff --checkandgit diff --cached --check-> clean.npm run test-unit -- test/playwright/unit/ai/provider/KeepAlive.spec.mjs test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs test/playwright/unit/ai/services/memory-core/helpers/remRunStateStore.spec.mjs --reporter=line-> 48 passed, 1 skipped.npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjs -g "bounds the re-serve immediately" --reporter=line-> 1 passed.

No review body provided.
Resolves #13984
Bounds REM Tri-Vector model calls at the provider request boundary and at the scheduler re-serve boundary. The live
198k/204k+and later400k+LM Studio counter is treated as runaway generated output, not as evidence that input chunking alone explains the failure. Even if both input chunks were selected correctly, an uncapped structured-output generation can still monopolize the chat model; this PR now targets that boundary directly.The implementation adds a tracked
graphOutputLimitTokensAiConfig leaf, passes a provider-neutralmaxCompletionTokensbudget into REM graph calls, maps it to LMS/OpenAI-compatiblemax_tokensand Ollamanum_predict, preserves upstream streamingfinish_reasonmetadata, fails before dispatch when prompt estimate plus output reserve exceeds the model context, writes internal in-flight parser diagnostics to the REM run-state directory/logs, and marks provider-size parser failuresundigestibleimmediately so the same pathological session is not re-served on the next cadence.Evidence: L2 unit/static passed -> L3 runtime validation still required after merge, config overlay migration, and orchestrator/harness restart against the live REM backlog.
Deltas from ticket
No compact deterministic fallback was added. REM graph extraction remains model-based; the fix bounds the parser path instead of bypassing it.
No public MCP/openapi/get_rem_pipeline_state contract change is included. The active diagnostic added here is an internal run-state/log marker for this parser bug, not a new operator-facing MCP surface.
No provider-local bytes/token estimator was added. The output budget is enforced through native provider request options (
max_tokens/num_predict) and upstream finish metadata, while the existing shared consumer-friction estimator remains the prompt-side guardrail.The exact session shape is represented by a deterministic 192 raw-turn production-path fixture through
executeTriVectorExtraction(). That test proves chunk planning, in-flight diagnostics, and provider option propagation; it deliberately does not claim that chunking explains the 400k generated-output symptom.ADR-19 cleanup after self-review: the REM parser and DreamService no longer use dynamic AiConfig helper indirection for touched leaves. They read concrete AiConfig leaves at the use site, and the focused SemanticGraphExtractor tests use
setEnvOverride()instead of direct config-object mutation.REM V-B-A correction after self-review:
graphOutputLimitTokensis the structured JSON completion cap, not the session bundle size. The 30-50k range belongs to the prompt side, so this PR addsgraphChunkLimitTokenswith a 50000-token default and clamps each effective chunk budget tomin(graphChunkLimitTokens, safeProcessingLimitTokens, contextLimitTokens - graphOutputLimitTokens)before subtracting the prompt envelope.CI fix after the unit run failed: the removed DreamService helper had been an ADR-19 violation, but its fail-loud semantics were still correct. DreamService now validates malformed concrete AiConfig leaves at the use sites instead of silently coercing
NaNinto an empty REM batch.Rebased onto current
devafter generated content sync landed there; the PR file list no longer containsapps/paths.Test Evidence
node --check ai/provider/OpenAiCompatible.mjsnode --check ai/provider/Ollama.mjsnode --check ai/services/graph/SemanticGraphExtractor.mjsnode --check ai/services/memory-core/helpers/remRunStateStore.mjsnode --check ai/daemons/orchestrator/services/DreamService.mjsnode --check test/playwright/unit/ai/provider/KeepAlive.spec.mjsnode --check test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjsnode --check test/playwright/unit/ai/services/memory-core/helpers/remRunStateStore.spec.mjsnode --check test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjsnode ai/scripts/lint/lint-config-template-ssot.mjs-> OK.git diff --checkandgit diff --cached --check-> clean.npm run test-unit -- test/playwright/unit/ai/provider/KeepAlive.spec.mjs test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs test/playwright/unit/ai/services/memory-core/helpers/remRunStateStore.spec.mjs --reporter=line-> 48 passed, 1 skipped before the final dev rebase.npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjs -g "bounds the re-serve immediately" --reporter=line-> 1 passed before the final dev rebase.git diff --check origin/dev...HEAD-> clean after rebase.git diff --name-only origin/dev...HEAD | rg '^apps/' || true-> noapps/paths after rebase.gh pr diff 13986 --name-only-> noapps/paths after force-push.npm run agent-preflight -- ... --pr-body /private/tmp/pr-13986-body-updated.md-> passed after rebase; emitted mechanical alignment committed as2ae804f057.rg -n "readRequiredChatNumberLeaf|readRequiredNumberLeaf|(?:AiConfig|aiConfig)\[[^\]]+\]|localModels\.chat\[[^\]]+\]" ai/services/graph/SemanticGraphExtractor.mjs ai/daemons/orchestrator/services/DreamService.mjs test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs-> no matches after ADR-19 cleanup.rg -n "(?:aiConfig|AiConfig)\.[^\n=]+=[^=]" ai/services/graph/SemanticGraphExtractor.mjs ai/daemons/orchestrator/services/DreamService.mjs test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs-> no direct config mutations after ADR-19 cleanup.node --check ai/services/graph/SemanticGraphExtractor.mjs,node --check ai/daemons/orchestrator/services/DreamService.mjs, andnode --check test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs-> passed after ADR-19 cleanup.node ai/scripts/lint/lint-config-template-ssot.mjs-> OK after ADR-19 cleanup.npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjs -g "bounds the re-serve immediately" --reporter=line-> 1 passed after ADR-19 cleanup.npm run test-unit -- test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs -g "REM marathon|prompt plus output reserve|configured graph-provider model|chunk-aware Tri-Vector" --reporter=line-> 6 passed after ADR-19 cleanup.npm run agent-preflight -- ... --pr-body /private/tmp/pr-13986-body-updated.md-> passed after ADR-19 cleanup.node ./ai/scripts/setup/initServerConfigs.mjs --migrate-config-> refreshed ignored local config overlays forgraphChunkLimitTokensvalidation.node --input-type=module -e "... cfg.localModels.chat ..."-> confirmed local validation config reportscontext=131072,safe=100000,graphOutput=8192,graphChunk=50000.npm run test-unit -- test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs -g "REM marathon|prompt plus output reserve|configured graph-provider model|chunk-aware Tri-Vector" --reporter=line-> 7 passed after graph chunk-budget correction.npm run agent-preflight -- ... --pr-body /private/tmp/pr-13986-body-updated.md-> passed after graph chunk-budget correction.28155897855/ job83384292532: stale behavior exposed byDreamService.spec.mjsline 619 after helper removal.npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjs -g "findUndigestedSessions fails loud when remSleepBatchLimit is malformed" --reporter=line-> 1 passed after restoring concrete-leaf fail-loud validation.npm run test-unit -- test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjs --reporter=line-> 34 passed after restoring concrete-leaf fail-loud validation.npm run test-unit -- test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs -g "REM marathon|prompt plus output reserve|configured graph-provider model|chunk-aware Tri-Vector" --reporter=line-> 7 passed after CI fix.npm run agent-preflight -- ... --pr-body /private/tmp/pr-13986-body-updated.md-> passed after CI fix; aligned 3 DreamService lines.Post-Merge Validation
dev, runnode ./ai/scripts/setup/initServerConfigs.mjs --migrate-config, then restart orchestrator and harness.2d993feb-ea2f-4468-8fbd-c53e62365f4dgenerate beyond the configured parser output cap.Commits
05f3ebdef0-fix(ai): bound REM parser model calls (#13984)8bc6209b4a-fix(ai): expose active REM parser diagnostics (#13984)9e31c9ec3c-test(ai): align REM handoff config mutation (#13984)da4835802e-fix(ai): narrow REM parser budget fix (#13984)2ae804f057-chore(ai): align REM parser formatting (#13984)0d2622bfde-fix(ai): remove REM parser AiConfig indirection (#13984)000ddbe246-fix(ai): separate REM graph prompt chunk budget (#13984)0cdb02b388-fix(ai): restore DreamService config fail-loud checks (#13984)Authored by Euclid (GPT-5, Codex Desktop). Session 019ef378-527d-7393-bc74-ec3a1d3f2ddf.