feat(session): persist /goal directives across compaction and resume#695
Conversation
…d resume A /goal <text> directive (or a goal: / objective: marker) was never preserved: extractIntent stored only a coarse mode (investigate/implement) and discarded the goal text, so the objective was lost across compaction and --resume. The resume snapshot restored only <intent mode=...>. Capture the goal text as a first-class 'goal' event (priority 1) and restore the most recent one verbatim in a <session_goal> section placed first in the resume snapshot, so the resuming model reads its active objective before anything else and keeps working toward it. - extract: extractGoal() detects /goal <text> / goal: / objective: - analytics: 'goal' label 'Session goal'; 'intent' relabeled 'Session intent' (it stores a mode, not the goal text) - snapshot: buildGoalSection() -> <session_goal>, assembled right after how_to_search Verified: new tests/session/session-goal.test.ts (7 cases) + full suite (3700 pass) + build (tsc/bundle/asserts). End-to-end: a fresh agent given only the restored snapshot correctly identified the goal and derived a next step toward it with no prompting. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
src↔bundle drift from the goal-persistence change. Normally CI regenerates bundles on merge; included here so the full diff is reviewable. This PR carries the drift label / is opened as draft and must NOT be merged as-is. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Goal persistence now uses the DB's actual priority ordering, where higher numeric values survive eviction. The new regression stores a goal, fills the session event cap with lower-priority events, and asserts the objective remains available for resume snapshots. Constraint: PR mksglu#695 fixes goal continuity across compaction and resume, so the goal event must survive long sessions before snapshot restore. Rejected: Keep priority 1 as critical | existing SessionDB eviction deletes lower numeric priorities first. Confidence: high Scope-risk: narrow Directive: Keep goal priority aligned with SessionDB eviction semantics unless the whole priority scale is migrated. Tested: npx vitest run tests/session/session-goal.test.ts --reporter=dot; npx vitest run tests/session/session-db.test.ts --reporter=dot; npx vitest run tests/session/session-snapshot.test.ts --reporter=dot; npx tsc --noEmit; npm run build; git diff --check
Codex goal persistence now has an integration test that runs the real Codex UserPromptSubmit, PreCompact, and SessionStart compact hooks. The test verifies that a slash /goal directive is restored in additionalContext as <session_goal> after compaction. Constraint: PR mksglu#695 specifically targets Codex slash goal continuity across compact/resume, not only pure extractor/snapshot units. Rejected: Rely on extractor-only coverage | it does not prove the Codex hook lifecycle preserves the slash command. Confidence: high Scope-risk: narrow Directive: Keep this test tied to Codex hooks so future hook-path drift cannot silently drop /goal. Tested: npx vitest run tests/hooks/codex-goal-compact.test.ts tests/session/session-goal.test.ts tests/session/session-db.test.ts tests/session/session-snapshot.test.ts --reporter=dot; npx tsc --noEmit; git diff --check
Goal persistence now uses the DB's actual priority ordering, where higher numeric values survive eviction. The new regression stores a goal, fills the session event cap with lower-priority events, and asserts the objective remains available for resume snapshots. Constraint: PR mksglu#695 fixes goal continuity across compaction and resume, so the goal event must survive long sessions before snapshot restore. Rejected: Keep priority 1 as critical | existing SessionDB eviction deletes lower numeric priorities first. Confidence: high Scope-risk: narrow Directive: Keep goal priority aligned with SessionDB eviction semantics unless the whole priority scale is migrated. Tested: npx vitest run tests/session/session-goal.test.ts --reporter=dot; npx vitest run tests/session/session-db.test.ts --reporter=dot; npx vitest run tests/session/session-snapshot.test.ts --reporter=dot; npx tsc --noEmit; npm run build; git diff --check
Codex goal persistence now has an integration test that runs the real Codex UserPromptSubmit, PreCompact, and SessionStart compact hooks. The test verifies that a slash /goal directive is restored in additionalContext as <session_goal> after compaction. Constraint: PR mksglu#695 specifically targets Codex slash goal continuity across compact/resume, not only pure extractor/snapshot units. Rejected: Rely on extractor-only coverage | it does not prove the Codex hook lifecycle preserves the slash command. Confidence: high Scope-risk: narrow Directive: Keep this test tied to Codex hooks so future hook-path drift cannot silently drop /goal. Tested: npx vitest run tests/hooks/codex-goal-compact.test.ts tests/session/session-goal.test.ts tests/session/session-db.test.ts tests/session/session-snapshot.test.ts --reporter=dot; npx tsc --noEmit; git diff --check
a579219 to
8d5081f
Compare
|
Reach out to me via DM I would like to invite you our private engineering group on WhatsApp. Send me e-mail. @ken-jo |
|
Hi @mksglu, Claude Code and Codex verification are both complete on this PR. |
| * Without this, a `/goal` directive is lost across compaction/resume. | ||
| */ | ||
| const GOAL_DIRECTIVE_PATTERN = | ||
| /^(?:\/goal\s+|(?:goal|objective)\s*:\s*)(.+)$/is; |
There was a problem hiding this comment.
Good question — let me clarify the design intent and what I measured.
Marker keywords (goal / objective): these mirror only the goal keyword convention that Codex and Claude Code already support. I deliberately did not add per-language translations (目标 / 目標 / 목표 / …) as marker keywords — that felt like overfitting (which languages? simplified vs. traditional? where do we stop?).
Goal body (the text after the marker): this already supports any language — the capture group (.+) with the s flag is Unicode-agnostic. I verified it:
| Input | Result |
|---|---|
/goal 中文目标内容 (slash + Chinese text) |
✅ match, captured 中文目标内容 |
goal: 日本語の目標 (English keyword + Japanese text) |
✅ match |
objective: 한국어 목표 (English keyword + Korean text) |
✅ match |
目标: 提高性能 (Chinese marker keyword) |
❌ no match |
目標:… (Japanese keyword + fullwidth colon) |
❌ no match |
So CJK content is fully supported today; only the marker keyword is English-only. (Note: CJK input also tends to use the fullwidth colon : U+FF1A, which the current ASCII-: regex wouldn't match either — so marker i18n would mean both keywords and colon variants.)
Given that the body is already language-agnostic, do you still want the marker keywords themselves to be multilingual?
My suggestion: support only the language-neutral /goal slash command for now, and split per-language marker matching into a separate follow-up — implementing it later once the feature has stabilized and seen enough real-world usage, rather than guessing the language set up front. Happy to go either way — just let me know your preference.
Resolve bundle build conflicts by regenerating cli.bundle.mjs + server.bundle.mjs from merged source (esbuild) rather than hand-merging minified output. src/session/analytics.ts auto-merged cleanly: goal category labels + next's perf refactors coexist. Build green (assert-bundle OK x6, assert-asymmetric-drift OK); full suite 3716 passed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
What
A
/goal <text>directive, or explicitgoal:/objective:marker, was not preserved across compaction or resume.extractIntentkept only the coarse mode (investigate/implement) and discarded the actual objective, so restored context could say only<intent mode="implement"/>without saying what to implement.This PR makes the stated session goal first-class session memory so a resumed model can continue toward the user's objective without asking the user to restate it.
Change
src/session/extract.ts): addextractGoal()and wire it intoextractUserEvents(). It captures/goal <text>,goal: <text>, andobjective: <text>as agoalevent.goalatpriority: 4, matching the existingSessionDBeviction contract where higher numeric priority survives before lower-priority events.src/session/snapshot.ts): render the latest goal in a<session_goal>section immediately after<how_to_search>, before files, tasks, and<intent>.src/session/analytics.ts): labelgoalas "Session goal" and relabelintentas "Session intent".tests/hooks/codex-goal-compact.test.ts): run the actual Codex hook sequenceUserPromptSubmit -> PreCompact -> SessionStart(source=compact)and assert the slash/goalobjective is restored inhookSpecificOutput.additionalContext.server.bundle.mjs,cli.bundle.mjs,hooks/session-extract.bundle.mjs,hooks/session-snapshot.bundle.mjs).No behavior change to the coarse
intentmode.Codex Review Follow-Up
Codex review found that the original
priority: 1goal event would be evicted first at the per-session 1000-event cap becauseSessionDBremoves the lowest numeric priority first. That would break the core continuity claim for long Codex sessions before compaction.Follow-up changes:
4;/goalsurvives through compact restore when the prompt text reachesUserPromptSubmit.Verification
Local
npx vitest run tests/hooks/codex-goal-compact.test.ts tests/session/session-goal.test.ts tests/session/session-db.test.ts tests/session/session-snapshot.test.ts --reporter=dot— 4 files / 132 tests passed.npx tsc --noEmit— passed.npm run build— passed (tsc,bundle,assert-bundle,assert-asymmetric-drift).git diff --check— passed.Independent Codex Subagent Verification
A separate Codex subagent verified the goal behavior without editing files:
/goal,goal:, andObjective:creategoalevents withpriority: 4;<session_goal>is ordered before files/intent.A second Codex subagent independently exercised the Codex hook compact path:
tests/hooks/codex-goal-compact.test.tssuccessfully;hooks/codex/userpromptsubmit.mjs -> hooks/codex/precompact.mjs -> hooks/codex/sessionstart.mjs(source=compact)with a fakeCODEX_HOME;{ category: "goal", priority: 4 };additionalContextcontains<session_goal>, contains the objective, and orders goal before<intent>.Remote CI
test (ubuntu-latest)— success.test (macos-latest)— success.test (windows-latest)— success.openclaw-e2e (ubuntu-latest)— success.openclaw-e2e (macos-latest)— success.Constraints / Assumptions
/goal ...only when the host passes the submitted slash-command text toUserPromptSubmit. If a host consumes the slash command before hooks see it, context-mode cannot infer that exact text from this path.goal:andobjective:remain explicit marker forms for hosts or workflows where slash-command text is not available to hooks.priority: 4intentionally follows today'sSessionDBeviction contract. A future cleanup can introduce named priority constants to remove numeric ambiguity.nextand remains a draft while maintainers review the new behavior and test shape.Future Work
<session_goal>restore path.CRITICAL,HIGH, etc.) so future continuity-critical events cannot accidentally use the wrong eviction priority.Closes #694