From 24feb11cef69ac7fcd389c8e83df2a00a1e27702 Mon Sep 17 00:00:00 2001 From: hugocasa Date: Wed, 16 Sep 2026 09:57:30 +0200 Subject: [PATCH] fix: name the memory setting in ignored-input notes and keep conversions honest Co-Authored-By: Claude Opus 5 --- .../components/flows/agentFormFields.test.ts | 13 +++++++++++- .../lib/components/flows/agentFormFields.ts | 9 ++++++--- .../flows/content/AgentMemoryNotes.svelte | 9 +++++++-- .../flows/content/AiAgentStepInputs.svelte | 20 +++++++++++++++++-- .../src/lib/components/flows/flowInfers.ts | 20 +++++++++++++------ 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/frontend/src/lib/components/flows/agentFormFields.test.ts b/frontend/src/lib/components/flows/agentFormFields.test.ts index 23d7d6f04f..f21d8d793b 100644 --- a/frontend/src/lib/components/flows/agentFormFields.test.ts +++ b/frontend/src/lib/components/flows/agentFormFields.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest' -import { AI_AGENT_SCHEMA, memoryPropertyFor } from './flowInfers' +import { AI_AGENT_SCHEMA, memoryOptionLabel, memoryPropertyFor } from './flowInfers' import { AGENT_FIELD_BY_KEY, AGENT_FIELDS, @@ -94,6 +94,7 @@ describe('historyInputApplies', () => { expect(agentMemoryMode({ kind: 'window', context_length: 10 })).toBe('managed') expect(agentMemoryMode({ kind: 'manual', messages: [] })).toBe('legacy') expect(agentMemoryMode({ kind: 'auto', context_length: 4, memory_id: 'x' })).toBe('legacy') + expect(agentMemoryMode({ kind: 'auto' })).toBe('off') expect(historyInputApplies('memory_id', 'managed')).toBe(true) expect(historyInputApplies('previous_messages', 'managed')).toBe(false) expect(historyInputApplies('memory_id', 'off')).toBe(false) @@ -104,6 +105,16 @@ describe('historyInputApplies', () => { }) }) +describe('memoryOptionLabel', () => { + // The ignored-input note names the setting by the same label its own button carries. + it('names each memory option the way the field renders it', () => { + expect(memoryOptionLabel({ kind: 'manual', messages: [] })).toBe('Previous messages (legacy)') + expect(memoryOptionLabel({ kind: 'auto', context_length: 4 })).toBe('On (legacy)') + expect(memoryOptionLabel({ kind: 'window', context_length: 10 })).toBe('On') + expect(memoryOptionLabel(undefined)).toBeUndefined() + }) +}) + describe('memoryPropertyFor', () => { const property = schemaProperties.memory const kinds = (value: unknown) => diff --git a/frontend/src/lib/components/flows/agentFormFields.ts b/frontend/src/lib/components/flows/agentFormFields.ts index 4a76c3a4f4..3d62497ba3 100644 --- a/frontend/src/lib/components/flows/agentFormFields.ts +++ b/frontend/src/lib/components/flows/agentFormFields.ts @@ -45,9 +45,12 @@ export function keepsManagedMemory(memory: any): boolean { export type AgentMemoryMode = 'legacy' | 'managed' | 'off' -/** Which shape the step's memory holds: an older `auto`/`manual` setting, or the current one. */ +/** Which shape a run reads this memory as: an older `auto`/`manual` setting, or the current one. */ export function agentMemoryMode(memory: any): AgentMemoryMode { - if (memory?.kind === 'auto' || memory?.kind === 'manual') return 'legacy' + if (memory?.kind === 'manual') return 'legacy' + // The worker reads an `auto` that keeps no messages as off, history inputs included, so the form + // offers what that run would read. + if (memory?.kind === 'auto') return memory.context_length ? 'legacy' : 'off' return keepsManagedMemory(memory) ? 'managed' : 'off' } @@ -66,7 +69,7 @@ export function historyInputApplies( /** A memory setting in words, for a linked agent's summary. */ export function describeMemoryPolicy(memory: any): string { if (keepsManagedMemory(memory)) return `Last ${memory.context_length} messages` - if (memory?.kind === 'manual') return 'Off, sends a fixed list of messages' + if (memory?.kind === 'manual') return 'Off, sends previous messages saved with the agent' return 'Off' } diff --git a/frontend/src/lib/components/flows/content/AgentMemoryNotes.svelte b/frontend/src/lib/components/flows/content/AgentMemoryNotes.svelte index 9223d75c12..491a26daa3 100644 --- a/frontend/src/lib/components/flows/content/AgentMemoryNotes.svelte +++ b/frontend/src/lib/components/flows/content/AgentMemoryNotes.svelte @@ -42,7 +42,10 @@ // state, so switching to that setting is the only choice. let legacyEquivalent = $derived(memory?.kind === 'auto' && !legacyMemoryId) + // The older setting never read the step's own memory id, so a conversion that promises the same + // behaviour, or the run's id, drops it rather than bringing it to life. Off keeps ignoring it. function switchToEquivalent() { + if (on) delete args.memory_id args.memory = { type: 'static', value: on ? { kind: 'window', context_length: memory?.context_length } : { kind: 'off' } @@ -52,6 +55,8 @@ function convertLegacyMemoryId(keepAsMemoryId: boolean) { if (keepAsMemoryId && legacyMemoryId) { args.memory_id = { type: 'static', value: legacyMemoryId } + } else { + delete args.memory_id } args.memory = { type: 'static', @@ -74,8 +79,8 @@
- An earlier version of the editor saved a fixed list of messages here, which this agent still - sends. + An earlier version of the editor saved these previous messages inside the memory setting, + and this agent still sends them. {#if historyOnStep}
diff --git a/frontend/src/lib/components/flows/content/AiAgentStepInputs.svelte b/frontend/src/lib/components/flows/content/AiAgentStepInputs.svelte index 63464b8958..4b3dbb79a1 100644 --- a/frontend/src/lib/components/flows/content/AiAgentStepInputs.svelte +++ b/frontend/src/lib/components/flows/content/AiAgentStepInputs.svelte @@ -68,7 +68,7 @@ } from '../agentFormFields' import AgentToolRoster from './AgentToolRoster.svelte' import AgentMemoryNotes from './AgentMemoryNotes.svelte' - import { memoryPropertyFor } from '../flowInfers' + import { memoryOptionLabel, memoryPropertyFor } from '../flowInfers' interface Props { schema: Schema | { properties?: Record } @@ -241,6 +241,22 @@ 'memory' in schemaProperties && (args?.memory?.type === 'javascript' || args?.memory?.type === 'ai') ) + // Names the legacy value the way the memory field's own button does, reading it wherever the mode + // came from, so the row and the setting it points at cannot name it differently. + let legacyMemoryNote = $derived.by(() => { + const onThisForm = 'memory' in schemaProperties + const label = memoryOptionLabel( + onThisForm + ? args?.memory?.type === 'static' + ? args.memory.value + : undefined + : linkedMemory?.memory + ) + return onThisForm + ? `Ignored while memory is set to ${label}.` + : `Ignored while the agent's memory is set to ${label}.` + }) + let memoryIdOffered = $derived( (memoryMode === 'managed' || memoryIsExpression) && scopedFields.some((spec) => spec.key === 'memory_id') @@ -582,7 +598,7 @@ {#if isHistoryKey(spec.key) && !historyInputApplies(spec.key, memoryMode)}

{memoryMode === 'legacy' - ? 'Not read by the older memory setting on this step.' + ? legacyMemoryNote : `Ignored while managed memory is ${memoryMode === 'managed' ? 'on' : 'off'}.`}

{/if} diff --git a/frontend/src/lib/components/flows/flowInfers.ts b/frontend/src/lib/components/flows/flowInfers.ts index 44ff76cd27..43b71123b5 100644 --- a/frontend/src/lib/components/flows/flowInfers.ts +++ b/frontend/src/lib/components/flows/flowInfers.ts @@ -6,6 +6,19 @@ import type { FlowModule, InputTransform } from '$lib/gen' import { AGENT_FLOW_LOCAL_KEYS } from './agentResourceUtils' import { AGENT_HISTORY_KEYS } from './agentFormFields' +/** Display names for the memory field's options, so anything else naming the setting an author + * picked cannot drift from the button they see. */ +export const MEMORY_OPTION_LABELS: Record = { + off: 'Off', + window: 'On', + auto: 'On (legacy)', + manual: 'Previous messages (legacy)' +} + +export function memoryOptionLabel(memory: any): string | undefined { + return memory?.kind ? MEMORY_OPTION_LABELS[memory.kind] : undefined +} + export const AI_AGENT_SCHEMA: Schema = { $schema: 'https://json-schema.org/draft/2020-12/schema', properties: { @@ -42,12 +55,7 @@ export const AI_AGENT_SCHEMA: Schema = { type: 'object', description: 'Windmill stores the conversation and sends its last messages with each request.', - enumLabels: { - off: 'Off', - window: 'On', - auto: 'On (legacy)', - manual: 'Previous messages (legacy)' - }, + enumLabels: MEMORY_OPTION_LABELS, oneOf: [ { type: 'object',