Skip to content

fix(memory-core): safe statistical fallback when dreaming narrative can't run#2

Merged
LightDriverCS merged 1 commit into
mainfrom
fix/dreaming-narrative-safe-fallback
Apr 18, 2026
Merged

fix(memory-core): safe statistical fallback when dreaming narrative can't run#2
LightDriverCS merged 1 commit into
mainfrom
fix/dreaming-narrative-safe-fallback

Conversation

@LightDriverCS
Copy link
Copy Markdown

Summary

Fixes openclaw/openclaw#68723 — the dreaming Diary was leaking raw memory content (including API tokens + session cookies) into user-visible DREAMS.md when the cron-dispatched sweep couldn't obtain a subagent runtime.

Root cause: the previous fallback at `dreaming-narrative.ts:130` returned the first non-empty memory snippet verbatim. So when the request-scoped subagent was unavailable (cron system-event code path), memory content became the "narrative."

Fix

`buildRequestScopedFallbackNarrative` now composes a short statistical note from phase counts only, never quoting snippet/promotion bodies:

Phase: rem · 8 fragments surfaced · 2 themes noted. The narrator was elsewhere this pass — details stayed in the recall store.

What breaks (and is intentional)

Two tests were asserting the old leaky behavior:

  • `falls back to a local narrative when subagent runtime is request-scoped` — updated to assert raw snippet is absent and the safe template is present
  • `falls back when the request-scoped runtime error is detected by stable code` — same

What's preserved

  • Happy-path runs still use the subagent-generated poetic narrative (the primary code path)
  • The spoofed-error skip case still writes nothing to DREAMS.md (security test)
  • Session cleanup runs regardless of fallback
  • No upstream code modified except the two files listed

Verified

`pnpm test:extension memory-core` — 486 passed / 3 skipped / 0 failed.

Related

🤖 Generated with Claude Code

…an't run

Replaces the request-scoped fallback in buildRequestScopedFallbackNarrative
so the dreaming Diary no longer leaks raw memory content. Previously when
the cron-dispatched sweep couldn't obtain a subagent runtime, the fallback
returned the first non-empty snippet verbatim — which meant arbitrary memory
fragments (including API tokens, session cookies, and pending-todo lists)
ended up as the user-visible diary entry for a given phase.

The new fallback composes a short note from phase statistics only:

  Phase: rem · 8 fragments surfaced · 2 themes noted. The narrator was
  elsewhere this pass — details stayed in the recall store.

No snippet bodies, promotion bodies, or theme labels are quoted. The entry
is stylistically minimal and cleanly signals that the proper narrator was
unavailable, so readers understand why the tone differs from
request-scoped (subagent-backed) entries.

Unrelated behavior preserved:
  - The spoofed-error skip case still writes nothing (test unchanged)
  - Happy-path runs still use the subagent-generated narrative
  - Session cleanup still runs regardless of fallback

Tests updated to assert the raw snippet is NOT present and the expected
phase/count markers ARE present.

Filed upstream as openclaw#68723 — offering this PR as a concrete
fix direction (option 3 from the report: richer local fallback).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@LightDriverCS LightDriverCS merged commit f251c01 into main Apr 18, 2026
1 check passed
LightDriverCS pushed a commit that referenced this pull request May 6, 2026
Two small Codex W3 findings:

#1 — Null-body guard. A JSON body of `null` (or a primitive/array)
would skip the LlmTurnWireRequest cast and throw on field access,
producing a 500 from the dispatch error handler instead of a clean
400. Added top-level object guard at the start of validateLlmTurnRequest
that returns invalid_field('<root>', 'request body must be a JSON object').
Renamed local var `body` → `wire` for the rest of the function so
TypeScript sees the narrowed type.

#2 — Fractional max_tokens. Original `Number.isFinite(x) && x > 0`
let `max_tokens: 0.5` pass and floor to a 0-token budget. Now require
`Number.isInteger(x) && x > 0`. Reason text updated to 'must be a positive integer'.

Tests: 14 → 16 (added fractional + non-object body cases).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LightDriverCS pushed a commit that referenced this pull request May 6, 2026
P1 #1 — thinking.budget_tokens < max_tokens constraint.

Anthropic rejects requests where thinking.budget_tokens >= max_tokens;
my budget map (high=16384, xhigh=32768) exceeded typical max_tokens=8192
which would 400 immediately. Fix: cap budget to (max_tokens - 1024)
reserving room for actual output. If the cap drops below Anthropic's
minimum thinking budget (1024), disable thinking entirely rather than
send an invalid request.

P1 #2 — ANTHROPIC_AUTH_TOKEN env var.

Anthropic SDK reads ANTHROPIC_AUTH_TOKEN for OAuth tokens and
ANTHROPIC_API_KEY for API keys. My resolver only checked
ANTHROPIC_API_KEY, so OAuth-only setups got 'no_anthropic_credential'.
Fix: read both, with ANTHROPIC_AUTH_TOKEN taking precedence (matches
SDK behavior). API_KEY-shaped OAuth tokens (sk-ant-oat*) still detected
and routed via authToken.

