diff --git a/backend/ee-repo-ref.txt b/backend/ee-repo-ref.txt index 9b7bafc9c6..7acd297dac 100644 --- a/backend/ee-repo-ref.txt +++ b/backend/ee-repo-ref.txt @@ -1 +1 @@ -1a8f29cd14fce3b7c6eb20d27f5b58b8ae56d9f5 +1c1dab33563c4907aff8b0da825fb66db60af82a diff --git a/frontend/src/lib/components/FlowBuilder.svelte b/frontend/src/lib/components/FlowBuilder.svelte index 1aa3c7cafb..6888c61f2d 100644 --- a/frontend/src/lib/components/FlowBuilder.svelte +++ b/frontend/src/lib/components/FlowBuilder.svelte @@ -90,7 +90,7 @@ } from './flows/linkedAgentDrafts' import { agentDraftDeployRefusal } from './flows/agentDraft.svelte' import { markAgentWritten } from './flows/agentEditorStore.svelte' - import { logReusableAgentUsage, logStepMemoryIdUsage } from './flows/agentTelemetry' + import { logReusableAgentUsage } from './flows/agentTelemetry' import { deployDraft } from '$lib/utils_draft_deploy' import { getUserExt } from '$lib/user' import { Triggers } from './triggers/triggers.svelte' @@ -657,11 +657,6 @@ ...structuredClone($state.snapshot(newSavedFlow)), path: $pathStore } as Flow - dfsApply(flow.value.modules ?? [], (module) => { - if (module.value.type === 'aiagent') { - logStepMemoryIdUsage(module.value.input_transforms?.memory_id) - } - }) setDraftTriggers([]) loadingSave = false onDeploy?.({ path: $pathStore }) diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index f1c94874fb..f51e459f77 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -1085,8 +1085,7 @@ change a membership, the plan tier and quota shown when the execution meter is opened, whether app sandbox isolation is turned on, whether a step's workspace script is edited from the flow editor, which skin approval steps are given, how many AI sessions - are brought back from the workspace object storage backup, whether AI agent steps set - their own memory id as a fixed id or an expression, how data tables and their + are brought back from the workspace object storage backup, how data tables and their migrations are set up and used, how often an empty workspace home is seen, how often the home page’s create menu and hub-project picker are opened and from which entry point, the name of any public hub project imported from the home page and how far that @@ -1152,8 +1151,7 @@ change a membership, the plan tier and quota shown when the execution meter is opened, whether app sandbox isolation is turned on, whether a step's workspace script is edited from the flow editor, which skin approval steps are given, how many AI sessions - are brought back from the workspace object storage backup, whether AI agent steps set - their own memory id as a fixed id or an expression, how data tables and their + are brought back from the workspace object storage backup, how data tables and their migrations are set up and used, how often an empty workspace home is seen, how often the home page’s create menu and hub-project picker are opened and from which entry point, the name of any public hub project imported from the home page and how far that diff --git a/frontend/src/lib/components/flows/agentFormFields.test.ts b/frontend/src/lib/components/flows/agentFormFields.test.ts index 6163f4f869..d3112beb3b 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 } from './flowInfers' +import { AI_AGENT_SCHEMA, memoryPropertyFor } from './flowInfers' import { AGENT_FIELD_BY_KEY, AGENT_FIELDS, @@ -100,3 +100,20 @@ describe('historyInputApplies', () => { expect(historyInputApplies('messages', undefined)).toBe(true) }) }) + +describe('memoryPropertyFor', () => { + const property = schemaProperties.memory + const kinds = (value: unknown) => + memoryPropertyFor(property, value).oneOf.map((variant: { title: string }) => variant.title) + + it('adds a legacy kind as an option only while the value holds it', () => { + expect(memoryPropertyFor(property, { kind: 'window', context_length: 10 })).toBe(property) + expect(memoryPropertyFor(property, undefined)).toBe(property) + expect(kinds({ kind: 'auto', context_length: 4, memory_id: 'x' })).toEqual([ + 'off', + 'window', + 'auto' + ]) + expect(kinds({ kind: 'manual', messages: [] })).toEqual(['off', 'window', 'manual']) + }) +}) diff --git a/frontend/src/lib/components/flows/agentFormFields.ts b/frontend/src/lib/components/flows/agentFormFields.ts index 38cabd1bd3..7078c09398 100644 --- a/frontend/src/lib/components/flows/agentFormFields.ts +++ b/frontend/src/lib/components/flows/agentFormFields.ts @@ -142,7 +142,6 @@ export const AGENT_FIELDS: AgentFieldSpec[] = [ tooltip: 'Conversation history id: runs with the same id share their history. Inherited uses the memory_id the run was started with: the conversation id in chat mode, or the memory_id query parameter otherwise. Without either, each run starts fresh. Custom sets the id on the step: a fixed id shares one history across all runs, an expression keeps one history per value.', implicit: '', - defaultHint: 'Default: inherited from the run', textOnly: true }, { diff --git a/frontend/src/lib/components/flows/agentTelemetry.ts b/frontend/src/lib/components/flows/agentTelemetry.ts index 8deee98306..87fcb0dad4 100644 --- a/frontend/src/lib/components/flows/agentTelemetry.ts +++ b/frontend/src/lib/components/flows/agentTelemetry.ts @@ -1,4 +1,3 @@ -import type { InputTransform } from '$lib/gen' import { logFeatureUsage } from '$lib/utils/featureUsage' // Anonymous counters for the reusable-agent lifecycle (`docs/reusable-ai-agents.md`). Same rules @@ -22,12 +21,3 @@ export type ReusableAgentEvent = export function logReusableAgentUsage(event: ReusableAgentEvent): void { logFeatureUsage('ai_agent', 'reusable', { key: event }) } - -/** An agent step deployed with a memory id of its own instead of the run's, keyed by whether it is - * one fixed id or an expression giving one memory per key. */ -export function logStepMemoryIdUsage(memoryId: InputTransform | undefined): void { - if (!memoryId) return - logFeatureUsage('ai_agent', 'memory_id', { - key: memoryId.type === 'static' ? 'fixed' : 'expression' - }) -} diff --git a/frontend/src/lib/components/flows/content/AiAgentStepInputs.svelte b/frontend/src/lib/components/flows/content/AiAgentStepInputs.svelte index e620943eb3..648f547553 100644 --- a/frontend/src/lib/components/flows/content/AiAgentStepInputs.svelte +++ b/frontend/src/lib/components/flows/content/AiAgentStepInputs.svelte @@ -180,7 +180,7 @@ let schemaProperties = $derived((schema?.properties ?? {}) as Record) // Whether the brain edited here, or the linked agent's, keeps managed memory. Unknown for an - // expression or a linked agent still loading, which leaves both history inputs open. + // expression or a linked agent that has not loaded, which keeps previous messages addable. let managedMemory = $derived.by((): boolean | undefined => { if ('memory' in schemaProperties) { const transform = args?.memory @@ -232,7 +232,8 @@ ) ) - // Offered whenever the agent may keep managed memory: on, or an expression the form cannot read. + // Offered when managed memory is on or an expression the form cannot read, not while a linked + // agent's setting is unknown. // Unset, the memory id the run was started with applies, so the step's own id sits behind a // choice and the key exists only once Custom is picked. let memoryIsExpression = $derived( @@ -325,6 +326,8 @@ !spec.virtual && !isShown(spec.key) && !(imageOutput && spec.textOnly) && + // Memory id's row appears on its own when it is offered, so the menu never adds it. + spec.key !== 'memory_id' && !(isHistoryKey(spec.key) && !historyInputApplies(spec.key, managedMemory)) ) } diff --git a/frontend/src/lib/components/flows/flowInfers.ts b/frontend/src/lib/components/flows/flowInfers.ts index d65d5175d9..8a344a370d 100644 --- a/frontend/src/lib/components/flows/flowInfers.ts +++ b/frontend/src/lib/components/flows/flowInfers.ts @@ -210,7 +210,8 @@ export const LEGACY_MEMORY_VARIANTS: Record = { } /** The memory property to render for a value: a legacy kind is added as an option only while the - * value holds it. */ + * value holds it. Otherwise the property itself is returned, which callers compare by identity to + * avoid rebuilding the step schema. */ export function memoryPropertyFor(property: any, value: any): any { const legacy = value?.kind ? LEGACY_MEMORY_VARIANTS[value.kind] : undefined if (!legacy || !property?.oneOf) return property