LearnNewsExamplesServices
Frontmatter
titlefix(ai): bound REM parser model calls (#13984)
authorneo-gpt
stateMerged
createdAtJun 25, 2026, 3:59 AM
updatedAtJun 25, 2026, 10:36 AM
closedAtJun 25, 2026, 10:36 AM
mergedAtJun 25, 2026, 10:36 AM
branchesdevcodex/13984-rem-parser-budget
urlhttps://github.com/neomjs/neo/pull/13986
contentTrust
projected
quarantined0
signals[]
Merged
neo-gpt
neo-gpt commented on Jun 25, 2026, 3:59 AM

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 later 400k+ 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 graphOutputLimitTokens AiConfig leaf, passes a provider-neutral maxCompletionTokens budget into REM graph calls, maps it to LMS/OpenAI-compatible max_tokens and Ollama num_predict, preserves upstream streaming finish_reason metadata, 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 failures undigestible immediately 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: graphOutputLimitTokens is the structured JSON completion cap, not the session bundle size. The 30-50k range belongs to the prompt side, so this PR adds graphChunkLimitTokens with a 50000-token default and clamps each effective chunk budget to min(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 NaN into an empty REM batch.

Rebased onto current dev after generated content sync landed there; the PR file list no longer contains apps/ paths.

Test Evidence

  • node --check ai/provider/OpenAiCompatible.mjs
  • node --check ai/provider/Ollama.mjs
  • node --check ai/services/graph/SemanticGraphExtractor.mjs
  • node --check ai/services/memory-core/helpers/remRunStateStore.mjs
  • node --check ai/daemons/orchestrator/services/DreamService.mjs
  • node --check test/playwright/unit/ai/provider/KeepAlive.spec.mjs
  • node --check test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs
  • node --check test/playwright/unit/ai/services/memory-core/helpers/remRunStateStore.spec.mjs
  • node --check test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjs
  • node ai/scripts/lint/lint-config-template-ssot.mjs -> OK.
  • git diff --check and git 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 -> no apps/ paths after rebase.
  • gh pr diff 13986 --name-only -> no apps/ paths after force-push.
  • npm run agent-preflight -- ... --pr-body /private/tmp/pr-13986-body-updated.md -> passed after rebase; emitted mechanical alignment committed as 2ae804f057.
  • 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, and node --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 for graphChunkLimitTokens validation.
  • node --input-type=module -e "... cfg.localModels.chat ..." -> confirmed local validation config reports context=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.
  • CI unit failure diagnosed from run 28155897855 / job 83384292532: stale behavior exposed by DreamService.spec.mjs line 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

  • Pull dev, run node ./ai/scripts/setup/initServerConfigs.mjs --migrate-config, then restart orchestrator and harness.
  • Verify REM no longer lets session 2d993feb-ea2f-4468-8fbd-c53e62365f4d generate beyond the configured parser output cap.
  • Verify internal REM diagnostics identify the session id, chunk index, provider, model, prompt estimate, output cap, and started-at timestamp while a chunk is in flight.
  • Verify the failed provider-size parser session is not immediately re-served on the next REM cadence.
  • Verify Golden Path can run after REM no longer monopolizes the chat model.

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.

neo-gpt
neo-gpt commented on Jun 25, 2026, 4:42 AM

[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:

  • SemanticGraphExtractor writes bounded active-rem-call.json before provider.generate() and clears it in finally.
  • get_rem_pipeline_state now returns activeCall with session/chunk/turn/output-budget/context fields while a Tri-Vector call is streaming.
  • The MCP OpenAPI schema exposes activeCall as a required nullable field.

Validation run locally:

  • git diff --check
  • npm run test-unit -- test/playwright/unit/ai/services/memory-core/helpers/remRunStateStore.spec.mjs — 11/11 passed
  • npm run test-unit -- test/playwright/unit/ai/services/rem-observability.spec.mjs — 27/27 passed
  • npm run test-unit -- test/playwright/unit/ai/mcp/server/memory-core/McpServerToolLimits.spec.mjs — 7/7 passed
  • npm run test-unit -- test/playwright/unit/ai/services/graph/SemanticGraphExtractor.spec.mjs — 14/14 passed

Head is now 1aa67b8619e68e89273c5c38aaa5ee7febaa44e4.


neo-gpt
neo-gpt commented on Jun 25, 2026, 6:46 AM

Update after stacked P0 validation:

  • Replaced aiConfig.data.handoffFilePath test shadow mutations in rem-observability.spec.mjs with the ADR-19 leaf aiConfig.handoffFilePathTest. Production code reads the computed aiConfig.handoffFilePath; mutating the shadow path was brittle across same-worker test files.
  • Temp stacked proof before pushing: GoldenPathSynthesizer.spec.mjs + rem-observability.spec.mjs went from one failure to 63 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.


neo-gpt
neo-gpt COMMENTED reviewed on Jun 25, 2026, 8:54 AM

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 for SemanticGraphExtractor / REM state / DreamPipeline.md, and current DreamService scheduler 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 maxCompletionTokens to max_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 including test/playwright/unit/ai/daemons/orchestrator/services/DreamService.spec.mjs failed 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 under test/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...HEAD passed; node ai/scripts/lint/lint-config-template-ssot.mjs passed.

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 terminalForCadence REM parser failure starting from digestAttempts unset/0 still persists as digestState: 'undigested' at ai/daemons/orchestrator/services/DreamService.mjs:593-601, and findUndigestedSessions() re-selects undigested rows at ai/daemons/orchestrator/services/DreamService.mjs:154-157. Either persist an immediately excluded/resumable state for #13984 terminal parser failures, or stop claiming Resolves #13984 and 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 DreamService or executeTriVectorExtraction(), build the chunk plan from actual turn documents, verify maxCompletionTokens on 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, and apps/portal/sitemap.xml add 310 changed lines unrelated to REM parser safety.
  • Refresh the PR body to match the current head. It still lists test evidence at 1aa67b8 and only the first two commits, while live head is 6a301fe6 with 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.


neo-gpt
neo-gpt commented on Jun 25, 2026, 9:12 AM

Fixup pushed at def553f745.

Self-review response:

  • [ADDRESSED] Scheduler close-target drift: provider-size terminal failures (size-precheck-skip / context-overflow) now become digestState: 'undigestible' on the first classified failure instead of waiting for maxDigestAttempts; 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, and maxCompletionTokens propagation.
  • [ADDRESSED] Generated churn: devindex and sitemap changes are removed from the final PR diff.
  • [ADDRESSED] Stale PR body: refreshed for head def553f745 and 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 = 3 heuristic. Output bounding now uses the native provider request cap (max_tokens / num_predict) plus upstream finish_reason metadata; 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 --check passed 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 --check and git 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.

tobiu
tobiu APPROVED reviewed on Jun 25, 2026, 10:11 AM

No review body provided.