P2 (deferred) — `as never` casts. Codex flagged that imports of SDK
param types would catch shape bugs (e.g. Tool.InputSchema requires
`type: "object"`). Validation layer already enforces shape, but
upgrading to proper SDK types is a follow-up — not landing this commit
to keep the patch focused.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LightDriverCS added a commit that referenced this pull request May 6, 2026
* feat(gateway): W3 scaffold for /v1/llm_turn cloud-brain endpoint

Phase 1B W3 of BenchAGI ADR-0002. Route registration + body validation +
auth check for the cloud-brain dispatch target. Anthropic call is TODO
(returns 501 so relay claim path can land first).

Also: minor lint-fix in extensions/claude-code-bridge/serve.mjs to
unblock the repo-wide pre-commit hook (oxlint curly-rule on a pre-
existing single-line return). Out of W3 scope but required for commit.

Spec: ~/.openclaw/wiki/main/_boards/specs/phase-1a-design-gate-2026-05-05-v2.md §7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(gateway-w3): address Codex GO-WITH-PATCHES feedback

Two small Codex W3 findings:

#1 — Null-body guard. A JSON body of `null` (or a primitive/array)
would skip the LlmTurnWireRequest cast and throw on field access,
producing a 500 from the dispatch error handler instead of a clean
400. Added top-level object guard at the start of validateLlmTurnRequest
that returns invalid_field('<root>', 'request body must be a JSON object').
Renamed local var `body` → `wire` for the rest of the function so
TypeScript sees the narrowed type.

#2 — Fractional max_tokens. Original `Number.isFinite(x) && x > 0`
let `max_tokens: 0.5` pass and floor to a 0-token budget. Now require
`Number.isInteger(x) && x > 0`. Reason text updated to 'must be a positive integer'.

Tests: 14 → 16 (added fractional + non-object body cases).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gateway-w3): wire real Anthropic call (env-credential MVP)

Replaces the 501 stub with an actual Anthropic SDK call. Resolves the
credential from process.env.ANTHROPIC_API_KEY, distinguishing OAuth
tokens (sk-ant-oat*) from API keys, and surfaces the resolved profile
name back as 'used_auth_profile' so the cloud orchestrator (W2) can
decide between OAuth-mode (no aiUsageRecords) and API/coin-mode
(write aiUsageRecords) per spec §6.

NEW IN llm-turn-http.ts:

- callAnthropicForLlmTurn(request) — translates camelCase request to
  Anthropic SDK params. Handles cache_control on system prompt
  (ephemeral), thinking_level → thinking config with conservative
  budget_tokens (low=4096, medium=8192, high=16384, xhigh=32768).
  Calls messages.create, returns wire-format response (snake_case).

- resolveAnthropicCredential() — env-based credential resolution.
  Reports 'env-api-key' or 'env-oauth-token' as the profile name.

- LlmTurnWireResponse type — explicit snake_case shape per spec §7
  line 741.

ERROR PATHS:
- 'no_anthropic_credential' (500) — env not set
- 'anthropic_auth_failed' (401/403) — credential rejected
- 'anthropic_call_failed' (502 default) — SDK error

EXPLICIT NEXT-ITERATION work captured inline:

- Real auth-profile-machinery integration (loads the agent's
  configured profile via OpenClaw's auth-profile store, handles OAuth
  refresh, etc.). The env approach gets Cory's local OpenClaw to a
  working smoke test path; production-grade auth-profile resolution
  is the follow-up.

- Idempotency-key write-ahead store (lease-recovery semantics per
  spec §7 line 851).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(gateway-w3): address Codex Anthropic-call P1 feedback (2 fixes)

P1 #1 — thinking.budget_tokens < max_tokens constraint.

Anthropic rejects requests where thinking.budget_tokens >= max_tokens;
my budget map (high=16384, xhigh=32768) exceeded typical max_tokens=8192
which would 400 immediately. Fix: cap budget to (max_tokens - 1024)
reserving room for actual output. If the cap drops below Anthropic's
minimum thinking budget (1024), disable thinking entirely rather than
send an invalid request.

P1 #2 — ANTHROPIC_AUTH_TOKEN env var.

Anthropic SDK reads ANTHROPIC_AUTH_TOKEN for OAuth tokens and
ANTHROPIC_API_KEY for API keys. My resolver only checked
ANTHROPIC_API_KEY, so OAuth-only setups got 'no_anthropic_credential'.
Fix: read both, with ANTHROPIC_AUTH_TOKEN taking precedence (matches
SDK behavior). API_KEY-shaped OAuth tokens (sk-ant-oat*) still detected
and routed via authToken.

