fix: name the memory setting in ignored-input notes and keep conversions honest

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
hugocasa
2026-09-16 09:57:30 +02:00
co-authored by Claude Opus 5
parent 4e2eec4c64
commit 24feb11cef
5 changed files with 57 additions and 14 deletions
@@ -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) =>
@@ -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'
}
@@ -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 @@
<Alert type="info" title="Older memory setting" class="mt-2">
<div class="flex flex-col gap-2">
<span>
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.
</span>
{#if historyOnStep}
<div class="flex">
@@ -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<string, any> }
@@ -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)}
<p class="mt-1 text-2xs text-hint">
{memoryMode === 'legacy'
? 'Not read by the older memory setting on this step.'
? legacyMemoryNote
: `Ignored while managed memory is ${memoryMode === 'managed' ? 'on' : 'off'}.`}
</p>
{/if}
@@ -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<string, string> = {
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',