feat(memory): add daily log continuity controls
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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 });
|
||||
});
|
||||
|
||||
@@ -140,6 +140,12 @@ export interface OrchestratorConfig {
|
||||
memoryDailyLogEnabled?: boolean;
|
||||
/** Namespace prefix for daily logs (full namespace: <prefix>/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,
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user