P2 (deferred) — `as never` casts. Codex flagged that imports of SDK
param types would catch shape bugs (e.g. Tool.InputSchema requires
`type: "object"`). Validation layer already enforces shape, but
upgrading to proper SDK types is a follow-up — not landing this commit
to keep the patch focused.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cory Shelton <coryshelton@Corys-Mac-mini.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LightDriverCS added a commit that referenced this pull request May 6, 2026
* feat(gateway): W3 scaffold for /v1/llm_turn cloud-brain endpoint

Phase 1B W3 of BenchAGI ADR-0002. Route registration + body validation +
auth check for the cloud-brain dispatch target. Anthropic call is TODO
(returns 501 so relay claim path can land first).

Also: minor lint-fix in extensions/claude-code-bridge/serve.mjs to
unblock the repo-wide pre-commit hook (oxlint curly-rule on a pre-
existing single-line return). Out of W3 scope but required for commit.

Spec: ~/.openclaw/wiki/main/_boards/specs/phase-1a-design-gate-2026-05-05-v2.md §7

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(gateway-w3): address Codex GO-WITH-PATCHES feedback

Two small Codex W3 findings:

#1 — Null-body guard. A JSON body of `null` (or a primitive/array)
would skip the LlmTurnWireRequest cast and throw on field access,
producing a 500 from the dispatch error handler instead of a clean
400. Added top-level object guard at the start of validateLlmTurnRequest
that returns invalid_field('<root>', 'request body must be a JSON object').
Renamed local var `body` → `wire` for the rest of the function so
TypeScript sees the narrowed type.

#2 — Fractional max_tokens. Original `Number.isFinite(x) && x > 0`
let `max_tokens: 0.5` pass and floor to a 0-token budget. Now require
`Number.isInteger(x) && x > 0`. Reason text updated to 'must be a positive integer'.

Tests: 14 → 16 (added fractional + non-object body cases).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(gateway-w3): wire real Anthropic call (env-credential MVP)

Replaces the 501 stub with an actual Anthropic SDK call. Resolves the
credential from process.env.ANTHROPIC_API_KEY, distinguishing OAuth
tokens (sk-ant-oat*) from API keys, and surfaces the resolved profile
name back as 'used_auth_profile' so the cloud orchestrator (W2) can
decide between OAuth-mode (no aiUsageRecords) and API/coin-mode
(write aiUsageRecords) per spec §6.

NEW IN llm-turn-http.ts:

- callAnthropicForLlmTurn(request) — translates camelCase request to
  Anthropic SDK params. Handles cache_control on system prompt
  (ephemeral), thinking_level → thinking config with conservative
  budget_tokens (low=4096, medium=8192, high=16384, xhigh=32768).
  Calls messages.create, returns wire-format response (snake_case).

- resolveAnthropicCredential() — env-based credential resolution.
  Reports 'env-api-key' or 'env-oauth-token' as the profile name.

- LlmTurnWireResponse type — explicit snake_case shape per spec §7
  line 741.

ERROR PATHS:
- 'no_anthropic_credential' (500) — env not set
- 'anthropic_auth_failed' (401/403) — credential rejected
- 'anthropic_call_failed' (502 default) — SDK error

EXPLICIT NEXT-ITERATION work captured inline:

- Real auth-profile-machinery integration (loads the agent's
  configured profile via OpenClaw's auth-profile store, handles OAuth
  refresh, etc.). The env approach gets Cory's local OpenClaw to a
  working smoke test path; production-grade auth-profile resolution
  is the follow-up.

- Idempotency-key write-ahead store (lease-recovery semantics per
  spec §7 line 851).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(gateway-w3): address Codex Anthropic-call P1 feedback (2 fixes)

P1 #1 — thinking.budget_tokens < max_tokens constraint.

Anthropic rejects requests where thinking.budget_tokens >= max_tokens;
my budget map (high=16384, xhigh=32768) exceeded typical max_tokens=8192
which would 400 immediately. Fix: cap budget to (max_tokens - 1024)
reserving room for actual output. If the cap drops below Anthropic's
minimum thinking budget (1024), disable thinking entirely rather than
send an invalid request.

P1 #2 — ANTHROPIC_AUTH_TOKEN env var.

Anthropic SDK reads ANTHROPIC_AUTH_TOKEN for OAuth tokens and
ANTHROPIC_API_KEY for API keys. My resolver only checked
ANTHROPIC_API_KEY, so OAuth-only setups got 'no_anthropic_credential'.
Fix: read both, with ANTHROPIC_AUTH_TOKEN taking precedence (matches
SDK behavior). API_KEY-shaped OAuth tokens (sk-ant-oat*) still detected
and routed via authToken.

P2 (deferred) — `as never` casts. Codex flagged that imports of SDK
param types would catch shape bugs (e.g. Tool.InputSchema requires
`type: "object"`). Validation layer already enforces shape, but
upgrading to proper SDK types is a follow-up — not landing this commit
to keep the patch focused.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cory Shelton <coryshelton@Corys-Mac-mini.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

memory-core: cron-fired dreaming writes raw memory snippets to DREAMS.md because subagent runtime is request-scoped

1 participant