Memory size recovery: September 14
Historical investigation: v0.0.30.0 supersedes the strict size-rejection policy below. See partial memory saves and warning recovery for current behavior.
Confirmed cause
Production database access used the explicit PRODUCTION_DATABASE_URL, a standalone pg client, BEGIN READ ONLY, a 30-second statement timeout and ROLLBACK. No production records or balances were changed. PostHog project: 318450; event: summary_job; system: session-memory.
The reported session (239f733d-fbae-481c-a95d-1a64c969c57a) still fails after the September 12 repair. At September 14 22:31 UTC its saved memory was 11,886 characters, its retry count was three, and it had 205 eligible pending replies (206 after the saved cursor, excluding the newest reply).
At 14:51 UTC, a one-exchange batch returned 12,585 characters twice, both with stop_reason=stop, using google/gemini-2.5-flash-lite. Earlier attempts also exhausted the output token limit. Events identify memory_revision=2026-09-12-soft-size-targets; these are fresh failures, not a stale error left by an old deployment.
The size recovery loop consumes both calls on the selected model. The alternate model is only reachable for empty/repetitive output. A size exception exits before that alternate can run. The same session demonstrates that the existing alternate path can help: on September 14 DeepSeek recovered empty/repetitive failures into accepted memories of 6,285 and 6,446 characters. That is supporting historical evidence, not a live validation of this candidate change.
History
| Change | Behavior |
|---|---|
July 9, f3e4cd2f6 | Plain text replaced strict JSON. The prompt requested up to 12k characters with a 4k output-token cap. Cut output was accepted as usable text; absence of an error did not establish complete retained memory. |
August 13, 9f54578c3 | Repetitive output gained an alternate-model retry. |
September 5, 63dd07563 | Reject token-truncated output, retain old memory; ask for 7k characters with 8,192 output tokens. |
September 6 UTC, 938cf240e | Share two calls, preserve pending coverage, enforce character limits; retry incorrectly used the smaller writing target as its acceptance ceiling. |
September 10, 76d885894 / e7c6d37fd | Stable acceptance ceiling, bounded pending batches and accepted-save billing fixed the first regression. |
September 12, beb0b00b4 / 66bcd1170 | Compress complete oversized drafts and accept complete text up to the existing 12k storage ceiling. Dense memory later grew to that ceiling again; the same-model retry still could not recover. |
Fix
Use the existing alternate-model resolver on the second size/truncation attempt, subject to the same BYOK and provider boundary as semantic recovery. When no allowed alternate exists, retain the selected model's shorter retry. Keep two caller generation attempts, the selected memory model setting, lag-one coverage, and accepted-save billing. Record usage and successful billing against the model that actually returned the accepted result.
Complete oversized drafts are compressed intact. Truncated drafts recover from the original evidence. Rejected recovery never advances coverage or charges an accepted-memory debit. No character cap was raised and no text is silently cut to make a test pass.
Memory calls also request disableReasoning: true, matching the existing summary caller. The custom provider already translates this into thinking: { type: "disabled" } for the official DeepSeek endpoint, after applying account body settings. Normal story chat settings are unaffected. The memory updater retains its two-generation-attempt budget and providers retain their existing empty-output and parameter-compatibility retries. Disabling those provider retries was reviewed and excluded because it would regress generic custom endpoints.
Deterministic reproduction
Base: remote testing d9feca2b2d0223c9d17bfb86effc621b6313a380, confirmed by its /health/release endpoint before changes. Run:
node packages/server/scripts/test-local.mjs src/lib/session-memory-billing.test.ts src/lib/session-memory-batch.test.ts src/lib/session-memory-prompts.test.ts src/lib/session-memory.test.ts src/lib/session-memory-retry.test.tsThe integration test invokes the actual retry entry point, provider resolver, persistence and billing code against isolated PGlite. Only model streams are replaced. A deterministic provider repeats a non-repetitive 12,585-character draft whenever the primary model is selected; the alternate returns complete continuity facts.
Before the fix, oversized and truncated cases remain failed and the alternate is never called. After the fix they save once, advance only the eligible coverage cursor, and bill only the accepted alternate usage. Tests also cover two failed attempts, a repetitive alternate, and a private provider that cannot use the alternate. The initial focused run passed 58 tests.
Initial candidate verification: complete server suite 1,847 passed, zero failed, one skipped (1,848 total); monorepo build 5/5 passed; monorepo typecheck 8/8 passed. Local logs are retained in D:/Yumina/yumina/.codex_tmp/memory-sept14-{red,green,server-suite,build-final,typecheck-final}.log. The untouched primary checkout's unrelated edits and merge conflicts were not modified.
This is a controlled reproduction on testing source, not a deployed live-model replay. Synthetic error rows or a healthy deployment must not be described as evidence of a real model failure or recovery.
Confirmed remote testing reproduction
The private synthetic fixture is session 1c95b7d4-7255-4131-a615-08060ff1a01a, world 085177fd-b731-40d2-9c61-47011ba46d31, on yumina-testing.up.railway.app. It started idle with no error, 11,876 characters of non-repetitive synthetic memory, and 30 new facts across three eligible exchanges. The latest reply is a separate excluded sentinel. No customer story was copied.
The operator ran the fixture and confirmed the failure, then explicitly authorized the testing push. A read-only testing DB query confirmed status failed, the reported size-budget error, retry count one, unchanged 11,876-character memory, and two session-memory usage records with 8,192 completion tokens each. The selected model was custom/deepseek-v4-flash; the configured provider endpoint was api.deepseek.com. This establishes a real live-model baseline. The earlier September 12 fixture's seeded Synthetic test state was not used as evidence.
This exposes a second defect in the memory caller: it omitted the reasoning-disable option already used by story compaction. The usage rows do not retain reasoning-token breakdowns, so they cannot establish how many tokens were thinking. The missing request option is established from code; existing provider request-body tests prove the native DeepSeek translation. A new actual-updater regression models output-cap exhaustion when that option is absent and succeeds when supplied. It failed before this change.
The fix was merged with testing base ea5ae38b7 before the final checks. No production mutation or production push is part of this rollout.
Post-deployment verification
Use a private synthetic dense-memory fixture owned by the test operator; do not publish or copy a customer's story into a shared testing world. Record the starting state before every attempt. Check summary_job by exact session and job ID, not just environment=production (testing also runs with production Node settings).
After deployment, retry the same fixture. For the custom DeepSeek case, verify native thinking is disabled, a complete memory is persisted, coverage advances, and the latest reply remains excluded. BYOK must not create an official-credit debit. No usable local credential can decrypt the testing account's active key, so a deployed end-to-end model replay requires the operator's authenticated retry; a healthy release endpoint alone is not proof of memory recovery.
For the official-model size failure, expect revision 2026-09-14-size-recovery-fallback: an initial oversized or truncated attempt, a size_retry naming the permitted alternate, then an accepted attempt and ok. Verify exactly one accepted-output debit using the alternate's usage log. If both models fail, old memory and coverage must remain intact and no successful-save debit may be made.
Story-summary output-limit failures also remain visible on this production session. This candidate is scoped to session memory; it does not claim to repair the separate story-compaction path.
