From cc7d3c536d75dba9eaf4834ec215e6522f28db04 Mon Sep 17 00:00:00 2001 From: Oleg Ivaniv Date: Tue, 5 May 2026 13:06:17 +0200 Subject: [PATCH] refactor(instance-ai): reset obsolete native persistence --- .../config/src/configs/instance-ai.config.ts | 8 - packages/@n8n/config/test/config.test.ts | 2 - ...000000-ResetInstanceAiNativePersistence.ts | 28 ++++ .../db/src/migrations/postgresdb/index.ts | 2 + .../@n8n/db/src/migrations/sqlite/index.ts | 2 + packages/@n8n/instance-ai/CLAUDE.md | 2 +- packages/@n8n/instance-ai/SPECS.md | 3 +- packages/@n8n/instance-ai/docs/ENGINEERING.md | 8 +- .../@n8n/instance-ai/docs/architecture.md | 2 +- .../@n8n/instance-ai/docs/configuration.md | 20 +-- packages/@n8n/instance-ai/docs/memory.md | 146 +++++++----------- .../src/modules/instance-ai/entities/index.ts | 2 - ...instance-ai-observational-memory.entity.ts | 99 ------------ .../instance-ai-workflow-snapshot.entity.ts | 20 --- .../modules/instance-ai/instance-ai.module.ts | 12 -- .../modules/instance-ai/repositories/index.ts | 2 - ...ance-ai-observational-memory.repository.ts | 11 -- ...nstance-ai-workflow-snapshot.repository.ts | 11 -- .../instance-ai/snapshot-pruning.service.ts | 69 --------- 19 files changed, 104 insertions(+), 345 deletions(-) create mode 100644 packages/@n8n/db/src/migrations/common/1778060000000-ResetInstanceAiNativePersistence.ts delete mode 100644 packages/cli/src/modules/instance-ai/entities/instance-ai-observational-memory.entity.ts delete mode 100644 packages/cli/src/modules/instance-ai/entities/instance-ai-workflow-snapshot.entity.ts delete mode 100644 packages/cli/src/modules/instance-ai/repositories/instance-ai-observational-memory.repository.ts delete mode 100644 packages/cli/src/modules/instance-ai/repositories/instance-ai-workflow-snapshot.repository.ts delete mode 100644 packages/cli/src/modules/instance-ai/snapshot-pruning.service.ts diff --git a/packages/@n8n/config/src/configs/instance-ai.config.ts b/packages/@n8n/config/src/configs/instance-ai.config.ts index 7dc80dfec32..88a385633e3 100644 --- a/packages/@n8n/config/src/configs/instance-ai.config.ts +++ b/packages/@n8n/config/src/configs/instance-ai.config.ts @@ -102,14 +102,6 @@ export class InstanceAiConfig { @Env('N8N_INSTANCE_AI_THREAD_TTL_DAYS') threadTtlDays: number = 90; - /** Interval in milliseconds between snapshot pruning runs. 0 = disabled. */ - @Env('N8N_INSTANCE_AI_SNAPSHOT_PRUNE_INTERVAL') - snapshotPruneInterval: number = 60 * 60 * 1000; // 1 hour - - /** Retention period in milliseconds for orphaned workflow snapshots before pruning. */ - @Env('N8N_INSTANCE_AI_SNAPSHOT_RETENTION') - snapshotRetention: number = 24 * 60 * 60 * 1000; // 24 hours - /** Timeout in milliseconds for HITL confirmation requests. 0 = no timeout. */ @Env('N8N_INSTANCE_AI_CONFIRMATION_TIMEOUT') confirmationTimeout: number = 10 * 60 * 1000; // 10 minutes diff --git a/packages/@n8n/config/test/config.test.ts b/packages/@n8n/config/test/config.test.ts index f352a357e98..f9d37e7306a 100644 --- a/packages/@n8n/config/test/config.test.ts +++ b/packages/@n8n/config/test/config.test.ts @@ -291,8 +291,6 @@ describe('GlobalConfig', () => { searxngUrl: '', gatewayApiKey: '', threadTtlDays: 90, - snapshotPruneInterval: 3_600_000, - snapshotRetention: 86_400_000, confirmationTimeout: 600_000, }, queue: { diff --git a/packages/@n8n/db/src/migrations/common/1778060000000-ResetInstanceAiNativePersistence.ts b/packages/@n8n/db/src/migrations/common/1778060000000-ResetInstanceAiNativePersistence.ts new file mode 100644 index 00000000000..fa27457f133 --- /dev/null +++ b/packages/@n8n/db/src/migrations/common/1778060000000-ResetInstanceAiNativePersistence.ts @@ -0,0 +1,28 @@ +import type { IrreversibleMigration, MigrationContext } from '../migration-types'; + +const persistedRuntimeTables = [ + 'ai_builder_temporary_workflow', + 'instance_ai_checkpoints', + 'instance_ai_iteration_logs', + 'instance_ai_run_snapshots', + 'instance_ai_messages', + 'instance_ai_resources', + 'instance_ai_threads', +] as const; + +const obsoleteTables = [ + 'instance_ai_workflow_snapshots', + 'instance_ai_observational_memory', +] as const; + +export class ResetInstanceAiNativePersistence1778060000000 implements IrreversibleMigration { + async up({ queryRunner, escape, schemaBuilder: { dropTable } }: MigrationContext) { + for (const table of persistedRuntimeTables) { + await queryRunner.query(`DELETE FROM ${escape.tableName(table)}`); + } + + for (const table of obsoleteTables) { + await dropTable(table); + } + } +} diff --git a/packages/@n8n/db/src/migrations/postgresdb/index.ts b/packages/@n8n/db/src/migrations/postgresdb/index.ts index 84f679b3242..552731211b4 100644 --- a/packages/@n8n/db/src/migrations/postgresdb/index.ts +++ b/packages/@n8n/db/src/migrations/postgresdb/index.ts @@ -169,6 +169,7 @@ import { AddLangsmithIdsToInstanceAiRunSnapshots1777100000000 } from '../common/ import { CreateAiBuilderTemporaryWorkflowTable1777281990043 } from '../common/1777281990043-CreateAiBuilderTemporaryWorkflowTable'; import { AddExecutionDeduplicationKey1778000000000 } from '../common/1778000000000-AddExecutionDeduplicationKey'; import { CreateInstanceAiCheckpointTable1778050000000 } from '../common/1778050000000-CreateInstanceAiCheckpointTable'; +import { ResetInstanceAiNativePersistence1778060000000 } from '../common/1778060000000-ResetInstanceAiNativePersistence'; import type { Migration } from '../migration-types'; export const postgresMigrations: Migration[] = [ @@ -343,4 +344,5 @@ export const postgresMigrations: Migration[] = [ AddTracingContextToExecution1777045000000, CreateAiBuilderTemporaryWorkflowTable1777281990043, ExpandVariablesValueColumnToText1777420800000, + ResetInstanceAiNativePersistence1778060000000, ]; diff --git a/packages/@n8n/db/src/migrations/sqlite/index.ts b/packages/@n8n/db/src/migrations/sqlite/index.ts index f2390605a48..0d7210d3489 100644 --- a/packages/@n8n/db/src/migrations/sqlite/index.ts +++ b/packages/@n8n/db/src/migrations/sqlite/index.ts @@ -162,6 +162,7 @@ import { AddLangsmithIdsToInstanceAiRunSnapshots1777100000000 } from '../common/ import { CreateAiBuilderTemporaryWorkflowTable1777281990043 } from '../common/1777281990043-CreateAiBuilderTemporaryWorkflowTable'; import { AddExecutionDeduplicationKey1778000000000 } from '../common/1778000000000-AddExecutionDeduplicationKey'; import { CreateInstanceAiCheckpointTable1778050000000 } from '../common/1778050000000-CreateInstanceAiCheckpointTable'; +import { ResetInstanceAiNativePersistence1778060000000 } from '../common/1778060000000-ResetInstanceAiNativePersistence'; import type { Migration } from '../migration-types'; const sqliteMigrations: Migration[] = [ @@ -329,6 +330,7 @@ const sqliteMigrations: Migration[] = [ CreateInstanceAiCheckpointTable1778050000000, AddTracingContextToExecution1777045000000, CreateAiBuilderTemporaryWorkflowTable1777281990043, + ResetInstanceAiNativePersistence1778060000000, ]; export { sqliteMigrations }; diff --git a/packages/@n8n/instance-ai/CLAUDE.md b/packages/@n8n/instance-ai/CLAUDE.md index d0740e98fa1..d6fc52bbcc6 100644 --- a/packages/@n8n/instance-ai/CLAUDE.md +++ b/packages/@n8n/instance-ai/CLAUDE.md @@ -68,4 +68,4 @@ See `docs/e2e-tests.md` for the full recording/replay architecture. - **Run lifecycle**: `run-start` (first) → events → `run-finish` (last, carries status) - **Planned tasks**: `plan` tool for multi-step work; tasks run detached as background agents - **Sub-agents**: stateless, native domain tools only, no MCP, no recursive delegation -- **Memory**: observational memory = thread-scoped, working memory is disabled +- **Memory**: native thread messages plus rolling compaction; sub-agents are stateless diff --git a/packages/@n8n/instance-ai/SPECS.md b/packages/@n8n/instance-ai/SPECS.md index f629fe4f1f3..20f0f664d1d 100644 --- a/packages/@n8n/instance-ai/SPECS.md +++ b/packages/@n8n/instance-ai/SPECS.md @@ -460,7 +460,7 @@ pnpm typecheck - [x] Implement native chunk to Instance AI event mapper. - [x] Implement TypeORM `BuiltMemory`. - [x] Implement TypeORM `CheckpointStore`. -- [ ] Add fresh DB migration for native Instance AI persistence. +- [x] Add fresh DB migration for native Instance AI persistence. - [x] Remove `TypeORMCompositeStore` from the active runtime path. - [x] Remove `TypeORMWorkflowsStorage` from the active runtime path. - [x] Remove observational memory runtime usage. @@ -474,7 +474,6 @@ pnpm typecheck Current remaining implementation gaps: -- Add the destructive/fresh native persistence migration. - Decide whether LangSmith should move further onto native telemetry/events or keep the current product-level spans. diff --git a/packages/@n8n/instance-ai/docs/ENGINEERING.md b/packages/@n8n/instance-ai/docs/ENGINEERING.md index bd87671ceeb..13c4b7d30b6 100644 --- a/packages/@n8n/instance-ai/docs/ENGINEERING.md +++ b/packages/@n8n/instance-ai/docs/ENGINEERING.md @@ -241,12 +241,12 @@ export function createListWorkflowsTool(context: InstanceAiContext) { ### Memory usage -The memory system is thread-scoped. Writing observations from a sub-agent -corrupts the orchestrator's context, and manually summarizing tool results -fights with the Observer doing the same thing. +The memory system is thread-scoped. Writing conversation state from a sub-agent +corrupts the orchestrator's context, and ad hoc summaries fight the rolling +compaction service. - Never read/write memory from sub-agents — they're stateless by design -- Let observational memory handle compression — don't manually summarize +- Let the compaction service handle compression — don't manually summarize ### Agent creation diff --git a/packages/@n8n/instance-ai/docs/architecture.md b/packages/@n8n/instance-ai/docs/architecture.md index 9f95052a1f3..3bcb527f61b 100644 --- a/packages/@n8n/instance-ai/docs/architecture.md +++ b/packages/@n8n/instance-ai/docs/architecture.md @@ -7,7 +7,7 @@ natural language interface to workflows, executions, credentials, and nodes — the goal that most users never need to interact with workflows directly. The system follows the **deep agent architecture** — an orchestrator with explicit -planning, dynamic sub-agent delegation, observational memory, and structured +planning, dynamic sub-agent delegation, rolling context compaction, and structured prompts. The LLM controls the execution loop; the architecture provides the primitives. diff --git a/packages/@n8n/instance-ai/docs/configuration.md b/packages/@n8n/instance-ai/docs/configuration.md index 392a076285e..bfe54d65357 100644 --- a/packages/@n8n/instance-ai/docs/configuration.md +++ b/packages/@n8n/instance-ai/docs/configuration.md @@ -66,21 +66,11 @@ When no search provider is available, `web-search` and `research-with-agent` too Sandbox workspaces persist per thread — the same container is reused across messages in a conversation. Workspaces are destroyed on server shutdown. -### Observational Memory - -| Variable | Type | Default | Description | -|----------|------|---------|-------------| -| `N8N_INSTANCE_AI_OBSERVER_MODEL` | string | `google/gemini-2.5-flash` | LLM for Observer/Reflector compression agents | -| `N8N_INSTANCE_AI_OBSERVER_MESSAGE_TOKENS` | number | `30000` | Token threshold for Observer to trigger compression | -| `N8N_INSTANCE_AI_REFLECTOR_OBSERVATION_TOKENS` | number | `40000` | Token threshold for Reflector to condense observations | - ### Lifecycle & Housekeeping | Variable | Type | Default | Description | |----------|------|---------|-------------| | `N8N_INSTANCE_AI_THREAD_TTL_DAYS` | number | `90` | Conversation thread TTL in days. Threads older than this are auto-expired. 0 = no expiration. | -| `N8N_INSTANCE_AI_SNAPSHOT_PRUNE_INTERVAL` | number | `3600000` | Interval in ms between snapshot pruning runs. 0 = disabled. | -| `N8N_INSTANCE_AI_SNAPSHOT_RETENTION` | number | `86400000` | Retention period in ms for orphaned workflow snapshots before pruning. | | `N8N_INSTANCE_AI_CONFIRMATION_TIMEOUT` | number | `600000` | Timeout in ms for HITL confirmation requests. 0 = no timeout. | ## Enabling / Disabling @@ -193,14 +183,14 @@ N8N_INSTANCE_AI_GATEWAY_API_KEY=my-secret-key N8N_INSTANCE_AI_MODEL=custom/llama-3.1-70b N8N_INSTANCE_AI_MODEL_URL=http://localhost:1234/v1 -# Full configuration with observational memory tuning +# Full configuration N8N_INSTANCE_AI_MODEL=anthropic/claude-sonnet-4-6 N8N_INSTANCE_AI_MCP_SERVERS="github=https://mcp.github.com/sse" +N8N_INSTANCE_AI_LAST_MESSAGES=20 N8N_INSTANCE_AI_EMBEDDER_MODEL=openai/text-embedding-3-small -N8N_INSTANCE_AI_MAX_STEPS=50 -N8N_INSTANCE_AI_MAX_LOOP_ITERATIONS=10 -N8N_INSTANCE_AI_OBSERVER_MODEL=google/gemini-2.5-flash -N8N_INSTANCE_AI_OBSERVER_MESSAGE_TOKENS=30000 +N8N_INSTANCE_AI_SEMANTIC_RECALL_TOP_K=5 +N8N_INSTANCE_AI_SUB_AGENT_MAX_STEPS=100 +N8N_INSTANCE_AI_THREAD_TTL_DAYS=90 ``` ## SearXNG Setup (Docker Compose) diff --git a/packages/@n8n/instance-ai/docs/memory.md b/packages/@n8n/instance-ai/docs/memory.md index d00c700f127..7602686a59d 100644 --- a/packages/@n8n/instance-ai/docs/memory.md +++ b/packages/@n8n/instance-ai/docs/memory.md @@ -2,121 +2,98 @@ ## Overview -The memory system serves two purposes: +The memory system serves three purposes: -- **Operational context management** — observational memory that compresses - the agent's operational history during long autonomous loops to prevent - context degradation (thread-scoped) -- **Conversation history** — recent messages and semantic recall for the - current thread (thread-scoped) +- **Native agent persistence** — thread and message storage through the + `@n8n/agents` `BuiltMemory` interface. +- **Operational context management** — rolling compaction of older messages into + a thread metadata summary when the conversation approaches the model context + window. +- **Conversation continuity** — recent messages, optional semantic recall, plan + state, retry history, checkpoints, and UI run snapshots for the current + thread. -Sub-agents are stateless — context is passed via the briefing only. +Sub-agents are stateless. Context is passed through the briefing, and retry +history is appended from thread-scoped iteration logs. ## Tiers -### Tier 1: Storage Backend +### Tier 1: Native Storage -The persistence layer. Stores all messages, observational memory, plan state, -event history, and vector embeddings. +Instance AI uses n8n's application database for native agent storage. -| Backend | When Used | Connection | -|---------|-----------|------------| -| PostgreSQL | n8n is configured with `postgresdb` | Built from n8n's DB config | -| LibSQL/SQLite | All other cases (default) | `file:instance-ai-memory.db` | +| Store | Tables | +|-------|--------| +| `TypeORMAgentMemory` | `instance_ai_threads`, `instance_ai_messages`, `instance_ai_resources` | +| `TypeORMAgentCheckpointStore` | `instance_ai_checkpoints` | +| UI run snapshots | `instance_ai_run_snapshots` | +| Iteration logs | `instance_ai_iteration_logs` | +| Temporary workflow mapping | `ai_builder_temporary_workflow` | -The storage backend is selected automatically based on n8n's database -configuration — no separate config needed. +The obsolete workflow snapshot and observational memory tables are dropped by +the native agents reset migration. Existing Instance AI runtime data may be +cleared during that migration. ### Tier 2: Recent Messages -A sliding window of the most recent N messages in the conversation, sent as -context to the LLM on every request. +A sliding window of the most recent N messages is sent as context to the LLM on +every request. - **Default**: 20 messages - **Config**: `N8N_INSTANCE_AI_LAST_MESSAGES` -### Tier 3: Observational Memory +### Tier 3: Rolling Compaction -Automatic context compression for long-running autonomous loops. Two background -agents manage the orchestrator's context size: +`InstanceAiCompactionService` estimates thread token usage. When a conversation +exceeds the configured context threshold, older messages outside the recent +tail are summarized by a native compaction agent. -- **Observer** — when message tokens exceed a threshold (default: 30K), compresses - old messages into dense observations -- **Reflector** — when observations exceed their threshold (default: 40K), - condenses observations into higher-level patterns - -``` -Context window layout during autonomous loop: - -┌──────────────────────────────────────────┐ -│ Observation Block (≤40K tokens) │ ← compressed history -│ "Built wf-123 with Schedule→HTTP→Slack. │ (append-only, cacheable) -│ Exec failed: 401 on HTTP node. │ -│ Debugger identified missing API key. │ -│ Rebuilt workflow, re-executed, passed." │ -├──────────────────────────────────────────┤ -│ Raw Message Block (≤30K tokens) │ ← recent tool calls & results -│ [current step's tool calls and results] │ (rotated as new messages arrive) -└──────────────────────────────────────────┘ -``` - -**Why this matters for the autonomous loop**: - -- Tool-heavy workloads (workflow definitions, execution results, node - descriptions) get **5–40x compression** — a 50-step loop that would blow - out the context window stays manageable -- The observation block is **append-only** until reflection runs, enabling - high prompt cache hit rates (4–10x cost reduction) -- **Async buffering** pre-computes observations in the background — no - user-visible pause when the threshold is hit -- Uses a secondary LLM (default: `google/gemini-2.5-flash`) for compression — - cheap and has a 1M token context window for the Reflector - -Observational memory is **thread-scoped** — it tracks the operational history -of the current task. +Compaction state is stored in thread metadata under +`instanceAiConversationSummary`. Raw messages remain in the database for UI and +debugging; compaction only changes the model input. ### Tier 4: Semantic Recall (Optional) -Vector-based retrieval of relevant past messages. When enabled, the system -embeds each message and retrieves semantically similar past messages to include -as context. +When configured and supported by the active memory backend, the native agents +runtime can retrieve semantically related past messages and inject them into +context. -- **Requires**: `N8N_INSTANCE_AI_EMBEDDER_MODEL` to be set +- **Config**: `N8N_INSTANCE_AI_EMBEDDER_MODEL` - **Config**: `N8N_INSTANCE_AI_SEMANTIC_RECALL_TOP_K` (default: 5) -- **Message range**: 2 messages before and 1 after each match -Disabled by default. When the embedder model is not set, only tiers 1–3 are -active. +Disabled by default. -### Tier 5: Plan Storage +### Tier 5: Plan And Retry State -The `plan` tool stores execution plans in thread-scoped storage. Plans are -structured data (goal, current phase, iteration count, step statuses) that -persist across reconnects within a conversation. See the [tools](./tools.md) -documentation for the plan tool schema. +The `plan` tool stores execution plans in thread metadata. Workflow loop +attempts are stored in `instance_ai_iteration_logs` and appended to sub-agent +briefings on retry. + +### Tier 6: Checkpoints And Run Snapshots + +Native checkpoints persist suspended agent state for human-in-the-loop resume. +Run snapshots persist the UI agent tree used to reconstruct visible progress +after reconnects. ## Scoping Model -All memory is thread-scoped (isolated per conversation): +All memory is thread-scoped unless a native memory call explicitly requests a +resource-scoped working-memory key. -- **Recent messages** — the sliding window of N messages -- **Observational memory** — compressed operational history -- **Semantic recall** — vector retrieval of relevant past messages -- **Plan** — the current execution plan +- **Recent messages** — current conversation history. +- **Compaction summary** — older context summarized for the same thread. +- **Plan and iteration logs** — current task state and retry history. +- **Checkpoints** — suspended native agent state keyed by run. -### Sub-agent memory +### Sub-Agent Memory -Sub-agents are fully stateless — context is passed via the briefing and -`conversationContext` fields in the `delegate` and `build-workflow-with-agent` -tools. +Sub-agents do not read or write persistent memory directly. The orchestrator +builds their briefing from the current request, relevant task state, and retry +history. -Past failed attempts are tracked via the `IterationLog` (stored in thread -metadata) and appended to sub-agent briefings on retry, providing cross-attempt -context without persistent memory. +### Cross-User Isolation -### Cross-user isolation - -Each user's memory is fully independent. The agent cannot see other users' +Each user's memory is independent. The agent cannot see other users' conversations or semantic history. ## Configuration @@ -124,8 +101,5 @@ conversations or semantic history. | Variable | Type | Default | Description | |----------|------|---------|-------------| | `N8N_INSTANCE_AI_LAST_MESSAGES` | number | 20 | Recent message window | -| `N8N_INSTANCE_AI_EMBEDDER_MODEL` | string | `''` | Embedder model (empty = disabled) | +| `N8N_INSTANCE_AI_EMBEDDER_MODEL` | string | `''` | Embedder model for semantic recall (empty = disabled) | | `N8N_INSTANCE_AI_SEMANTIC_RECALL_TOP_K` | number | 5 | Number of semantic matches | -| `N8N_INSTANCE_AI_OBSERVER_MODEL` | string | `google/gemini-2.5-flash` | LLM for Observer/Reflector | -| `N8N_INSTANCE_AI_OBSERVER_MESSAGE_TOKENS` | number | 30000 | Observer trigger threshold | -| `N8N_INSTANCE_AI_REFLECTOR_OBSERVATION_TOKENS` | number | 40000 | Reflector trigger threshold | diff --git a/packages/cli/src/modules/instance-ai/entities/index.ts b/packages/cli/src/modules/instance-ai/entities/index.ts index 7acbb935e8e..6c9ab53fd25 100644 --- a/packages/cli/src/modules/instance-ai/entities/index.ts +++ b/packages/cli/src/modules/instance-ai/entities/index.ts @@ -1,8 +1,6 @@ export { InstanceAiThread } from './instance-ai-thread.entity'; export { InstanceAiMessage } from './instance-ai-message.entity'; export { InstanceAiResource } from './instance-ai-resource.entity'; -export { InstanceAiObservationalMemory } from './instance-ai-observational-memory.entity'; -export { InstanceAiWorkflowSnapshot } from './instance-ai-workflow-snapshot.entity'; export { InstanceAiRunSnapshot } from './instance-ai-run-snapshot.entity'; export { InstanceAiIterationLog } from './instance-ai-iteration-log.entity'; export { InstanceAiCheckpoint } from './instance-ai-checkpoint.entity'; diff --git a/packages/cli/src/modules/instance-ai/entities/instance-ai-observational-memory.entity.ts b/packages/cli/src/modules/instance-ai/entities/instance-ai-observational-memory.entity.ts deleted file mode 100644 index b2f05622661..00000000000 --- a/packages/cli/src/modules/instance-ai/entities/instance-ai-observational-memory.entity.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { WithTimestamps, DateTimeColumn, JsonColumn } from '@n8n/db'; -import { Column, Entity, Index, PrimaryColumn } from '@n8n/typeorm'; - -@Entity({ name: 'instance_ai_observational_memory' }) -@Index('IDX_instance_ai_om_scope_thread_resource', ['scope', 'threadId', 'resourceId'], { - unique: true, -}) -export class InstanceAiObservationalMemory extends WithTimestamps { - @PrimaryColumn({ type: 'varchar', length: 36 }) - id: string; - - @Index() - @Column({ type: 'varchar', length: 255 }) - lookupKey: string; - - @Column({ type: 'varchar', length: 16 }) - scope: string; - - @Column({ type: 'uuid', nullable: true }) - threadId: string | null; - - @Column({ type: 'varchar', length: 255 }) - resourceId: string; - - @Column({ type: 'text', default: '' }) - activeObservations: string; - - @Column({ type: 'varchar', length: 32 }) - originType: string; - - @Column({ type: 'text' }) - config: string; - - @Column({ type: 'int', default: 0 }) - generationCount: number; - - @DateTimeColumn({ nullable: true }) - lastObservedAt: Date | null; - - @Column({ type: 'int', default: 0 }) - pendingMessageTokens: number; - - @Column({ type: 'int', default: 0 }) - totalTokensObserved: number; - - @Column({ type: 'int', default: 0 }) - observationTokenCount: number; - - @Column({ type: 'boolean', default: false }) - isObserving: boolean; - - @Column({ type: 'boolean', default: false }) - isReflecting: boolean; - - @JsonColumn({ nullable: true }) - observedMessageIds: string[] | null; - - @Column({ type: 'varchar', nullable: true }) - observedTimezone: string | null; - - @Column({ type: 'text', nullable: true }) - bufferedObservations: string | null; - - @Column({ type: 'int', nullable: true }) - bufferedObservationTokens: number | null; - - @JsonColumn({ nullable: true }) - bufferedMessageIds: string[] | null; - - @Column({ type: 'text', nullable: true }) - bufferedReflection: string | null; - - @Column({ type: 'int', nullable: true }) - bufferedReflectionTokens: number | null; - - @Column({ type: 'int', nullable: true }) - bufferedReflectionInputTokens: number | null; - - @Column({ type: 'int', nullable: true }) - reflectedObservationLineCount: number | null; - - @JsonColumn({ nullable: true }) - bufferedObservationChunks: unknown[] | null; - - @Column({ type: 'boolean', default: false }) - isBufferingObservation: boolean; - - @Column({ type: 'boolean', default: false }) - isBufferingReflection: boolean; - - @Column({ type: 'int', default: 0 }) - lastBufferedAtTokens: number; - - @DateTimeColumn({ nullable: true }) - lastBufferedAtTime: Date | null; - - @JsonColumn({ nullable: true }) - metadata: Record | null; -} diff --git a/packages/cli/src/modules/instance-ai/entities/instance-ai-workflow-snapshot.entity.ts b/packages/cli/src/modules/instance-ai/entities/instance-ai-workflow-snapshot.entity.ts deleted file mode 100644 index 872418ce7de..00000000000 --- a/packages/cli/src/modules/instance-ai/entities/instance-ai-workflow-snapshot.entity.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { WithTimestamps } from '@n8n/db'; -import { Column, Entity, PrimaryColumn } from '@n8n/typeorm'; - -@Entity({ name: 'instance_ai_workflow_snapshots' }) -export class InstanceAiWorkflowSnapshot extends WithTimestamps { - @PrimaryColumn({ type: 'varchar', length: 36 }) - runId: string; - - @PrimaryColumn({ type: 'varchar', length: 255 }) - workflowName: string; - - @Column({ type: 'varchar', length: 255, nullable: true }) - resourceId: string | null; - - @Column({ type: 'varchar', nullable: true }) - status: string | null; - - @Column({ type: 'text' }) - snapshot: string; -} diff --git a/packages/cli/src/modules/instance-ai/instance-ai.module.ts b/packages/cli/src/modules/instance-ai/instance-ai.module.ts index ecebaa7b65d..c49b53fee3b 100644 --- a/packages/cli/src/modules/instance-ai/instance-ai.module.ts +++ b/packages/cli/src/modules/instance-ai/instance-ai.module.ts @@ -31,10 +31,6 @@ export class InstanceAiModule implements ModuleInterface { void Container.get(InstanceAiMemoryService) .cleanupExpiredThreads(async (threadId) => await aiService.clearThreadState(threadId)) .catch(() => undefined); - - // Initialize snapshot pruning — lifecycle decorators handle multi-main start/stop - const { SnapshotPruningService } = await import('./snapshot-pruning.service'); - Container.get(SnapshotPruningService).init(); } async settings() { @@ -60,12 +56,6 @@ export class InstanceAiModule implements ModuleInterface { const { InstanceAiThread } = await import('./entities/instance-ai-thread.entity'); const { InstanceAiMessage } = await import('./entities/instance-ai-message.entity'); const { InstanceAiResource } = await import('./entities/instance-ai-resource.entity'); - const { InstanceAiObservationalMemory } = await import( - './entities/instance-ai-observational-memory.entity' - ); - const { InstanceAiWorkflowSnapshot } = await import( - './entities/instance-ai-workflow-snapshot.entity' - ); const { InstanceAiRunSnapshot } = await import('./entities/instance-ai-run-snapshot.entity'); const { InstanceAiIterationLog } = await import('./entities/instance-ai-iteration-log.entity'); const { InstanceAiCheckpoint } = await import('./entities/instance-ai-checkpoint.entity'); @@ -74,8 +64,6 @@ export class InstanceAiModule implements ModuleInterface { InstanceAiThread, InstanceAiMessage, InstanceAiResource, - InstanceAiObservationalMemory, - InstanceAiWorkflowSnapshot, InstanceAiRunSnapshot, InstanceAiIterationLog, InstanceAiCheckpoint, diff --git a/packages/cli/src/modules/instance-ai/repositories/index.ts b/packages/cli/src/modules/instance-ai/repositories/index.ts index 5543ff6572e..212c417d448 100644 --- a/packages/cli/src/modules/instance-ai/repositories/index.ts +++ b/packages/cli/src/modules/instance-ai/repositories/index.ts @@ -1,8 +1,6 @@ export { InstanceAiThreadRepository } from './instance-ai-thread.repository'; export { InstanceAiMessageRepository } from './instance-ai-message.repository'; export { InstanceAiResourceRepository } from './instance-ai-resource.repository'; -export { InstanceAiObservationalMemoryRepository } from './instance-ai-observational-memory.repository'; -export { InstanceAiWorkflowSnapshotRepository } from './instance-ai-workflow-snapshot.repository'; export { InstanceAiRunSnapshotRepository } from './instance-ai-run-snapshot.repository'; export { InstanceAiIterationLogRepository } from './instance-ai-iteration-log.repository'; export { InstanceAiCheckpointRepository } from './instance-ai-checkpoint.repository'; diff --git a/packages/cli/src/modules/instance-ai/repositories/instance-ai-observational-memory.repository.ts b/packages/cli/src/modules/instance-ai/repositories/instance-ai-observational-memory.repository.ts deleted file mode 100644 index 548db18db4d..00000000000 --- a/packages/cli/src/modules/instance-ai/repositories/instance-ai-observational-memory.repository.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Service } from '@n8n/di'; -import { DataSource, Repository } from '@n8n/typeorm'; - -import { InstanceAiObservationalMemory } from '../entities/instance-ai-observational-memory.entity'; - -@Service() -export class InstanceAiObservationalMemoryRepository extends Repository { - constructor(dataSource: DataSource) { - super(InstanceAiObservationalMemory, dataSource.manager); - } -} diff --git a/packages/cli/src/modules/instance-ai/repositories/instance-ai-workflow-snapshot.repository.ts b/packages/cli/src/modules/instance-ai/repositories/instance-ai-workflow-snapshot.repository.ts deleted file mode 100644 index 896e7618fbe..00000000000 --- a/packages/cli/src/modules/instance-ai/repositories/instance-ai-workflow-snapshot.repository.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Service } from '@n8n/di'; -import { DataSource, Repository } from '@n8n/typeorm'; - -import { InstanceAiWorkflowSnapshot } from '../entities/instance-ai-workflow-snapshot.entity'; - -@Service() -export class InstanceAiWorkflowSnapshotRepository extends Repository { - constructor(dataSource: DataSource) { - super(InstanceAiWorkflowSnapshot, dataSource.manager); - } -} diff --git a/packages/cli/src/modules/instance-ai/snapshot-pruning.service.ts b/packages/cli/src/modules/instance-ai/snapshot-pruning.service.ts deleted file mode 100644 index f1398c5becd..00000000000 --- a/packages/cli/src/modules/instance-ai/snapshot-pruning.service.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Logger } from '@n8n/backend-common'; -import { InstanceAiConfig } from '@n8n/config'; -import { OnLeaderStepdown, OnLeaderTakeover, OnShutdown } from '@n8n/decorators'; -import { Service } from '@n8n/di'; -import { LessThan } from '@n8n/typeorm'; -import { InstanceSettings } from 'n8n-core'; - -import { InstanceAiWorkflowSnapshotRepository } from './repositories/instance-ai-workflow-snapshot.repository'; - -@Service() -export class SnapshotPruningService { - private pruningInterval: NodeJS.Timeout | undefined; - - constructor( - private readonly logger: Logger, - private readonly config: InstanceAiConfig, - private readonly snapshotRepo: InstanceAiWorkflowSnapshotRepository, - private readonly instanceSettings: InstanceSettings, - ) { - this.logger = this.logger.scoped('instance-ai'); - } - - init() { - if (this.instanceSettings.isLeader) this.startPruning(); - } - - @OnLeaderTakeover() - startPruning() { - if (this.config.snapshotPruneInterval <= 0) return; - - this.pruningInterval = setInterval( - async () => await this.prune(), - this.config.snapshotPruneInterval, - ); - this.logger.debug('Started snapshot pruning timer', { - pruneIntervalMs: this.config.snapshotPruneInterval, - retentionMs: this.config.snapshotRetention, - }); - } - - @OnLeaderStepdown() - stopPruning() { - if (this.pruningInterval) { - clearInterval(this.pruningInterval); - this.pruningInterval = undefined; - this.logger.debug('Stopped snapshot pruning timer'); - } - } - - @OnShutdown() - shutdown() { - this.stopPruning(); - } - - async prune() { - const cutoff = new Date(Date.now() - this.config.snapshotRetention); - - const { affected } = await this.snapshotRepo.delete({ - updatedAt: LessThan(cutoff), - }); - - if (affected === 0) { - this.logger.debug('Found no workflow snapshots to prune'); - return; - } - - this.logger.debug('Pruned stale workflow snapshots', { count: affected }); - } -}