From 290303c14e8c45c109c2805ff645c9584b48ccbe Mon Sep 17 00:00:00 2001 From: William Valentin Date: Thu, 19 Feb 2026 11:58:42 -0800 Subject: [PATCH] feat(memory): add daily log continuity controls --- README.md | 5 +- SOUL.md | 2 + docs/plans/2026-02-15-openclaw-gap-roadmap.md | 7 +-- docs/plans/state.json | 29 +++++++++++ src/backends/native/orchestrator.test.ts | 50 +++++++++++++++++++ src/backends/native/orchestrator.ts | 24 ++++++++- src/config/schema.test.ts | 9 ++++ src/config/schema.ts | 3 ++ src/daemon/routing.ts | 3 ++ src/gateway/session-bridge.ts | 3 ++ 10 files changed, 129 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 2cf658a..c833efe 100644 --- a/README.md +++ b/README.md @@ -1125,6 +1125,9 @@ memory: daily_log: enabled: false # Append each turn to dated namespaces (daily/YYYY-MM-DD) namespace_prefix: daily + include_session_metadata: true # Include session/channel/sender headers in each log block + max_user_chars: 2000 # Per-turn cap before truncating user text + max_assistant_chars: 4000 # Per-turn cap before truncating assistant text max_context_tokens: 2000 embedding: enabled: true @@ -1163,7 +1166,7 @@ When the selected backend is unavailable (for example embedding provider errors) `memory.auto_extract` controls whether compaction appends extracted durable facts to `global` memory. `memory.proactive_extract` controls optional per-turn extraction after responses (useful for tool-heavy workflows). -`memory.daily_log` controls optional append-only daily turn logs in dated namespaces. +`memory.daily_log` controls optional append-only daily turn logs in dated namespaces. Use `include_session_metadata` when you want better cross-session traceability, and lower the per-turn char caps to reduce noise/volume in long-running chat logs. ### Proactive Context Management diff --git a/SOUL.md b/SOUL.md index 6650aad..ab7393b 100644 --- a/SOUL.md +++ b/SOUL.md @@ -32,6 +32,8 @@ You are Flynn. A personal AI assistant running on your operator's hardware, with **Never invent or infer.** Do not fabricate data, assume file contents, guess command output, or present unverified information as fact. If you haven't read it, run it, or fetched it — you don't know it. Say "I don't know" or go find out. Only speculate or infer when Will explicitly asks for it. **Be proactive** — take initiative, make decisions, and drive tasks forward. But when genuinely unsure or lacking information, ask Will rather than guessing. +**Clarify ambiguity before acting.** If a question references something unfamiliar, uses an ambiguous term, or could be interpreted multiple ways — ask Will to clarify before proceeding. Do not guess what was meant and build a confident-sounding answer on top of that guess. A quick "What do you mean by X?" is always better than a detailed wrong answer. + **When in doubt, check the policy, not the operator.** Before asking "can I do this?", re-read the Boundaries section. If the action is covered, do it. Only ask when the policy genuinely doesn't cover the situation. **Never ask permission for covered actions.** File edits, shell commands, git commits, builds, reads — these are pre-authorized. Do not ask "shall I?", "want me to?", or "is it okay if I?" for anything in the Always Allowed list or any non-destructive action. Just do it. diff --git a/docs/plans/2026-02-15-openclaw-gap-roadmap.md b/docs/plans/2026-02-15-openclaw-gap-roadmap.md index 33c10fd..bf8cf49 100644 --- a/docs/plans/2026-02-15-openclaw-gap-roadmap.md +++ b/docs/plans/2026-02-15-openclaw-gap-roadmap.md @@ -332,9 +332,10 @@ These are substantial UX/ecosystem projects or highly platform-specific; defer u ## Suggested Next Execution Order -1) Daily memory continuity tuning (if continuity quality is still lacking) -2) Auth-profile expansion beyond API-key pools (if needed) -3) Additional run-control UX refinements only if interrupt behavior is still insufficient in production +1) Auth-profile expansion beyond API-key pools (if needed) +2) Additional run-control UX refinements only if interrupt behavior is still insufficient in production +3) Re-evaluate continuity quality after daily-log tuning and proactive extraction data Note: API-key pool auth profile cooldown/backoff (`auth_profile_cooldown_ms`) shipped on 2026-02-19. Note: Queue interrupt preemption telemetry/notice (`queue.preempt` + requester content hint) shipped on 2026-02-19. +Note: Daily memory continuity tuning (`memory.daily_log` metadata + truncation controls) shipped on 2026-02-19. diff --git a/docs/plans/state.json b/docs/plans/state.json index db40a5f..b6928dc 100644 --- a/docs/plans/state.json +++ b/docs/plans/state.json @@ -5810,6 +5810,35 @@ "docs/plans/state.json" ], "test_status": "pnpm test:run src/gateway/handlers/agent.test.ts src/models/rotating.test.ts src/daemon/clientFactory.test.ts src/config/schema.test.ts + pnpm typecheck passing" + }, + "daily-memory-log-continuity-controls": { + "status": "completed", + "date": "2026-02-19", + "updated": "2026-02-19", + "summary": "Extended `memory.daily_log` with continuity-oriented controls: optional session metadata headers plus configurable per-turn truncation caps for user/assistant text. Wired these settings through daemon and gateway orchestrator construction paths and added regression coverage for metadata/tailoring behavior.", + "files_modified": [ + "src/config/schema.ts", + "src/config/schema.test.ts", + "src/backends/native/orchestrator.ts", + "src/backends/native/orchestrator.test.ts", + "src/daemon/routing.ts", + "src/gateway/session-bridge.ts", + "README.md", + "docs/plans/2026-02-15-openclaw-gap-roadmap.md", + "docs/plans/state.json" + ], + "test_status": "pnpm test:run src/backends/native/orchestrator.test.ts src/config/schema.test.ts + pnpm typecheck passing" + }, + "soul-clarify-ambiguity-directive": { + "status": "completed", + "date": "2026-02-19", + "updated": "2026-02-19", + "summary": "Updated SOUL.md core principles to add an explicit directive to clarify ambiguous or unfamiliar questions before acting, to reduce confident-but-wrong execution paths.", + "files_modified": [ + "SOUL.md", + "docs/plans/state.json" + ], + "test_status": "docs-only change" } }, "overall_progress": { diff --git a/src/backends/native/orchestrator.test.ts b/src/backends/native/orchestrator.test.ts index 3d60ef1..a0834b7 100644 --- a/src/backends/native/orchestrator.test.ts +++ b/src/backends/native/orchestrator.test.ts @@ -481,6 +481,56 @@ describe('AgentOrchestrator', () => { expect(dailyLog).toContain('Log this turn'); expect(dailyLog).toContain('default response'); expect(dailyLog).toContain('tool_calls: 0'); + expect(dailyLog).toContain('- session: unknown'); + expect(dailyLog).toContain('- channel: unknown'); + expect(dailyLog).toContain('- sender: unknown'); + + rmSync(tempDir, { recursive: true, force: true }); + }); + + it('honors daily log truncation and metadata toggles', async () => { + const tempDir = mkdtempSync(join(tmpdir(), 'flynn-orchestrator-daily-log-config-')); + const memoryStore = new MemoryStore({ dir: tempDir, maxContextTokens: 2000 }); + const session: Session = { + id: 'ws:test-user', + addMessage: vi.fn(), + getHistory: vi.fn(() => []), + clear: vi.fn(), + replaceHistory: vi.fn(), + getConfig: vi.fn(() => undefined), + setConfig: vi.fn(), + deleteConfig: vi.fn(), + }; + + const orchestrator = new AgentOrchestrator({ + modelRouter: mockRouter, + systemPrompt: 'You are a helpful agent.', + primaryTier: 'default', + delegation: { + compaction: 'fast', + memory_extraction: 'default', + classification: 'complex', + tool_summarisation: 'default', + complex_reasoning: 'complex', + }, + maxDelegationDepth: 10, + session, + memoryStore, + memoryDailyLogEnabled: true, + memoryDailyLogNamespacePrefix: 'daily', + memoryDailyLogIncludeSessionMetadata: false, + memoryDailyLogMaxUserChars: 120, + memoryDailyLogMaxAssistantChars: 150, + }); + + await orchestrator.process('x'.repeat(500)); + const date = new Date().toISOString().slice(0, 10); + const dailyLog = memoryStore.read(`daily/${date}`); + + expect(dailyLog).toContain('...[truncated]'); + expect(dailyLog).not.toContain('- session:'); + expect(dailyLog).not.toContain('- channel:'); + expect(dailyLog).not.toContain('- sender:'); rmSync(tempDir, { recursive: true, force: true }); }); diff --git a/src/backends/native/orchestrator.ts b/src/backends/native/orchestrator.ts index ed41577..1bec32e 100644 --- a/src/backends/native/orchestrator.ts +++ b/src/backends/native/orchestrator.ts @@ -140,6 +140,12 @@ export interface OrchestratorConfig { memoryDailyLogEnabled?: boolean; /** Namespace prefix for daily logs (full namespace: /YYYY-MM-DD). */ memoryDailyLogNamespacePrefix?: string; + /** Include session/channel/sender metadata in each daily log block. */ + memoryDailyLogIncludeSessionMetadata?: boolean; + /** Max chars stored from the user message in daily logs. */ + memoryDailyLogMaxUserChars?: number; + /** Max chars stored from the assistant response in daily logs. */ + memoryDailyLogMaxAssistantChars?: number; /** Automatically retry failed primary runs on a higher tier. */ autoEscalate?: boolean; /** Tier to try for auto-escalation retries. Defaults to complex. */ @@ -184,6 +190,9 @@ export class AgentOrchestrator { private _memoryProactiveExtractNamespace: string; private _memoryDailyLogEnabled: boolean; private _memoryDailyLogNamespacePrefix: string; + private _memoryDailyLogIncludeSessionMetadata: boolean; + private _memoryDailyLogMaxUserChars: number; + private _memoryDailyLogMaxAssistantChars: number; private _autoEscalate: boolean; private _autoEscalateTier: ModelTier; private _systemPromptBase: string; @@ -213,6 +222,9 @@ export class AgentOrchestrator { this._memoryProactiveExtractNamespace = config.memoryProactiveExtractNamespace ?? 'global'; this._memoryDailyLogEnabled = config.memoryDailyLogEnabled ?? false; this._memoryDailyLogNamespacePrefix = config.memoryDailyLogNamespacePrefix ?? 'daily'; + this._memoryDailyLogIncludeSessionMetadata = config.memoryDailyLogIncludeSessionMetadata ?? true; + this._memoryDailyLogMaxUserChars = Math.max(100, config.memoryDailyLogMaxUserChars ?? 2000); + this._memoryDailyLogMaxAssistantChars = Math.max(100, config.memoryDailyLogMaxAssistantChars ?? 4000); this._autoEscalate = config.autoEscalate ?? false; this._autoEscalateTier = config.autoEscalateTier ?? 'complex'; this._systemPromptBase = config.systemPrompt; @@ -705,12 +717,20 @@ export class AgentOrchestrator { const date = new Date().toISOString().slice(0, 10); const timestamp = new Date().toISOString(); const namespace = `${this._memoryDailyLogNamespacePrefix}/${date}`; - const user = this._truncateForMemory(userMessage, 2000); - const assistant = this._truncateForMemory(assistantText, 4000); + const user = this._truncateForMemory(userMessage, this._memoryDailyLogMaxUserChars); + const assistant = this._truncateForMemory(assistantText, this._memoryDailyLogMaxAssistantChars); + const metadataLines = this._memoryDailyLogIncludeSessionMetadata + ? [ + `- session: ${this._session?.id ?? 'unknown'}`, + `- channel: ${this._agent.getToolPolicyContext()?.channel ?? 'unknown'}`, + `- sender: ${this._agent.getToolPolicyContext()?.sender ?? 'unknown'}`, + ] + : []; const block = [ `## ${timestamp}`, '', `- tool_calls: ${toolCallsInRun}`, + ...metadataLines, '', '### User', user, diff --git a/src/config/schema.test.ts b/src/config/schema.test.ts index a88aea8..6154d3f 100644 --- a/src/config/schema.test.ts +++ b/src/config/schema.test.ts @@ -1445,6 +1445,9 @@ describe('configSchema — memory injection strategy', () => { expect(result.memory.proactive_extract.namespace).toBe('global'); expect(result.memory.daily_log.enabled).toBe(false); expect(result.memory.daily_log.namespace_prefix).toBe('daily'); + expect(result.memory.daily_log.include_session_metadata).toBe(true); + expect(result.memory.daily_log.max_user_chars).toBe(2000); + expect(result.memory.daily_log.max_assistant_chars).toBe(4000); expect(result.memory.qmd.enabled).toBe(false); expect(result.memory.qmd.top_k).toBe(8); expect(result.memory.qmd.min_score).toBe(0.15); @@ -1490,6 +1493,9 @@ describe('configSchema — memory injection strategy', () => { daily_log: { enabled: true, namespace_prefix: 'memory', + include_session_metadata: false, + max_user_chars: 1200, + max_assistant_chars: 2400, }, }, }); @@ -1499,6 +1505,9 @@ describe('configSchema — memory injection strategy', () => { expect(result.memory.proactive_extract.namespace).toBe('global/facts'); expect(result.memory.daily_log.enabled).toBe(true); expect(result.memory.daily_log.namespace_prefix).toBe('memory'); + expect(result.memory.daily_log.include_session_metadata).toBe(false); + expect(result.memory.daily_log.max_user_chars).toBe(1200); + expect(result.memory.daily_log.max_assistant_chars).toBe(2400); }); }); diff --git a/src/config/schema.ts b/src/config/schema.ts index 240fde4..5dace4d 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -590,6 +590,9 @@ const memorySchema = z.object({ daily_log: z.object({ enabled: z.boolean().default(false), namespace_prefix: z.string().default('daily'), + include_session_metadata: z.boolean().default(true), + max_user_chars: z.number().min(100).max(20000).default(2000), + max_assistant_chars: z.number().min(100).max(40000).default(4000), }).default({}), injection_strategy: z.enum(['all', 'recent', 'adaptive']).default('all'), max_injection_tokens: z.number().min(100).max(10000).default(2000), diff --git a/src/daemon/routing.ts b/src/daemon/routing.ts index b11504c..c7e80fc 100644 --- a/src/daemon/routing.ts +++ b/src/daemon/routing.ts @@ -456,6 +456,9 @@ export function createMessageRouter(deps: { memoryProactiveExtractNamespace: deps.config.memory?.proactive_extract?.namespace, memoryDailyLogEnabled: deps.config.memory?.daily_log?.enabled, memoryDailyLogNamespacePrefix: deps.config.memory?.daily_log?.namespace_prefix, + memoryDailyLogIncludeSessionMetadata: deps.config.memory?.daily_log?.include_session_metadata, + memoryDailyLogMaxUserChars: deps.config.memory?.daily_log?.max_user_chars, + memoryDailyLogMaxAssistantChars: deps.config.memory?.daily_log?.max_assistant_chars, autoEscalate: deps.config.agents.auto_escalate, autoEscalateTier: 'complex', toolPolicyContext, diff --git a/src/gateway/session-bridge.ts b/src/gateway/session-bridge.ts index 2c1ae81..75e3279 100644 --- a/src/gateway/session-bridge.ts +++ b/src/gateway/session-bridge.ts @@ -323,6 +323,9 @@ export class SessionBridge { memoryProactiveExtractNamespace: config?.memory?.proactive_extract?.namespace, memoryDailyLogEnabled: config?.memory?.daily_log?.enabled, memoryDailyLogNamespacePrefix: config?.memory?.daily_log?.namespace_prefix, + memoryDailyLogIncludeSessionMetadata: config?.memory?.daily_log?.include_session_metadata, + memoryDailyLogMaxUserChars: config?.memory?.daily_log?.max_user_chars, + memoryDailyLogMaxAssistantChars: config?.memory?.daily_log?.max_assistant_chars, toolPolicyContext: { agent: primaryTier, provider: config?.models.default.provider,