diff --git a/docs/generated/postgres-schema/README.md b/docs/generated/postgres-schema/README.md
index 8f6c07493fb..7b21b239c83 100644
--- a/docs/generated/postgres-schema/README.md
+++ b/docs/generated/postgres-schema/README.md
@@ -36,7 +36,7 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand.
| [public.agent_thread_grants](public.agent_thread_grants.md) | 4 | | BASE TABLE |
| [public.agent_version_skill_dependency](public.agent_version_skill_dependency.md) | 4 | | BASE TABLE |
| [public.agent_workflow_dependency](public.agent_workflow_dependency.md) | 3 | | BASE TABLE |
-| [public.agents](public.agents.md) | 14 | | BASE TABLE |
+| [public.agents](public.agents.md) | 15 | | BASE TABLE |
| [public.agents_memory_entries](public.agents_memory_entries.md) | 13 | | BASE TABLE |
| [public.agents_memory_entry_candidates](public.agents_memory_entry_candidates.md) | 14 | | BASE TABLE |
| [public.agents_memory_entry_locks](public.agents_memory_entry_locks.md) | 6 | | BASE TABLE |
@@ -240,7 +240,7 @@ erDiagram
"public.agent_version_skill_dependency" }o--|| "public.skill_version" : "FOREIGN KEY (#quot;skillVersionId#quot;) REFERENCES skill_version(id)"
"public.agent_workflow_dependency" }o--|| "public.workflow_entity" : "FOREIGN KEY (#quot;workflowId#quot;) REFERENCES workflow_entity(id) ON DELETE CASCADE"
"public.agent_workflow_dependency" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
-"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
+"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.agents" }o--o| "public.agent_history" : "FOREIGN KEY (#quot;activeVersionId#quot;) REFERENCES agent_history(#quot;versionId#quot;) ON DELETE SET NULL"
"public.agents_memory_entries" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
"public.agents_memory_entries" }o--|| "public.agents_resources" : "FOREIGN KEY (#quot;resourceId#quot;) REFERENCES agents_resources(id) ON DELETE CASCADE"
@@ -758,6 +758,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_background_job.md b/docs/generated/postgres-schema/public.agent_background_job.md
index ef567001ba6..86a106e09be 100644
--- a/docs/generated/postgres-schema/public.agent_background_job.md
+++ b/docs/generated/postgres-schema/public.agent_background_job.md
@@ -95,6 +95,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_channel_status.md b/docs/generated/postgres-schema/public.agent_channel_status.md
index a4ca4c92a92..ed2d659cc90 100644
--- a/docs/generated/postgres-schema/public.agent_channel_status.md
+++ b/docs/generated/postgres-schema/public.agent_channel_status.md
@@ -70,6 +70,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_chat_attachments.md b/docs/generated/postgres-schema/public.agent_chat_attachments.md
index fb9814667dc..c20b0b3340b 100644
--- a/docs/generated/postgres-schema/public.agent_chat_attachments.md
+++ b/docs/generated/postgres-schema/public.agent_chat_attachments.md
@@ -75,6 +75,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_chat_subscriptions.md b/docs/generated/postgres-schema/public.agent_chat_subscriptions.md
index fa4bf668e2b..d5aedff03ef 100644
--- a/docs/generated/postgres-schema/public.agent_chat_subscriptions.md
+++ b/docs/generated/postgres-schema/public.agent_chat_subscriptions.md
@@ -56,6 +56,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_checkpoints.md b/docs/generated/postgres-schema/public.agent_checkpoints.md
index ee4865a8032..3b71a13d0c0 100644
--- a/docs/generated/postgres-schema/public.agent_checkpoints.md
+++ b/docs/generated/postgres-schema/public.agent_checkpoints.md
@@ -58,6 +58,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_credential_dependency.md b/docs/generated/postgres-schema/public.agent_credential_dependency.md
index 243130ebf69..ab82c4e1ac0 100644
--- a/docs/generated/postgres-schema/public.agent_credential_dependency.md
+++ b/docs/generated/postgres-schema/public.agent_credential_dependency.md
@@ -49,6 +49,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_eval_dataset.md b/docs/generated/postgres-schema/public.agent_eval_dataset.md
index 272c34d8d52..cfaaeefcfdd 100644
--- a/docs/generated/postgres-schema/public.agent_eval_dataset.md
+++ b/docs/generated/postgres-schema/public.agent_eval_dataset.md
@@ -69,6 +69,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_execution_threads.md b/docs/generated/postgres-schema/public.agent_execution_threads.md
index 07f0bc8ef83..f63f8e89a50 100644
--- a/docs/generated/postgres-schema/public.agent_execution_threads.md
+++ b/docs/generated/postgres-schema/public.agent_execution_threads.md
@@ -102,6 +102,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_files.md b/docs/generated/postgres-schema/public.agent_files.md
index 382622ed0d4..25ea458e566 100644
--- a/docs/generated/postgres-schema/public.agent_files.md
+++ b/docs/generated/postgres-schema/public.agent_files.md
@@ -70,6 +70,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_history.md b/docs/generated/postgres-schema/public.agent_history.md
index f88d49c9688..efc11c1b7ba 100644
--- a/docs/generated/postgres-schema/public.agent_history.md
+++ b/docs/generated/postgres-schema/public.agent_history.md
@@ -67,6 +67,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_skill_dependency.md b/docs/generated/postgres-schema/public.agent_skill_dependency.md
index b799959e324..74fe035f6a3 100644
--- a/docs/generated/postgres-schema/public.agent_skill_dependency.md
+++ b/docs/generated/postgres-schema/public.agent_skill_dependency.md
@@ -53,6 +53,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_task_definition.md b/docs/generated/postgres-schema/public.agent_task_definition.md
index 6be5911a4c3..49a97c48ce4 100644
--- a/docs/generated/postgres-schema/public.agent_task_definition.md
+++ b/docs/generated/postgres-schema/public.agent_task_definition.md
@@ -61,6 +61,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_task_run_lock.md b/docs/generated/postgres-schema/public.agent_task_run_lock.md
index caece49a764..6901d998856 100644
--- a/docs/generated/postgres-schema/public.agent_task_run_lock.md
+++ b/docs/generated/postgres-schema/public.agent_task_run_lock.md
@@ -55,6 +55,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agent_workflow_dependency.md b/docs/generated/postgres-schema/public.agent_workflow_dependency.md
index 3dc1145a36e..89f365b5bc3 100644
--- a/docs/generated/postgres-schema/public.agent_workflow_dependency.md
+++ b/docs/generated/postgres-schema/public.agent_workflow_dependency.md
@@ -49,6 +49,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agents.md b/docs/generated/postgres-schema/public.agents.md
index 63aa85de828..3716bbeb0ca 100644
--- a/docs/generated/postgres-schema/public.agents.md
+++ b/docs/generated/postgres-schema/public.agents.md
@@ -10,9 +10,10 @@
| id | varchar(36) | | false | [public.agent_background_job](public.agent_background_job.md) [public.agent_channel_status](public.agent_channel_status.md) [public.agent_chat_attachments](public.agent_chat_attachments.md) [public.agent_chat_subscriptions](public.agent_chat_subscriptions.md) [public.agent_checkpoints](public.agent_checkpoints.md) [public.agent_credential_dependency](public.agent_credential_dependency.md) [public.agent_eval_dataset](public.agent_eval_dataset.md) [public.agent_execution_threads](public.agent_execution_threads.md) [public.agent_files](public.agent_files.md) [public.agent_history](public.agent_history.md) [public.agent_skill_dependency](public.agent_skill_dependency.md) [public.agent_task_definition](public.agent_task_definition.md) [public.agent_task_run_lock](public.agent_task_run_lock.md) [public.agent_workflow_dependency](public.agent_workflow_dependency.md) [public.agents_memory_entries](public.agents_memory_entries.md) [public.agents_memory_entry_candidates](public.agents_memory_entry_candidates.md) [public.agents_memory_entry_locks](public.agents_memory_entry_locks.md) [public.agents_memory_entry_sources](public.agents_memory_entry_sources.md) [public.agents_observation_cursors](public.agents_observation_cursors.md) [public.agents_observation_locks](public.agents_observation_locks.md) [public.agents_observations](public.agents_observations.md) [public.skill](public.skill.md) | | |
| integrations | json | '[]'::json | false | | | |
| name | varchar(128) | | false | | | |
-| projectId | varchar(255) | | false | | [public.project](public.project.md) | |
+| projectId | varchar(255) | | true | | [public.project](public.project.md) | |
| revision | integer | 0 | false | | | |
| schema | json | | true | | | |
+| scope | varchar(16) | 'project'::character varying | false | | | project: belongs to projectId; instance: code-defined, belongs to no project (projectId is null) |
| setupCompletedAt | timestamp(3) with time zone | | true | | | When this agent first reached a complete, publishable setup |
| skills | json | '{}'::json | false | | | |
| tools | json | '{}'::json | false | | | |
@@ -23,6 +24,8 @@
| Name | Type | Definition |
| ---- | ---- | ---------- |
+| CHK_agents_scope | CHECK | CHECK (((scope)::text = ANY ((ARRAY['project'::character varying, 'instance'::character varying])::text[]))) |
+| CHK_agents_scope_project | CHECK | CHECK ((((scope)::text = 'instance'::text) = ("projectId" IS NULL))) |
| FK_940597dfe9753375309ce6aeea0 | FOREIGN KEY | FOREIGN KEY ("activeVersionId") REFERENCES agent_history("versionId") ON DELETE SET NULL |
| FK_a30d560207c4071d98aa03c179c | FOREIGN KEY | FOREIGN KEY ("projectId") REFERENCES project(id) ON DELETE CASCADE |
| PK_9c653f28ae19c5884d5baf6a1d9 | PRIMARY KEY | PRIMARY KEY (id) |
@@ -31,8 +34,8 @@
| agents_id_not_null | n | NOT NULL id |
| agents_integrations_not_null | n | NOT NULL integrations |
| agents_name_not_null | n | NOT NULL name |
-| agents_projectId_not_null | n | NOT NULL "projectId" |
| agents_revision_not_null | n | NOT NULL revision |
+| agents_scope_not_null | n | NOT NULL scope |
| agents_skills_not_null | n | NOT NULL skills |
| agents_tools_not_null | n | NOT NULL tools |
| agents_updatedAt_not_null | n | NOT NULL "updatedAt" |
@@ -73,7 +76,7 @@ erDiagram
"public.agents_observation_locks" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
"public.agents_observations" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
"public.skill" }o--o| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
-"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
+"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.agents" {
varchar_36_ activeVersionId FK
@@ -85,6 +88,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agents_memory_entries.md b/docs/generated/postgres-schema/public.agents_memory_entries.md
index 2e4e740d314..c8a2716a406 100644
--- a/docs/generated/postgres-schema/public.agents_memory_entries.md
+++ b/docs/generated/postgres-schema/public.agents_memory_entries.md
@@ -82,6 +82,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_candidates.md b/docs/generated/postgres-schema/public.agents_memory_entry_candidates.md
index 256ab1bf2fb..91a01fb6a6e 100644
--- a/docs/generated/postgres-schema/public.agents_memory_entry_candidates.md
+++ b/docs/generated/postgres-schema/public.agents_memory_entry_candidates.md
@@ -91,6 +91,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_locks.md b/docs/generated/postgres-schema/public.agents_memory_entry_locks.md
index 19bd1c192aa..9c71ff2a020 100644
--- a/docs/generated/postgres-schema/public.agents_memory_entry_locks.md
+++ b/docs/generated/postgres-schema/public.agents_memory_entry_locks.md
@@ -58,6 +58,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agents_memory_entry_sources.md b/docs/generated/postgres-schema/public.agents_memory_entry_sources.md
index 951027ae410..f84f4c27118 100644
--- a/docs/generated/postgres-schema/public.agents_memory_entry_sources.md
+++ b/docs/generated/postgres-schema/public.agents_memory_entry_sources.md
@@ -80,6 +80,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agents_observation_cursors.md b/docs/generated/postgres-schema/public.agents_observation_cursors.md
index 3fb08a0e2dd..f8907721346 100644
--- a/docs/generated/postgres-schema/public.agents_observation_cursors.md
+++ b/docs/generated/postgres-schema/public.agents_observation_cursors.md
@@ -58,6 +58,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agents_observation_locks.md b/docs/generated/postgres-schema/public.agents_observation_locks.md
index 997e2d78046..8834ab68de5 100644
--- a/docs/generated/postgres-schema/public.agents_observation_locks.md
+++ b/docs/generated/postgres-schema/public.agents_observation_locks.md
@@ -62,6 +62,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.agents_observations.md b/docs/generated/postgres-schema/public.agents_observations.md
index cb6acde34ba..a8fda278622 100644
--- a/docs/generated/postgres-schema/public.agents_observations.md
+++ b/docs/generated/postgres-schema/public.agents_observations.md
@@ -81,6 +81,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.project.md b/docs/generated/postgres-schema/public.project.md
index 04d30b4ff68..7f114875982 100644
--- a/docs/generated/postgres-schema/public.project.md
+++ b/docs/generated/postgres-schema/public.project.md
@@ -42,7 +42,7 @@ erDiagram
"public.activity_event" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.agent_chat_attachments" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.agent_execution_threads" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
-"public.agents" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
+"public.agents" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.ai_preference" }o--o| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.data_table" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.folder" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
@@ -148,6 +148,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/postgres-schema/public.skill.md b/docs/generated/postgres-schema/public.skill.md
index 9b424ab5239..d644698182f 100644
--- a/docs/generated/postgres-schema/public.skill.md
+++ b/docs/generated/postgres-schema/public.skill.md
@@ -65,6 +65,7 @@ erDiagram
varchar_255_ projectId FK
integer revision
json schema
+ varchar_16_ scope
timestamp_3__with_time_zone setupCompletedAt
json skills
json tools
diff --git a/docs/generated/sqlite-schema/README.md b/docs/generated/sqlite-schema/README.md
index 6e946dc580d..19db9d7643f 100644
--- a/docs/generated/sqlite-schema/README.md
+++ b/docs/generated/sqlite-schema/README.md
@@ -36,7 +36,7 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand.
| [agent_thread_grants](agent_thread_grants.md) | 4 | | table |
| [agent_version_skill_dependency](agent_version_skill_dependency.md) | 4 | | table |
| [agent_workflow_dependency](agent_workflow_dependency.md) | 3 | | table |
-| [agents](agents.md) | 14 | | table |
+| [agents](agents.md) | 15 | | table |
| [agents_memory_entries](agents_memory_entries.md) | 13 | | table |
| [agents_memory_entry_candidates](agents_memory_entry_candidates.md) | 14 | | table |
| [agents_memory_entry_locks](agents_memory_entry_locks.md) | 6 | | table |
@@ -222,7 +222,7 @@ erDiagram
"agent_version_skill_dependency" |o--|| "agent_history" : "FOREIGN KEY (agentVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agent_workflow_dependency" |o--|| "workflow_entity" : "FOREIGN KEY (workflowId) REFERENCES workflow_entity (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agent_workflow_dependency" |o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
-"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
+"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agents" }o--o| "agent_history" : "FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"agents_memory_entries" }o--o| "agents_memory_entries" : "FOREIGN KEY (supersededBy) REFERENCES agents_memory_entries (id) ON UPDATE NO ACTION ON DELETE NO ACTION MATCH NONE"
"agents_memory_entries" }o--|| "agents_resources" : "FOREIGN KEY (resourceId) REFERENCES agents_resources (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
@@ -744,6 +744,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_background_job.md b/docs/generated/sqlite-schema/agent_background_job.md
index f7e22cd9e97..ecb211cb620 100644
--- a/docs/generated/sqlite-schema/agent_background_job.md
+++ b/docs/generated/sqlite-schema/agent_background_job.md
@@ -97,6 +97,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_channel_status.md b/docs/generated/sqlite-schema/agent_channel_status.md
index b41e3f3ab51..2477a1c83f6 100644
--- a/docs/generated/sqlite-schema/agent_channel_status.md
+++ b/docs/generated/sqlite-schema/agent_channel_status.md
@@ -77,6 +77,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_chat_attachments.md b/docs/generated/sqlite-schema/agent_chat_attachments.md
index beae5589f66..295a3024ceb 100644
--- a/docs/generated/sqlite-schema/agent_chat_attachments.md
+++ b/docs/generated/sqlite-schema/agent_chat_attachments.md
@@ -77,6 +77,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_chat_subscriptions.md b/docs/generated/sqlite-schema/agent_chat_subscriptions.md
index 9a2c0b2539a..84ed7ad5bc6 100644
--- a/docs/generated/sqlite-schema/agent_chat_subscriptions.md
+++ b/docs/generated/sqlite-schema/agent_chat_subscriptions.md
@@ -65,6 +65,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_checkpoints.md b/docs/generated/sqlite-schema/agent_checkpoints.md
index 3af00512ccc..9b964420d53 100644
--- a/docs/generated/sqlite-schema/agent_checkpoints.md
+++ b/docs/generated/sqlite-schema/agent_checkpoints.md
@@ -66,6 +66,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_credential_dependency.md b/docs/generated/sqlite-schema/agent_credential_dependency.md
index 0d7b62f90bd..5d65426e1cf 100644
--- a/docs/generated/sqlite-schema/agent_credential_dependency.md
+++ b/docs/generated/sqlite-schema/agent_credential_dependency.md
@@ -59,6 +59,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_eval_dataset.md b/docs/generated/sqlite-schema/agent_eval_dataset.md
index ecc23c2cc1d..1fca253a5c5 100644
--- a/docs/generated/sqlite-schema/agent_eval_dataset.md
+++ b/docs/generated/sqlite-schema/agent_eval_dataset.md
@@ -74,6 +74,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_execution_threads.md b/docs/generated/sqlite-schema/agent_execution_threads.md
index db855a5098d..3174f435166 100644
--- a/docs/generated/sqlite-schema/agent_execution_threads.md
+++ b/docs/generated/sqlite-schema/agent_execution_threads.md
@@ -102,6 +102,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_files.md b/docs/generated/sqlite-schema/agent_files.md
index 30c1e4dd3ba..2aff17b7b96 100644
--- a/docs/generated/sqlite-schema/agent_files.md
+++ b/docs/generated/sqlite-schema/agent_files.md
@@ -73,6 +73,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_history.md b/docs/generated/sqlite-schema/agent_history.md
index b157fab3042..f8e84c9c323 100644
--- a/docs/generated/sqlite-schema/agent_history.md
+++ b/docs/generated/sqlite-schema/agent_history.md
@@ -74,6 +74,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_skill_dependency.md b/docs/generated/sqlite-schema/agent_skill_dependency.md
index 114d3a3998e..c341daa1073 100644
--- a/docs/generated/sqlite-schema/agent_skill_dependency.md
+++ b/docs/generated/sqlite-schema/agent_skill_dependency.md
@@ -63,6 +63,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_task_definition.md b/docs/generated/sqlite-schema/agent_task_definition.md
index 48bac946715..bbfe9b09fe3 100644
--- a/docs/generated/sqlite-schema/agent_task_definition.md
+++ b/docs/generated/sqlite-schema/agent_task_definition.md
@@ -66,6 +66,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_task_run_lock.md b/docs/generated/sqlite-schema/agent_task_run_lock.md
index 4645d32fa85..c194d594998 100644
--- a/docs/generated/sqlite-schema/agent_task_run_lock.md
+++ b/docs/generated/sqlite-schema/agent_task_run_lock.md
@@ -62,6 +62,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agent_workflow_dependency.md b/docs/generated/sqlite-schema/agent_workflow_dependency.md
index 4725ec273ca..5b74a67a461 100644
--- a/docs/generated/sqlite-schema/agent_workflow_dependency.md
+++ b/docs/generated/sqlite-schema/agent_workflow_dependency.md
@@ -59,6 +59,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agents.md b/docs/generated/sqlite-schema/agents.md
index b7feda944a9..3c29aeabe19 100644
--- a/docs/generated/sqlite-schema/agents.md
+++ b/docs/generated/sqlite-schema/agents.md
@@ -6,7 +6,7 @@
Table Definition
```sql
-CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128) NOT NULL, "projectId" varchar(255) NOT NULL, "integrations" text NOT NULL DEFAULT ('[]'), "schema" text, "tools" text NOT NULL DEFAULT ('{}'), "skills" text NOT NULL DEFAULT ('{}'), "versionId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "activeVersionId" varchar(36), "availableInMCP" boolean NOT NULL DEFAULT (false), "setupCompletedAt" datetime(3), "revision" integer NOT NULL DEFAULT 0, CONSTRAINT "FK_940597dfe9753375309ce6aeea0" FOREIGN KEY ("activeVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_a30d560207c4071d98aa03c179c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)
+CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128) NOT NULL, "projectId" varchar(255), "integrations" text NOT NULL DEFAULT ('[]'), "schema" text, "tools" text NOT NULL DEFAULT ('{}'), "skills" text NOT NULL DEFAULT ('{}'), "versionId" varchar(36), "createdAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "updatedAt" datetime(3) NOT NULL DEFAULT (STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')), "activeVersionId" varchar(36), "availableInMCP" boolean NOT NULL DEFAULT (false), "setupCompletedAt" datetime(3), "revision" integer NOT NULL DEFAULT (0), "scope" varchar(16) NOT NULL DEFAULT ('project'), CONSTRAINT "CHK_agents_scope" CHECK (("scope" IN ('project', 'instance'))), CONSTRAINT "CHK_agents_scope_project" CHECK (("scope" = 'instance') = ("projectId" IS NULL)), CONSTRAINT "FK_940597dfe9753375309ce6aeea0" FOREIGN KEY ("activeVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_a30d560207c4071d98aa03c179c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)
```
@@ -21,9 +21,10 @@ CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128
| id | varchar(36) | | false | [agent_background_job](agent_background_job.md) [agent_channel_status](agent_channel_status.md) [agent_chat_attachments](agent_chat_attachments.md) [agent_chat_subscriptions](agent_chat_subscriptions.md) [agent_checkpoints](agent_checkpoints.md) [agent_credential_dependency](agent_credential_dependency.md) [agent_eval_dataset](agent_eval_dataset.md) [agent_execution_threads](agent_execution_threads.md) [agent_files](agent_files.md) [agent_history](agent_history.md) [agent_skill_dependency](agent_skill_dependency.md) [agent_task_definition](agent_task_definition.md) [agent_task_run_lock](agent_task_run_lock.md) [agent_workflow_dependency](agent_workflow_dependency.md) [agents_memory_entries](agents_memory_entries.md) [agents_memory_entry_candidates](agents_memory_entry_candidates.md) [agents_memory_entry_locks](agents_memory_entry_locks.md) [agents_memory_entry_sources](agents_memory_entry_sources.md) [agents_observation_cursors](agents_observation_cursors.md) [agents_observation_locks](agents_observation_locks.md) [agents_observations](agents_observations.md) [skill](skill.md) | | |
| integrations | TEXT | '[]' | false | | | |
| name | varchar(128) | | false | | | |
-| projectId | varchar(255) | | false | | [project](project.md) | |
+| projectId | varchar(255) | | true | | [project](project.md) | |
| revision | INTEGER | 0 | false | | | |
| schema | TEXT | | true | | | |
+| scope | varchar(16) | 'project' | false | | | |
| setupCompletedAt | datetime(3) | | true | | | |
| skills | TEXT | '{}' | false | | | |
| tools | TEXT | '{}' | false | | | |
@@ -34,6 +35,8 @@ CREATE TABLE "agents" ("id" varchar(36) PRIMARY KEY NOT NULL, "name" varchar(128
| Name | Type | Definition |
| ---- | ---- | ---------- |
+| - | CHECK | CHECK (("scope" IN ('project', 'instance'))) |
+| - | CHECK | CHECK (("scope" = 'instance') = ("projectId" IS NULL)) |
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (activeVersionId) REFERENCES agent_history (versionId) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
| id | PRIMARY KEY | PRIMARY KEY (id) |
@@ -75,7 +78,7 @@ erDiagram
"agents_observation_locks" |o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agents_observations" }o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"skill" }o--o| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
-"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
+"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agents" {
varchar_36_ activeVersionId FK
@@ -87,6 +90,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agents_memory_entries.md b/docs/generated/sqlite-schema/agents_memory_entries.md
index 7879d5a3d33..5094019bcdb 100644
--- a/docs/generated/sqlite-schema/agents_memory_entries.md
+++ b/docs/generated/sqlite-schema/agents_memory_entries.md
@@ -85,6 +85,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agents_memory_entry_candidates.md b/docs/generated/sqlite-schema/agents_memory_entry_candidates.md
index 7694fb712b9..8879e4773b2 100644
--- a/docs/generated/sqlite-schema/agents_memory_entry_candidates.md
+++ b/docs/generated/sqlite-schema/agents_memory_entry_candidates.md
@@ -90,6 +90,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agents_memory_entry_locks.md b/docs/generated/sqlite-schema/agents_memory_entry_locks.md
index be2dd9e55e3..fdc863f4813 100644
--- a/docs/generated/sqlite-schema/agents_memory_entry_locks.md
+++ b/docs/generated/sqlite-schema/agents_memory_entry_locks.md
@@ -65,6 +65,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agents_memory_entry_sources.md b/docs/generated/sqlite-schema/agents_memory_entry_sources.md
index 485afc513a9..d76d2acd15e 100644
--- a/docs/generated/sqlite-schema/agents_memory_entry_sources.md
+++ b/docs/generated/sqlite-schema/agents_memory_entry_sources.md
@@ -84,6 +84,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agents_observation_cursors.md b/docs/generated/sqlite-schema/agents_observation_cursors.md
index ee80c6bd989..ce4df78da7d 100644
--- a/docs/generated/sqlite-schema/agents_observation_cursors.md
+++ b/docs/generated/sqlite-schema/agents_observation_cursors.md
@@ -65,6 +65,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agents_observation_locks.md b/docs/generated/sqlite-schema/agents_observation_locks.md
index 6a9d479c37e..bb9549de9f6 100644
--- a/docs/generated/sqlite-schema/agents_observation_locks.md
+++ b/docs/generated/sqlite-schema/agents_observation_locks.md
@@ -69,6 +69,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/agents_observations.md b/docs/generated/sqlite-schema/agents_observations.md
index c72bd8bd06e..2c63954a9aa 100644
--- a/docs/generated/sqlite-schema/agents_observations.md
+++ b/docs/generated/sqlite-schema/agents_observations.md
@@ -84,6 +84,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/project.md b/docs/generated/sqlite-schema/project.md
index 46f39c7fb0b..6dd22d0d03f 100644
--- a/docs/generated/sqlite-schema/project.md
+++ b/docs/generated/sqlite-schema/project.md
@@ -48,7 +48,7 @@ erDiagram
"activity_event" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agent_chat_attachments" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agent_execution_threads" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
-"agents" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
+"agents" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"ai_preference" }o--o| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"data_table" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"folder" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
@@ -154,6 +154,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/docs/generated/sqlite-schema/skill.md b/docs/generated/sqlite-schema/skill.md
index 8be4273cb95..d26335ea399 100644
--- a/docs/generated/sqlite-schema/skill.md
+++ b/docs/generated/sqlite-schema/skill.md
@@ -73,6 +73,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER revision
TEXT schema
+ varchar_16_ scope
datetime_3_ setupCompletedAt
TEXT skills
TEXT tools
diff --git a/packages/@n8n/db/src/migrations/common/1792051765225-AddInstanceScopeToAgents.ts b/packages/@n8n/db/src/migrations/common/1792051765225-AddInstanceScopeToAgents.ts
new file mode 100644
index 00000000000..f00bb8dc974
--- /dev/null
+++ b/packages/@n8n/db/src/migrations/common/1792051765225-AddInstanceScopeToAgents.ts
@@ -0,0 +1,74 @@
+import { TableCheck } from '@n8n/typeorm';
+
+import type { MigrationContext, ReversibleMigration } from '../migration-types';
+
+const table = 'agents';
+const scopeCheck = 'agents_scope_project';
+
+/**
+ * Adds instance-scoped agents. An instance agent belongs to no project, so
+ * its `projectId` is null. Its runtime comes from code, and each of its
+ * threads carries its own working project. A CHECK constraint makes sure that
+ * `projectId` is null exactly for instance agents.
+ */
+export class AddInstanceScopeToAgents1792051765225 implements ReversibleMigration {
+ async up(context: MigrationContext) {
+ const {
+ schemaBuilder: { addColumns, column, dropNotNull },
+ } = context;
+ await addColumns(
+ table,
+ [
+ column('scope')
+ .varchar(16)
+ .notNull.default("'project'")
+ .withEnumCheck(['project', 'instance'])
+ .comment(
+ 'project: belongs to projectId; instance: code-defined, belongs to no project (projectId is null)',
+ ),
+ ],
+ { recreatesOnSqlite: true },
+ );
+ await dropNotNull(table, 'projectId', { recreatesOnSqlite: true });
+ await this.addScopeCheck(context);
+ }
+
+ async down(context: MigrationContext) {
+ const {
+ schemaBuilder: { addNotNull, dropColumns, dropEnumCheck },
+ } = context;
+ // The old schema cannot store an agent without a project.
+ // `DeleteInstanceAgentsOnRevert` already deleted these agents and their
+ // dependent rows with foreign keys on. This delete is a fallback.
+ await this.deleteInstanceAgents(context);
+ await this.dropScopeCheck(context);
+ await addNotNull(table, 'projectId', { recreatesOnSqlite: true });
+ await dropEnumCheck(table, 'scope', { recreatesOnSqlite: true });
+ await dropColumns(table, ['scope'], { recreatesOnSqlite: true });
+ }
+
+ private async addScopeCheck({ escape, queryRunner, tablePrefix }: MigrationContext) {
+ const scope = escape.columnName('scope');
+ const projectId = escape.columnName('projectId');
+ await queryRunner.createCheckConstraint(
+ `${tablePrefix}${table}`,
+ new TableCheck({
+ name: `CHK_${tablePrefix}${scopeCheck}`,
+ expression: `(${scope} = 'instance') = (${projectId} IS NULL)`,
+ }),
+ );
+ }
+
+ private async dropScopeCheck({ queryRunner, tablePrefix }: MigrationContext) {
+ await queryRunner.dropCheckConstraint(
+ `${tablePrefix}${table}`,
+ `CHK_${tablePrefix}${scopeCheck}`,
+ );
+ }
+
+ private async deleteInstanceAgents({ escape, runQuery }: MigrationContext) {
+ await runQuery(
+ `DELETE FROM ${escape.tableName(table)} WHERE ${escape.columnName('projectId')} IS NULL`,
+ );
+ }
+}
diff --git a/packages/@n8n/db/src/migrations/common/1792139680416-DeleteInstanceAgentsOnRevert.ts b/packages/@n8n/db/src/migrations/common/1792139680416-DeleteInstanceAgentsOnRevert.ts
new file mode 100644
index 00000000000..06ecbbfd640
--- /dev/null
+++ b/packages/@n8n/db/src/migrations/common/1792139680416-DeleteInstanceAgentsOnRevert.ts
@@ -0,0 +1,19 @@
+import type { MigrationContext, ReversibleMigration } from '../migration-types';
+
+/**
+ * Runs before `AddInstanceScopeToAgents` on revert. That migration recreates
+ * `agents` with foreign keys off on SQLite, so deleting instance agents there
+ * does not cascade. This migration keeps foreign keys on and deletes them
+ * first, so the cascades also delete the rows that depend on them.
+ */
+export class DeleteInstanceAgentsOnRevert1792139680416 implements ReversibleMigration {
+ async up() {
+ // Nothing to change on upgrade. This migration only does work on revert.
+ }
+
+ async down({ escape, runQuery }: MigrationContext) {
+ await runQuery(
+ `DELETE FROM ${escape.tableName('agents')} WHERE ${escape.columnName('scope')} = 'instance'`,
+ );
+ }
+}
diff --git a/packages/@n8n/db/src/migrations/sqlite/1792051765225-AddInstanceScopeToAgents.ts b/packages/@n8n/db/src/migrations/sqlite/1792051765225-AddInstanceScopeToAgents.ts
new file mode 100644
index 00000000000..64811b8367f
--- /dev/null
+++ b/packages/@n8n/db/src/migrations/sqlite/1792051765225-AddInstanceScopeToAgents.ts
@@ -0,0 +1,11 @@
+import { AddInstanceScopeToAgents1792051765225 as BaseMigration } from '../common/1792051765225-AddInstanceScopeToAgents';
+
+/**
+ * `agents` has incoming cascading foreign keys. Recreating it must not delete
+ * their rows, so foreign keys are off for this migration. On revert,
+ * `DeleteInstanceAgentsOnRevert` deletes instance agents and their dependent
+ * rows first, with foreign keys on.
+ */
+export class AddInstanceScopeToAgents1792051765225 extends BaseMigration {
+ withFKsDisabled = true as const;
+}
diff --git a/packages/cli/test/migration/1792051765225-add-instance-scope-to-agents.test.ts b/packages/cli/test/migration/1792051765225-add-instance-scope-to-agents.test.ts
new file mode 100644
index 00000000000..b9cd0353b46
--- /dev/null
+++ b/packages/cli/test/migration/1792051765225-add-instance-scope-to-agents.test.ts
@@ -0,0 +1,202 @@
+import {
+ createTestMigrationContext,
+ initDbUpToMigration,
+ runSingleMigration,
+ undoLastSingleMigration,
+ type TestMigrationContext,
+} from '@n8n/backend-test-utils';
+import { DbConnection } from '@n8n/db';
+import { Container } from '@n8n/di';
+import { DataSource } from '@n8n/typeorm';
+import { randomUUID } from 'node:crypto';
+
+const migrationName = 'AddInstanceScopeToAgents1792051765225';
+
+describe('AddInstanceScopeToAgents migration', () => {
+ let dataSource: DataSource;
+
+ beforeAll(async () => {
+ await Container.get(DbConnection).init();
+ dataSource = Container.get(DataSource);
+ });
+
+ beforeEach(async () => {
+ await withContext(async ({ queryRunner }) => await queryRunner.clearDatabase());
+ await initDbUpToMigration(migrationName);
+ });
+
+ afterAll(async () => {
+ await Container.get(DbConnection).close();
+ });
+
+ async function withContext(run: (context: TestMigrationContext) => Promise): Promise {
+ const context = createTestMigrationContext(dataSource);
+ try {
+ return await run(context);
+ } finally {
+ await context.queryRunner.release();
+ }
+ }
+
+ function columns(context: TestMigrationContext, names: string[]) {
+ return names.map((name) => context.escape.columnName(name)).join(', ');
+ }
+
+ async function insertProject(context: TestMigrationContext) {
+ const id = randomUUID();
+ await context.runQuery(
+ `INSERT INTO ${context.escape.tableName('project')} (${columns(context, ['id', 'name', 'type', 'createdAt', 'updatedAt'])})
+ VALUES (:id, 'Agents project', 'team', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
+ { id },
+ );
+ return id;
+ }
+
+ async function insertAgent(
+ context: TestMigrationContext,
+ projectId: string | null,
+ scope?: string,
+ ) {
+ const id = randomUUID();
+ const names = ['id', 'name', 'projectId', 'createdAt', 'updatedAt'];
+ const values = [':id', "'Agent'", ':projectId', 'CURRENT_TIMESTAMP', 'CURRENT_TIMESTAMP'];
+ if (scope !== undefined) {
+ names.push('scope');
+ values.push(':scope');
+ }
+ await context.runQuery(
+ `INSERT INTO ${context.escape.tableName('agents')} (${columns(context, names)}) VALUES (${values.join(', ')})`,
+ { id, projectId, scope },
+ );
+ return id;
+ }
+
+ /** A thread with one execution: rows that depend on the agent at two levels. */
+ async function insertThreadWithExecution(
+ context: TestMigrationContext,
+ agentId: string,
+ projectId: string,
+ ) {
+ const threadId = randomUUID();
+ await context.runQuery(
+ `INSERT INTO ${context.escape.tableName('agent_execution_threads')}
+ (${columns(context, ['id', 'agentId', 'agentName', 'projectId', 'createdAt', 'updatedAt'])})
+ VALUES (:threadId, :agentId, 'Agent', :projectId, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
+ { threadId, agentId, projectId },
+ );
+ await context.runQuery(
+ `INSERT INTO ${context.escape.tableName('agent_execution')}
+ (${columns(context, ['id', 'threadId', 'status', 'createdAt', 'updatedAt'])})
+ VALUES (:id, :threadId, 'success', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
+ { id: randomUUID(), threadId },
+ );
+ return threadId;
+ }
+
+ async function readIds(context: TestMigrationContext, table: string) {
+ const rows = await context.runQuery>(
+ `SELECT ${context.escape.columnName('id')} AS id FROM ${context.escape.tableName(table)}`,
+ );
+ return rows.map((row) => row.id).sort();
+ }
+
+ async function readAgent(context: TestMigrationContext, id: string) {
+ const [row] = await context.runQuery>(
+ `SELECT ${columns(context, ['scope', 'projectId'])} FROM ${context.escape.tableName('agents')} WHERE ${context.escape.columnName('id')} = :id`,
+ { id },
+ );
+ return row;
+ }
+
+ it('marks existing agents as project agents and keeps their dependent rows', async () => {
+ const { agentId, threadId } = await withContext(async (context) => {
+ const projectId = await insertProject(context);
+ const agentId = await insertAgent(context, projectId);
+ const threadId = await insertThreadWithExecution(context, agentId, projectId);
+ return { agentId, threadId };
+ });
+
+ await runSingleMigration(migrationName);
+
+ await withContext(async (context) => {
+ expect(await readAgent(context, agentId)).toMatchObject({ scope: 'project' });
+ expect(await readIds(context, 'agent_execution_threads')).toEqual([threadId]);
+ expect(await readIds(context, 'agent_execution')).toHaveLength(1);
+ });
+ });
+
+ it('stores an instance agent without a project and rejects an unknown scope', async () => {
+ await runSingleMigration(migrationName);
+
+ await withContext(async (context) => {
+ const agentId = await insertAgent(context, null, 'instance');
+ expect(await readAgent(context, agentId)).toEqual({ scope: 'instance', projectId: null });
+
+ const projectId = await insertProject(context);
+ await expect(insertAgent(context, projectId, 'global')).rejects.toThrow();
+ });
+ });
+
+ it('rejects a scope that does not match the project', async () => {
+ await runSingleMigration(migrationName);
+
+ await withContext(async (context) => {
+ const projectId = await insertProject(context);
+ // A project agent needs a project.
+ await expect(insertAgent(context, null)).rejects.toThrow();
+ await expect(insertAgent(context, null, 'project')).rejects.toThrow();
+ // An instance agent belongs to no project.
+ await expect(insertAgent(context, projectId, 'instance')).rejects.toThrow();
+ expect(await readIds(context, 'agents')).toEqual([]);
+ });
+ });
+
+ it('rejects an agent without a project before the migration', async () => {
+ await withContext(async (context) => {
+ await expect(insertAgent(context, null)).rejects.toThrow();
+ });
+ });
+
+ it('deletes instance agents on revert, and keeps project agents with their rows', async () => {
+ await runSingleMigration(migrationName);
+ const kept = await withContext(async (context) => {
+ const projectId = await insertProject(context);
+ const projectAgentId = await insertAgent(context, projectId);
+ const projectThreadId = await insertThreadWithExecution(context, projectAgentId, projectId);
+ // DeleteInstanceAgentsOnRevert deletes the rows that depend on instance agents first.
+ await insertAgent(context, null, 'instance');
+ return { projectAgentId, projectThreadId };
+ });
+
+ await undoLastSingleMigration();
+
+ await withContext(async (context) => {
+ expect(await readIds(context, 'agents')).toEqual([kept.projectAgentId]);
+ expect(await readIds(context, 'agent_execution_threads')).toEqual([kept.projectThreadId]);
+ expect(await readIds(context, 'agent_execution')).toHaveLength(1);
+ if (context.isSqlite) {
+ expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]);
+ }
+ await expect(insertAgent(context, null)).rejects.toThrow();
+ // The foreign keys still hold after the table was recreated.
+ await expect(
+ insertThreadWithExecution(context, randomUUID(), await insertProject(context)),
+ ).rejects.toThrow();
+ });
+ });
+
+ it('can apply again after a revert', async () => {
+ const agentId = await withContext(async (context) => {
+ return await insertAgent(context, await insertProject(context));
+ });
+
+ await runSingleMigration(migrationName);
+ await undoLastSingleMigration();
+ await runSingleMigration(migrationName);
+
+ await withContext(async (context) => {
+ expect(await readAgent(context, agentId)).toMatchObject({ scope: 'project' });
+ await expect(insertAgent(context, null, 'instance')).resolves.toBeDefined();
+ });
+ });
+});
diff --git a/packages/cli/test/migration/1792139680416-delete-instance-agents-on-revert.test.ts b/packages/cli/test/migration/1792139680416-delete-instance-agents-on-revert.test.ts
new file mode 100644
index 00000000000..ece01255810
--- /dev/null
+++ b/packages/cli/test/migration/1792139680416-delete-instance-agents-on-revert.test.ts
@@ -0,0 +1,138 @@
+import {
+ createTestMigrationContext,
+ initDbUpToMigration,
+ runSingleMigration,
+ undoLastSingleMigration,
+ type TestMigrationContext,
+} from '@n8n/backend-test-utils';
+import { DbConnection } from '@n8n/db';
+import { Container } from '@n8n/di';
+import { DataSource } from '@n8n/typeorm';
+import { randomUUID } from 'node:crypto';
+
+const migrationName = 'DeleteInstanceAgentsOnRevert1792139680416';
+
+describe('DeleteInstanceAgentsOnRevert migration', () => {
+ let dataSource: DataSource;
+
+ beforeAll(async () => {
+ await Container.get(DbConnection).init();
+ dataSource = Container.get(DataSource);
+ });
+
+ beforeEach(async () => {
+ await withContext(async ({ queryRunner }) => await queryRunner.clearDatabase());
+ await initDbUpToMigration(migrationName);
+ });
+
+ afterAll(async () => {
+ await Container.get(DbConnection).close();
+ });
+
+ async function withContext(run: (context: TestMigrationContext) => Promise): Promise {
+ const context = createTestMigrationContext(dataSource);
+ try {
+ return await run(context);
+ } finally {
+ await context.queryRunner.release();
+ }
+ }
+
+ function columns(context: TestMigrationContext, names: string[]) {
+ return names.map((name) => context.escape.columnName(name)).join(', ');
+ }
+
+ async function insertProject(context: TestMigrationContext) {
+ const id = randomUUID();
+ await context.runQuery(
+ `INSERT INTO ${context.escape.tableName('project')} (${columns(context, ['id', 'name', 'type', 'createdAt', 'updatedAt'])})
+ VALUES (:id, 'Agents project', 'team', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
+ { id },
+ );
+ return id;
+ }
+
+ async function insertAgent(context: TestMigrationContext, projectId: string | null) {
+ const id = randomUUID();
+ const scope = projectId === null ? 'instance' : 'project';
+ await context.runQuery(
+ `INSERT INTO ${context.escape.tableName('agents')} (${columns(context, ['id', 'name', 'projectId', 'scope', 'createdAt', 'updatedAt'])})
+ VALUES (:id, 'Agent', :projectId, :scope, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
+ { id, projectId, scope },
+ );
+ return id;
+ }
+
+ /** A thread with one execution: rows that depend on the agent at two levels. */
+ async function insertThreadWithExecution(
+ context: TestMigrationContext,
+ agentId: string,
+ projectId: string,
+ ) {
+ const threadId = randomUUID();
+ const executionId = randomUUID();
+ await context.runQuery(
+ `INSERT INTO ${context.escape.tableName('agent_execution_threads')}
+ (${columns(context, ['id', 'agentId', 'agentName', 'projectId', 'createdAt', 'updatedAt'])})
+ VALUES (:threadId, :agentId, 'Agent', :projectId, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
+ { threadId, agentId, projectId },
+ );
+ await context.runQuery(
+ `INSERT INTO ${context.escape.tableName('agent_execution')}
+ (${columns(context, ['id', 'threadId', 'status', 'createdAt', 'updatedAt'])})
+ VALUES (:executionId, :threadId, 'success', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
+ { executionId, threadId },
+ );
+ return { threadId, executionId };
+ }
+
+ async function readIds(context: TestMigrationContext, table: string) {
+ const rows = await context.runQuery>(
+ `SELECT ${context.escape.columnName('id')} AS id FROM ${context.escape.tableName(table)}`,
+ );
+ return rows.map((row) => row.id).sort();
+ }
+
+ it('deletes instance agents and their dependent rows on revert', async () => {
+ await runSingleMigration(migrationName);
+ const kept = await withContext(async (context) => {
+ const projectId = await insertProject(context);
+ const projectAgentId = await insertAgent(context, projectId);
+ const projectRows = await insertThreadWithExecution(context, projectAgentId, projectId);
+ const instanceAgentId = await insertAgent(context, null);
+ await insertThreadWithExecution(context, instanceAgentId, projectId);
+ return { projectAgentId, ...projectRows };
+ });
+
+ await undoLastSingleMigration();
+
+ await withContext(async (context) => {
+ expect(await readIds(context, 'agents')).toEqual([kept.projectAgentId]);
+ expect(await readIds(context, 'agent_execution_threads')).toEqual([kept.threadId]);
+ expect(await readIds(context, 'agent_execution')).toEqual([kept.executionId]);
+ if (context.isSqlite) {
+ expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]);
+ }
+ });
+ });
+
+ it('leaves no orphan rows after both migrations are reverted', async () => {
+ await runSingleMigration(migrationName);
+ await withContext(async (context) => {
+ const projectId = await insertProject(context);
+ await insertThreadWithExecution(context, await insertAgent(context, null), projectId);
+ });
+
+ await undoLastSingleMigration();
+ await undoLastSingleMigration();
+
+ await withContext(async (context) => {
+ expect(await readIds(context, 'agents')).toEqual([]);
+ expect(await readIds(context, 'agent_execution_threads')).toEqual([]);
+ expect(await readIds(context, 'agent_execution')).toEqual([]);
+ if (context.isSqlite) {
+ expect(await context.runQuery('PRAGMA foreign_key_check')).toEqual([]);
+ }
+ });
+ });
+});