mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-21 08:02:38 +00:00
* feat: split ai agent memory into agent policy, run memory id and step history * fix: scope string memory ids to workspace and flow, keep nested tool history inputs * chore: update sqlx cache for the flow context query * docs: describe memory id scoping as collision-free rather than isolated * chore: regenerate openflow json after merging main * fix: offer no memory id for legacy manual memory, document linked history inputs * fix: seed provided messages from legacy manual memory and hide its note once set * fix: bypass memory when a provided messages expression evaluates to null * fix: require a user message when provided messages are empty * chore: keep the empty messages comment within the line width * docs: name the history inputs wherever linked steps list their flow-local inputs * docs: keep the memory storage path on one line * feat: managed memory with an inherited or custom memory id per step Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: list a custom memory id in the test run form and name where an inherited one comes from Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep memory id out of the add-field menu and drop the memory id telemetry Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep legacy auto memory without an id working after an untouched redeploy Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: rename step messages to previous_messages and address review Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * style: rewrap comments and docs lines lengthened by the previous_messages rename Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * refactor: read agent memory as either a legacy shape or the current one Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: name the memory setting in ignored-input notes and keep conversions honest Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: keep a legacy memory count unset on open and read a cleared count as off Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: address review on cleared test history and zero-count memory Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: drop flow-local keys from a linked agent resource before interpolating it Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: keep a linked resource's own inputs as fallbacks and note ignored history on image runs Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: restore the linked agent draft tests and log ignored history on every image run Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: resolve the one-of variant from the value when the selected one leaves the list Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: treat zero-count managed memory as off when enabling chat mode and shorten comments Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> * fix: stop requiring user_message in the openflow agent contract when previous messages are the prompt Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
100 lines
3.2 KiB
TypeScript
100 lines
3.2 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import type { FlowValue } from '$lib/gen'
|
|
import { modulesWithRetryOrSleep, normalizeAgentHistory } from './utils.svelte'
|
|
|
|
const constantRetry = { constant: { attempts: 1, seconds: 5 } }
|
|
|
|
function step(id: string, extra: Record<string, unknown> = {}) {
|
|
return { id, value: { type: 'identity' }, ...extra } as any
|
|
}
|
|
|
|
describe('modulesWithRetryOrSleep', () => {
|
|
it('reports retries and sleeps everywhere same_worker applies', () => {
|
|
const flow: FlowValue = {
|
|
modules: [
|
|
step('a', { retry: constantRetry }),
|
|
step('b', { sleep: { type: 'static', value: 3 } }),
|
|
step('c'),
|
|
{
|
|
id: 'd',
|
|
value: {
|
|
type: 'forloopflow',
|
|
modules: [step('e', { retry: constantRetry })],
|
|
iterator: { type: 'static', value: [] },
|
|
skip_failures: false
|
|
}
|
|
} as any
|
|
],
|
|
failure_module: step('failure', { retry: constantRetry }),
|
|
preprocessor_module: step('preprocessor', { sleep: { type: 'static', value: 1 } })
|
|
}
|
|
|
|
expect(modulesWithRetryOrSleep(flow)).toEqual(['a', 'b', 'e', 'failure', 'preprocessor'])
|
|
})
|
|
|
|
it('ignores what same_worker does not govern: agent tools and attempt-less retries', () => {
|
|
const flow: FlowValue = {
|
|
modules: [
|
|
step('a', { retry: { constant: { attempts: 0, seconds: 5 } } }),
|
|
{
|
|
id: 'b',
|
|
value: { type: 'aiagent', tools: [step('tool', { retry: constantRetry })] }
|
|
} as any
|
|
]
|
|
}
|
|
|
|
expect(modulesWithRetryOrSleep(flow)).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('normalizeAgentHistory', () => {
|
|
const legacy = () => ({
|
|
memory: {
|
|
type: 'static',
|
|
value: { kind: 'auto', context_length: 10, memory_id: '0f5c3a8e-1d2b-4c6a-9e7f-3b8d2a1c4e6f' }
|
|
}
|
|
})
|
|
|
|
// Outside chat the baked id is still read for runs that pass none, and an older worker must keep
|
|
// accepting the step, so a save leaves it exactly as it was.
|
|
it('keeps a legacy baked memory id outside chat mode', () => {
|
|
const transforms = legacy()
|
|
normalizeAgentHistory(transforms, false)
|
|
expect(transforms).toEqual(legacy())
|
|
})
|
|
|
|
it('drops a legacy baked memory id in chat mode, where it was never read', () => {
|
|
const transforms = legacy()
|
|
normalizeAgentHistory(transforms, true)
|
|
expect(transforms.memory.value).toEqual({ kind: 'auto', context_length: 10 })
|
|
})
|
|
|
|
it('drops an empty baked memory id, which names no memory', () => {
|
|
const transforms = {
|
|
memory: { type: 'static', value: { kind: 'auto', context_length: 10, memory_id: '' } }
|
|
}
|
|
normalizeAgentHistory(transforms, false)
|
|
expect(transforms.memory.value).toEqual({ kind: 'auto', context_length: 10 })
|
|
})
|
|
|
|
it('saves managed memory that keeps no messages as off, which is how it runs', () => {
|
|
for (const context_length of [0, null, undefined]) {
|
|
const transforms: Record<string, any> = {
|
|
memory: { type: 'static', value: { kind: 'window', context_length } }
|
|
}
|
|
normalizeAgentHistory(transforms, false)
|
|
expect(transforms.memory.value).toEqual({ kind: 'off' })
|
|
}
|
|
})
|
|
|
|
it('does not persist an empty static memory id or message list', () => {
|
|
const transforms: Record<string, any> = {
|
|
memory_id: { type: 'static', value: ' ' },
|
|
previous_messages: { type: 'static', value: [] }
|
|
}
|
|
normalizeAgentHistory(transforms, false)
|
|
expect(transforms).toEqual({})
|
|
})
|
|
})
|