-
Notifications
You must be signed in to change notification settings - Fork 0
fix: persist boot context in system prompt and sticky banner #369
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -229,6 +229,46 @@ describe('fetchBootContext', () => { | |
| expect(result.fullMarkdown).toBe('json fallback'); | ||
| }); | ||
|
|
||
| it('returns fullMarkdown suitable for system prompt append', async () => { | ||
| const markdown = '# Identity\nYou are a helpful assistant.\n\n# Preferences\nBe concise.'; | ||
| mockFetch.mockResolvedValueOnce({ | ||
| ok: true, | ||
| json: async () => ({ | ||
| boot: { | ||
| content: markdown, | ||
| tokens: 200, | ||
| tokenBudget: 8000, | ||
| sources: ['profile.md'], | ||
| }, | ||
| }), | ||
| }); | ||
|
|
||
| const result = await fetchBootContext('mitzo-conversational', CONTEXGIN_URL); | ||
|
|
||
| expect(result.fullMarkdown).toBe(markdown); | ||
| // Verify the append pattern matches what chat.ts does: | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔵 style: The test at lines 249-254 is tautological — it constructs a template string and then asserts it contains substrings that are obviously present. It doesn't test any production code path; it tests the test's own string interpolation. The meaningful assertion is on line 248 ( |
||
| // `\n\n# Boot Context\n${fullMarkdown}` | ||
| const append = result.fullMarkdown ? `\n\n# Boot Context\n${result.fullMarkdown}` : ''; | ||
| expect(append).toContain('# Boot Context'); | ||
| expect(append).toContain('# Identity'); | ||
| expect(append).toContain('Be concise.'); | ||
| }); | ||
|
|
||
| it('returns empty string for append when fullMarkdown is missing', async () => { | ||
| mockFetch.mockResolvedValueOnce({ | ||
| ok: true, | ||
| json: async () => ({ | ||
| boot: { tokens: 0, sources: [] }, | ||
| }), | ||
| }); | ||
|
|
||
| const result = await fetchBootContext('mitzo-conversational', CONTEXGIN_URL); | ||
|
|
||
| expect(result.fullMarkdown).toBeUndefined(); | ||
| const append = result.fullMarkdown ? `\n\n# Boot Context\n${result.fullMarkdown}` : ''; | ||
| expect(append).toBe(''); | ||
| }); | ||
|
|
||
| it('uses default URL from env when not provided', async () => { | ||
| const origUrl = process.env.CONTEXGIN_URL; | ||
| process.env.CONTEXGIN_URL = 'http://test-host:9999'; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -900,6 +900,26 @@ async function _startChatInner( | |
| // Load project hooks from .claude/settings.json (e.g. SessionStart boot context) | ||
| const hooks = loadProjectHooks(cwd); | ||
|
|
||
| // Fetch boot context BEFORE building system prompt so it's part of the | ||
| // system prompt append and survives SDK context compaction. | ||
| const bootContextMsg = await fetchBootContext(agentName); | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 unsafe_assumptions: Changing fetchBootContext from fire-and-forget to
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 regressions: Changing fetchBootContext from fire-and-forget to awaited adds up to 5 seconds (the AbortSignal.timeout) to session startup latency when ContexGin is slow or unreachable, plus additional time for the local fallback subprocess. The old code let the session start immediately while boot context loaded in the background. Consider whether this latency tradeoff is acceptable — the user will see a blank chat for longer on ContexGin timeouts. |
||
| const bootContextAppend = bootContextMsg.fullMarkdown | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 missing_tests: The new behavior — boot context markdown being appended to the system prompt — has no test coverage. The existing |
||
| ? `\n\n# Boot Context\n${bootContextMsg.fullMarkdown}` | ||
| : ''; | ||
|
|
||
| // Send boot context to UI for display (separate from system prompt injection). | ||
| // stateSessionId is the worktree-level session ID, already computed above. | ||
| send(transport, { ...bootContextMsg, ...(stateSessionId ? { sessionId: stateSessionId } : {}) }); | ||
|
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 regressions: The old fire-and-forget code looked up |
||
| // Cache in ManagedSession for replay on reconnect/switch | ||
| session.bootContext = bootContextMsg as unknown as Record<string, unknown>; | ||
| // Persist to EventStore for cold-path recovery | ||
| if (stateSessionId) { | ||
| eventStore.upsertSession({ | ||
| sessionId: stateSessionId, | ||
| bootContext: JSON.stringify(bootContextMsg), | ||
| }); | ||
| } | ||
|
|
||
| // Build the system prompt append string (used by both query and comparison) | ||
| const systemPromptAppend = | ||
| 'This is Mitzo, a mobile chat interface. The user is on their phone.\n' + | ||
|
|
@@ -908,28 +928,9 @@ async function _startChatInner( | |
| '- Keep responses concise — small screen.\n' + | ||
| '- Read CLAUDE.md and .cursor/rules/ for project context before doing substantive work.' + | ||
| buildWorktreeSystemPrompt(repoWorktrees) + | ||
| buildTaskPromptForSession(clientId); | ||
|
|
||
| // Fire-and-forget: fetch boot context from running ContexGin server | ||
| fetchBootContext(agentName) | ||
| .then((msg) => { | ||
| // Include sessionId for consistency with reconnect/switch replay paths | ||
| const sid = options.resume ?? registry.get(clientId)?.sessionId; | ||
| send(transport, { ...msg, ...(sid ? { sessionId: sid } : {}) }); | ||
| // Cache in ManagedSession for replay on reconnect/switch | ||
| const s = registry.get(clientId); | ||
| if (s) s.bootContext = msg as unknown as Record<string, unknown>; | ||
| // Persist to EventStore for cold-path recovery (resumed sessions | ||
| // may not run the query loop long enough for it to upsert). | ||
| if (sid) { | ||
| eventStore.upsertSession({ sessionId: sid, bootContext: JSON.stringify(msg) }); | ||
| } | ||
| }) | ||
| .catch((err: unknown) => { | ||
| log.warn('boot context fetch unexpected error', { | ||
| error: err instanceof Error ? err.message : String(err), | ||
| }); | ||
| }); | ||
| buildTaskPromptForSession(clientId) + | ||
| bootContextAppend; | ||
|
|
||
| capturePromptComparison(wtId, cwd, systemPromptAppend, repoWorktrees).catch(() => {}); | ||
|
|
||
| // Resolve SDK session UUID for resume — worktree IDs are not valid SDK session IDs | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔵 missing_tests: The two new tests reconstruct the append pattern inline and assert against it, but they only test
fetchBootContextreturn values — not thatchat.tsactually appends the result tosystemPromptAppend. The append logic in chat.ts (lines 906-908, 932) has no direct test coverage. An integration test that verifies the system prompt passed toquery()contains the boot context would be more valuable.[fixable]