This commit is contained in:
Eugene 2026-10-16 18:54:28 +02:00 committed by GitHub
commit 026ed3855c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 846 additions and 13 deletions

View File

@ -9,7 +9,7 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand.
| Name | Columns | Comment | Type |
| ---- | ------- | ------- | ---- |
| [public.activity_event](public.activity_event.md) | 11 | | BASE TABLE |
| [public.agent_background_job](public.agent_background_job.md) | 20 | | BASE TABLE |
| [public.agent_background_job](public.agent_background_job.md) | 23 | | BASE TABLE |
| [public.agent_budget_applied_call](public.agent_budget_applied_call.md) | 2 | | BASE TABLE |
| [public.agent_budget_spend](public.agent_budget_spend.md) | 4 | | BASE TABLE |
| [public.agent_channel_status](public.agent_channel_status.md) | 11 | | BASE TABLE |
@ -23,7 +23,7 @@ Auto-generated from the PostgreSQL migrations in @n8n/db. Do not edit by hand.
| [public.agent_eval_run](public.agent_eval_run.md) | 14 | | BASE TABLE |
| [public.agent_execution](public.agent_execution.md) | 23 | | BASE TABLE |
| [public.agent_execution_message_links](public.agent_execution_message_links.md) | 5 | | BASE TABLE |
| [public.agent_execution_threads](public.agent_execution_threads.md) | 19 | | BASE TABLE |
| [public.agent_execution_threads](public.agent_execution_threads.md) | 20 | | BASE TABLE |
| [public.agent_files](public.agent_files.md) | 10 | | BASE TABLE |
| [public.agent_history](public.agent_history.md) | 9 | | BASE TABLE |
| [public.agent_message_queue](public.agent_message_queue.md) | 10 | | BASE TABLE |
@ -200,6 +200,8 @@ erDiagram
"public.activity_event" }o--o| "public.user" : "FOREIGN KEY (#quot;userId#quot;) REFERENCES #quot;user#quot;(id) ON DELETE SET NULL"
"public.activity_event" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.agent_background_job" }o--|| "public.agents" : "FOREIGN KEY (#quot;parentAgentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
"public.agent_background_job" }o--o| "public.agent_execution" : "FOREIGN KEY (#quot;sourceExecutionId#quot;) REFERENCES agent_execution(id) ON DELETE SET NULL"
"public.agent_background_job" }o--o| "public.agent_plan" : "FOREIGN KEY (#quot;planId#quot;) REFERENCES agent_plan(id) ON DELETE SET NULL"
"public.agent_channel_status" }o--|| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
"public.agent_chat_attachments" }o--|| "public.project" : "FOREIGN KEY (#quot;projectId#quot;) REFERENCES project(id) ON DELETE CASCADE"
"public.agent_chat_attachments" }o--o| "public.agents" : "FOREIGN KEY (#quot;agentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
@ -474,8 +476,11 @@ erDiagram
varchar_255_ parentResourceId
varchar_128_ parentThreadId
uuid pauseRequestId
uuid planId FK
uuid planTaskId
text result
timestamp_3__with_time_zone settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
timestamp_3__with_time_zone timeoutAt
@ -643,6 +648,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -16,8 +16,11 @@
| parentResourceId | varchar(255) | | false | | | Memory resource of the parent agent run |
| parentThreadId | varchar(128) | | false | | | |
| pauseRequestId | uuid | | true | | | Groups jobs stopped by one user request |
| planId | uuid | | true | | [public.agent_plan](public.agent_plan.md) | Plan that owns this work |
| planTaskId | uuid | | true | | | Task ID in the saved plan document |
| result | text | | true | | | Final answer of a settled sub-agent job |
| settledAt | timestamp(3) with time zone | | true | | | |
| sourceExecutionId | varchar(36) | | true | | [public.agent_execution](public.agent_execution.md) | Execution that dispatched this job |
| status | varchar(16) | | false | | | running: child works; suspended: child awaits approval; paused: user stopped the child; completed, failed, cancelled: terminal |
| subAgentId | varchar(36) | | true | | | Sub-agent jobs only |
| timeoutAt | timestamp(3) with time zone | | true | | | When reconciliation fails the job as timed out; NULL means no timeout |
@ -31,6 +34,8 @@
| ---- | ---- | ---------- |
| CHK_agent_background_job_kind | CHECK | CHECK (((kind)::text = ANY ((ARRAY['subagent'::character varying, 'workflow'::character varying])::text[]))) |
| CHK_agent_background_job_status | CHECK | CHECK (((status)::text = ANY ((ARRAY['running'::character varying, 'suspended'::character varying, 'paused'::character varying, 'completed'::character varying, 'failed'::character varying, 'cancelled'::character varying])::text[]))) |
| FK_4db3b7ebea4ed4f69649982018c | FOREIGN KEY | FOREIGN KEY ("sourceExecutionId") REFERENCES agent_execution(id) ON DELETE SET NULL |
| FK_ce131bff629d89015893e350e9c | FOREIGN KEY | FOREIGN KEY ("planId") REFERENCES agent_plan(id) ON DELETE SET NULL |
| FK_d46c6f00730c2ef8bcb6ee24b67 | FOREIGN KEY | FOREIGN KEY ("parentAgentId") REFERENCES agents(id) ON DELETE CASCADE |
| PK_6e0db58281aa2b4c956dc0d58e9 | PRIMARY KEY | PRIMARY KEY (id) |
| agent_background_job_createdAt_not_null | n | NOT NULL "createdAt" |
@ -51,6 +56,8 @@
| IDX_93d62baabe9858816b5adafb44 | CREATE INDEX "IDX_93d62baabe9858816b5adafb44" ON public.agent_background_job USING btree ("parentThreadId", status) |
| IDX_agent_background_job_childExecutionId | CREATE UNIQUE INDEX "IDX_agent_background_job_childExecutionId" ON public.agent_background_job USING btree ("childExecutionId") WHERE ("childExecutionId" IS NOT NULL) |
| IDX_agent_background_job_parentThreadId | CREATE INDEX "IDX_agent_background_job_parentThreadId" ON public.agent_background_job USING btree ("parentThreadId") WHERE (((status)::text <> 'running'::text) AND ("notifiedAt" IS NULL)) |
| IDX_agent_background_job_planId_planTaskId | CREATE INDEX "IDX_agent_background_job_planId_planTaskId" ON public.agent_background_job USING btree ("planId", "planTaskId") |
| IDX_agent_background_job_sourceExecutionId | CREATE INDEX "IDX_agent_background_job_sourceExecutionId" ON public.agent_background_job USING btree ("sourceExecutionId") |
| IDX_agent_background_job_timeoutAt | CREATE INDEX "IDX_agent_background_job_timeoutAt" ON public.agent_background_job USING btree ("timeoutAt") WHERE ((status)::text = ANY ((ARRAY['running'::character varying, 'suspended'::character varying])::text[])) |
| IDX_d46c6f00730c2ef8bcb6ee24b6 | CREATE INDEX "IDX_d46c6f00730c2ef8bcb6ee24b6" ON public.agent_background_job USING btree ("parentAgentId") |
| IDX_e43e630272995a93dfeb94ab3e | CREATE INDEX "IDX_e43e630272995a93dfeb94ab3e" ON public.agent_background_job USING btree ("settledAt") |
@ -62,6 +69,8 @@
erDiagram
"public.agent_background_job" }o--|| "public.agents" : "FOREIGN KEY (#quot;parentAgentId#quot;) REFERENCES agents(id) ON DELETE CASCADE"
"public.agent_background_job" }o--o| "public.agent_plan" : "FOREIGN KEY (#quot;planId#quot;) REFERENCES agent_plan(id) ON DELETE SET NULL"
"public.agent_background_job" }o--o| "public.agent_execution" : "FOREIGN KEY (#quot;sourceExecutionId#quot;) REFERENCES agent_execution(id) ON DELETE SET NULL"
"public.agent_background_job" {
varchar_36_ childExecutionId
@ -76,8 +85,11 @@ erDiagram
varchar_255_ parentResourceId
varchar_128_ parentThreadId
uuid pauseRequestId
uuid planId FK
uuid planTaskId
text result
timestamp_3__with_time_zone settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
timestamp_3__with_time_zone timeoutAt
@ -102,6 +114,41 @@ erDiagram
timestamp_3__with_time_zone updatedAt
varchar_36_ versionId
}
"public.agent_plan" {
timestamp_3__with_time_zone closedAt
timestamp_3__with_time_zone createdAt
json data
integer formatVersion
uuid id
integer revision
varchar_128_ threadId FK
timestamp_3__with_time_zone updatedAt
}
"public.agent_execution" {
boolean acceptsSteering
json attachments
json author
integer completionTokens
double_precision cost
timestamp_3__with_time_zone createdAt
integer duration
text error
json failureSummary
varchar_16_ hitlStatus
varchar_36_ id
varchar_255_ model
integer promptTokens
varchar_32_ source
timestamp_3__with_time_zone startedAt
varchar_16_ status
timestamp_3__with_time_zone stoppedAt
varchar_2_ storedAt
varchar_128_ threadId FK
json timeline
integer totalTokens
timestamp_3__with_time_zone updatedAt
text userMessage
}
```
---

View File

@ -14,7 +14,7 @@
| error | text | | true | | | |
| failureSummary | json | | true | | | Execution failure projection as {count, latest} for session list queries |
| hitlStatus | varchar(16) | | true | | | |
| id | varchar(36) | | false | [public.agent_execution_message_links](public.agent_execution_message_links.md) [public.agent_message_queue](public.agent_message_queue.md) | | |
| id | varchar(36) | | false | [public.agent_background_job](public.agent_background_job.md) [public.agent_execution_message_links](public.agent_execution_message_links.md) [public.agent_message_queue](public.agent_message_queue.md) | | |
| model | varchar(255) | | true | | | |
| promptTokens | integer | | true | | | |
| source | varchar(32) | | true | | | |
@ -59,6 +59,7 @@
```mermaid
erDiagram
"public.agent_background_job" }o--o| "public.agent_execution" : "FOREIGN KEY (#quot;sourceExecutionId#quot;) REFERENCES agent_execution(id) ON DELETE SET NULL"
"public.agent_execution_message_links" }o--|| "public.agent_execution" : "FOREIGN KEY (#quot;executionId#quot;) REFERENCES agent_execution(id) ON DELETE CASCADE"
"public.agent_message_queue" }o--o| "public.agent_execution" : "FOREIGN KEY (#quot;executionId#quot;) REFERENCES agent_execution(id)"
"public.agent_message_queue" }o--o| "public.agent_execution" : "FOREIGN KEY (#quot;steeringExecutionId#quot;) REFERENCES agent_execution(id)"
@ -89,6 +90,31 @@ erDiagram
timestamp_3__with_time_zone updatedAt
text userMessage
}
"public.agent_background_job" {
varchar_36_ childExecutionId
varchar_128_ childThreadId
timestamp_3__with_time_zone createdAt
text error
varchar_36_ id
varchar_16_ kind
timestamp_3__with_time_zone notifiedAt
varchar_36_ parentAgentId FK
varchar_64_ parentPrincipalHash
varchar_255_ parentResourceId
varchar_128_ parentThreadId
uuid pauseRequestId
uuid planId FK
uuid planTaskId
text result
timestamp_3__with_time_zone settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
timestamp_3__with_time_zone timeoutAt
varchar_255_ title
timestamp_3__with_time_zone updatedAt
varchar_36_ workflowId
}
"public.agent_execution_message_links" {
timestamp_3__with_time_zone createdAt
varchar_6_ direction
@ -121,6 +147,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -16,6 +16,7 @@
| projectId | varchar(255) | | false | | [public.project](public.project.md) | |
| sessionNumber | integer | 0 | false | | | |
| taskId | varchar(32) | | true | | | Published task ID that triggered this session; not an FK because published runs can outlive draft task definition rows |
| taskStop | json | | true | | | Latest task stop boundary and unresolved stop errors |
| taskVersionId | varchar(36) | | true | | [public.agent_history](public.agent_history.md) | Published agent_history version that supplied the task snapshot |
| title | varchar(255) | | true | | | |
| totalCompletionTokens | integer | 0 | false | | | |
@ -84,6 +85,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -104,6 +104,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -118,6 +118,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -8,7 +8,7 @@
| createdAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | |
| data | json | | false | | | Opaque plan document |
| formatVersion | integer | | false | | | Version of the plan JSON format |
| id | uuid | | false | [public.agent_plan_history](public.agent_plan_history.md) | | Plan ID generated by the caller |
| id | uuid | | false | [public.agent_background_job](public.agent_background_job.md) [public.agent_plan_history](public.agent_plan_history.md) | | Plan ID generated by the caller |
| revision | integer | 1 | false | | | Current document revision |
| threadId | varchar(128) | | false | | [public.agent_execution_threads](public.agent_execution_threads.md) | Session that owns the plan |
| updatedAt | timestamp(3) with time zone | CURRENT_TIMESTAMP(3) | false | | | |
@ -42,6 +42,7 @@
```mermaid
erDiagram
"public.agent_background_job" }o--o| "public.agent_plan" : "FOREIGN KEY (#quot;planId#quot;) REFERENCES agent_plan(id) ON DELETE SET NULL"
"public.agent_plan_history" }o--|| "public.agent_plan" : "FOREIGN KEY (#quot;planId#quot;) REFERENCES agent_plan(id) ON DELETE CASCADE"
"public.agent_plan" }o--|| "public.agent_execution_threads" : "FOREIGN KEY (#quot;threadId#quot;) REFERENCES agent_execution_threads(id) ON DELETE CASCADE"
@ -55,6 +56,31 @@ erDiagram
varchar_128_ threadId FK
timestamp_3__with_time_zone updatedAt
}
"public.agent_background_job" {
varchar_36_ childExecutionId
varchar_128_ childThreadId
timestamp_3__with_time_zone createdAt
text error
varchar_36_ id
varchar_16_ kind
timestamp_3__with_time_zone notifiedAt
varchar_36_ parentAgentId FK
varchar_64_ parentPrincipalHash
varchar_255_ parentResourceId
varchar_128_ parentThreadId
uuid pauseRequestId
uuid planId FK
uuid planTaskId
text result
timestamp_3__with_time_zone settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
timestamp_3__with_time_zone timeoutAt
varchar_255_ title
timestamp_3__with_time_zone updatedAt
varchar_36_ workflowId
}
"public.agent_plan_history" {
timestamp_3__with_time_zone closedAt
timestamp_3__with_time_zone createdAt
@ -76,6 +102,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -52,6 +52,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -119,8 +119,11 @@ erDiagram
varchar_255_ parentResourceId
varchar_128_ parentThreadId
uuid pauseRequestId
uuid planId FK
uuid planTaskId
text result
timestamp_3__with_time_zone settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
timestamp_3__with_time_zone timeoutAt
@ -202,6 +205,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -130,6 +130,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -178,6 +178,7 @@ erDiagram
varchar_255_ projectId FK
integer sessionNumber
varchar_32_ taskId
json taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
integer totalCompletionTokens

View File

@ -9,7 +9,7 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand.
| Name | Columns | Comment | Type |
| ---- | ------- | ------- | ---- |
| [activity_event](activity_event.md) | 11 | | table |
| [agent_background_job](agent_background_job.md) | 20 | | table |
| [agent_background_job](agent_background_job.md) | 23 | | table |
| [agent_budget_applied_call](agent_budget_applied_call.md) | 2 | | table |
| [agent_budget_spend](agent_budget_spend.md) | 4 | | table |
| [agent_channel_status](agent_channel_status.md) | 11 | | table |
@ -23,7 +23,7 @@ Auto-generated from the SQLite migrations in @n8n/db. Do not edit by hand.
| [agent_eval_run](agent_eval_run.md) | 14 | | table |
| [agent_execution](agent_execution.md) | 23 | | table |
| [agent_execution_message_links](agent_execution_message_links.md) | 5 | | table |
| [agent_execution_threads](agent_execution_threads.md) | 19 | | table |
| [agent_execution_threads](agent_execution_threads.md) | 20 | | table |
| [agent_files](agent_files.md) | 10 | | table |
| [agent_history](agent_history.md) | 9 | | table |
| [agent_message_queue](agent_message_queue.md) | 10 | | table |
@ -181,6 +181,8 @@ erDiagram
"activity_event" }o--|| "project" : "FOREIGN KEY (projectId) REFERENCES project (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"activity_event" }o--o| "user" : "FOREIGN KEY (userId) REFERENCES user (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"agent_background_job" }o--o| "agent_plan" : "FOREIGN KEY (planId) REFERENCES agent_plan (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"agent_background_job" }o--o| "agent_execution" : "FOREIGN KEY (sourceExecutionId) REFERENCES agent_execution (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"agent_background_job" }o--|| "agents" : "FOREIGN KEY (parentAgentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agent_channel_status" |o--|| "agents" : "FOREIGN KEY (agentId) REFERENCES agents (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"
@ -460,8 +462,11 @@ erDiagram
varchar_255_ parentResourceId
varchar_128_ parentThreadId
varchar pauseRequestId
varchar planId FK
varchar planTaskId
TEXT result
datetime_3_ settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
datetime_3_ timeoutAt
@ -629,6 +634,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -6,7 +6,7 @@
<summary><strong>Table Definition</strong></summary>
```sql
CREATE TABLE "agent_background_job" ("id" varchar(36) PRIMARY KEY NOT NULL, "kind" varchar(16) NOT NULL, "status" varchar(16) NOT NULL, "parentAgentId" varchar(36) NOT NULL, "parentThreadId" varchar(128) NOT NULL, "title" varchar(255) NOT NULL, "subAgentId" varchar(36), "childThreadId" varchar(128), "childExecutionId" varchar(36), "workflowId" varchar(36), "timeoutAt" datetime(3), "result" text, "error" text, "settledAt" datetime(3), "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')), "notifiedAt" datetime(3), "parentResourceId" varchar(255) NOT NULL, "parentPrincipalHash" varchar(64) NOT NULL, "pauseRequestId" varchar, CONSTRAINT "CHK_agent_background_job_kind" CHECK (((("kind" IN ('subagent', 'workflow'))))), CONSTRAINT "CHK_agent_background_job_status" CHECK ("status" IN ('running', 'suspended', 'paused', 'completed', 'failed', 'cancelled')), CONSTRAINT "FK_d46c6f00730c2ef8bcb6ee24b67" FOREIGN KEY ("parentAgentId") REFERENCES "agents" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)
CREATE TABLE "agent_background_job" ("id" varchar(36) PRIMARY KEY NOT NULL, "kind" varchar(16) NOT NULL, "status" varchar(16) NOT NULL, "parentAgentId" varchar(36) NOT NULL, "parentThreadId" varchar(128) NOT NULL, "title" varchar(255) NOT NULL, "subAgentId" varchar(36), "childThreadId" varchar(128), "childExecutionId" varchar(36), "workflowId" varchar(36), "timeoutAt" datetime(3), "result" text, "error" text, "settledAt" datetime(3), "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')), "notifiedAt" datetime(3), "parentResourceId" varchar(255) NOT NULL, "parentPrincipalHash" varchar(64) NOT NULL, "pauseRequestId" varchar, "sourceExecutionId" varchar(36), "planId" varchar, "planTaskId" varchar, CONSTRAINT "CHK_agent_background_job_kind" CHECK (((("kind" IN ('subagent', 'workflow'))))), CONSTRAINT "CHK_agent_background_job_status" CHECK ("status" IN ('running', 'suspended', 'paused', 'completed', 'failed', 'cancelled')), CONSTRAINT "FK_d46c6f00730c2ef8bcb6ee24b67" FOREIGN KEY ("parentAgentId") REFERENCES "agents" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_4db3b7ebea4ed4f69649982018c" FOREIGN KEY ("sourceExecutionId") REFERENCES "agent_execution" ("id") ON DELETE SET NULL, CONSTRAINT "FK_ce131bff629d89015893e350e9c" FOREIGN KEY ("planId") REFERENCES "agent_plan" ("id") ON DELETE SET NULL)
```
</details>
@ -27,8 +27,11 @@ CREATE TABLE "agent_background_job" ("id" varchar(36) PRIMARY KEY NOT NULL, "kin
| parentResourceId | varchar(255) | | false | | | |
| parentThreadId | varchar(128) | | false | | | |
| pauseRequestId | varchar | | true | | | |
| planId | varchar | | true | | [agent_plan](agent_plan.md) | |
| planTaskId | varchar | | true | | | |
| result | TEXT | | true | | | |
| settledAt | datetime(3) | | true | | | |
| sourceExecutionId | varchar(36) | | true | | [agent_execution](agent_execution.md) | |
| status | varchar(16) | | false | | | |
| subAgentId | varchar(36) | | true | | | |
| timeoutAt | datetime(3) | | true | | | |
@ -42,7 +45,9 @@ CREATE TABLE "agent_background_job" ("id" varchar(36) PRIMARY KEY NOT NULL, "kin
| ---- | ---- | ---------- |
| - | CHECK | CHECK (((("kind" IN ('subagent', 'workflow'))))) |
| - | CHECK | CHECK ("status" IN ('running', 'suspended', 'paused', 'completed', 'failed', 'cancelled')) |
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (parentAgentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
| - (Foreign key ID: 0) | FOREIGN KEY | FOREIGN KEY (planId) REFERENCES agent_plan (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
| - (Foreign key ID: 1) | FOREIGN KEY | FOREIGN KEY (sourceExecutionId) REFERENCES agent_execution (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE |
| - (Foreign key ID: 2) | FOREIGN KEY | FOREIGN KEY (parentAgentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE |
| id | PRIMARY KEY | PRIMARY KEY (id) |
| sqlite_autoindex_agent_background_job_1 | PRIMARY KEY | PRIMARY KEY (id) |
@ -53,6 +58,8 @@ CREATE TABLE "agent_background_job" ("id" varchar(36) PRIMARY KEY NOT NULL, "kin
| IDX_93d62baabe9858816b5adafb44 | CREATE INDEX "IDX_93d62baabe9858816b5adafb44" ON "agent_background_job" ("parentThreadId", "status") |
| IDX_agent_background_job_childExecutionId | CREATE UNIQUE INDEX "IDX_agent_background_job_childExecutionId" ON "agent_background_job" ("childExecutionId") WHERE "childExecutionId" IS NOT NULL |
| IDX_agent_background_job_parentThreadId | CREATE INDEX "IDX_agent_background_job_parentThreadId" ON "agent_background_job" ("parentThreadId") WHERE "status" <> 'running' AND "notifiedAt" IS NULL |
| IDX_agent_background_job_planId_planTaskId | CREATE INDEX "IDX_agent_background_job_planId_planTaskId" ON "agent_background_job" ("planId", "planTaskId") |
| IDX_agent_background_job_sourceExecutionId | CREATE INDEX "IDX_agent_background_job_sourceExecutionId" ON "agent_background_job" ("sourceExecutionId") |
| IDX_agent_background_job_timeoutAt | CREATE INDEX "IDX_agent_background_job_timeoutAt" ON "agent_background_job" ("timeoutAt") WHERE "status" IN ('running', 'suspended') |
| IDX_d46c6f00730c2ef8bcb6ee24b6 | CREATE INDEX "IDX_d46c6f00730c2ef8bcb6ee24b6" ON "agent_background_job" ("parentAgentId") |
| IDX_e43e630272995a93dfeb94ab3e | CREATE INDEX "IDX_e43e630272995a93dfeb94ab3e" ON "agent_background_job" ("settledAt") |
@ -64,6 +71,8 @@ CREATE TABLE "agent_background_job" ("id" varchar(36) PRIMARY KEY NOT NULL, "kin
erDiagram
"agent_background_job" }o--|| "agents" : "FOREIGN KEY (parentAgentId) REFERENCES agents (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agent_background_job" }o--o| "agent_plan" : "FOREIGN KEY (planId) REFERENCES agent_plan (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"agent_background_job" }o--o| "agent_execution" : "FOREIGN KEY (sourceExecutionId) REFERENCES agent_execution (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"agent_background_job" {
varchar_36_ childExecutionId
@ -78,8 +87,11 @@ erDiagram
varchar_255_ parentResourceId
varchar_128_ parentThreadId
varchar pauseRequestId
varchar planId FK
varchar planTaskId
TEXT result
datetime_3_ settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
datetime_3_ timeoutAt
@ -104,6 +116,41 @@ erDiagram
datetime_3_ updatedAt
varchar_36_ versionId
}
"agent_plan" {
datetime_3_ closedAt
datetime_3_ createdAt
TEXT data
INTEGER formatVersion
varchar id PK
INTEGER revision
varchar_128_ threadId FK
datetime_3_ updatedAt
}
"agent_execution" {
BOOLEAN acceptsSteering
TEXT attachments
TEXT author
INTEGER completionTokens
REAL cost
datetime_3_ createdAt
INTEGER duration
TEXT error
TEXT failureSummary
varchar_16_ hitlStatus
varchar_36_ id PK
varchar_255_ model
INTEGER promptTokens
varchar_32_ source
datetime_3_ startedAt
varchar_16_ status
datetime_3_ stoppedAt
varchar_2_ storedAt
varchar_128_ threadId FK
TEXT timeline
INTEGER totalTokens
datetime_3_ updatedAt
TEXT userMessage
}
```
---

View File

@ -25,7 +25,7 @@ CREATE TABLE "agent_execution" ("id" varchar(36) PRIMARY KEY NOT NULL, "threadId
| error | TEXT | | true | | | |
| failureSummary | TEXT | | true | | | |
| hitlStatus | varchar(16) | | true | | | |
| id | varchar(36) | | false | [agent_execution_message_links](agent_execution_message_links.md) [agent_message_queue](agent_message_queue.md) | | |
| id | varchar(36) | | false | [agent_background_job](agent_background_job.md) [agent_execution_message_links](agent_execution_message_links.md) [agent_message_queue](agent_message_queue.md) | | |
| model | varchar(255) | | true | | | |
| promptTokens | INTEGER | | true | | | |
| source | varchar(32) | | true | | | |
@ -63,6 +63,7 @@ CREATE TABLE "agent_execution" ("id" varchar(36) PRIMARY KEY NOT NULL, "threadId
```mermaid
erDiagram
"agent_background_job" }o--o| "agent_execution" : "FOREIGN KEY (sourceExecutionId) REFERENCES agent_execution (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"agent_execution_message_links" |o--|| "agent_execution" : "FOREIGN KEY (executionId) REFERENCES agent_execution (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agent_message_queue" }o--o| "agent_execution" : "FOREIGN KEY (executionId) REFERENCES agent_execution (id) ON UPDATE NO ACTION ON DELETE NO ACTION MATCH NONE"
"agent_message_queue" }o--o| "agent_execution" : "FOREIGN KEY (steeringExecutionId) REFERENCES agent_execution (id) ON UPDATE NO ACTION ON DELETE NO ACTION MATCH NONE"
@ -93,6 +94,31 @@ erDiagram
datetime_3_ updatedAt
TEXT userMessage
}
"agent_background_job" {
varchar_36_ childExecutionId
varchar_128_ childThreadId
datetime_3_ createdAt
TEXT error
varchar_36_ id PK
varchar_16_ kind
datetime_3_ notifiedAt
varchar_36_ parentAgentId FK
varchar_64_ parentPrincipalHash
varchar_255_ parentResourceId
varchar_128_ parentThreadId
varchar pauseRequestId
varchar planId FK
varchar planTaskId
TEXT result
datetime_3_ settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
datetime_3_ timeoutAt
varchar_255_ title
datetime_3_ updatedAt
varchar_36_ workflowId
}
"agent_execution_message_links" {
datetime_3_ createdAt
varchar_6_ direction
@ -125,6 +151,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -6,7 +6,7 @@
<summary><strong>Table Definition</strong></summary>
```sql
CREATE TABLE "agent_execution_threads" ("id" varchar(128) PRIMARY KEY NOT NULL, "agentId" varchar(36) NOT NULL, "agentName" varchar(255) NOT NULL, "projectId" varchar(255) NOT NULL, "sessionNumber" integer NOT NULL DEFAULT (0), "totalPromptTokens" integer NOT NULL DEFAULT (0), "totalCompletionTokens" integer NOT NULL DEFAULT (0), "totalCost" real NOT NULL DEFAULT (0), "totalDuration" integer NOT NULL DEFAULT (0), "title" varchar(255), "emoji" varchar(8), "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')), "taskId" varchar(32), "taskVersionId" varchar(36), "parentThreadId" varchar(128), "parentAgentId" varchar(36), "ownerId" varchar, "accessScope" varchar(16) NOT NULL DEFAULT ('user'), CONSTRAINT "CHK_agent_execution_threads_accessScope" CHECK ("accessScope" IN ('user', 'project')), CONSTRAINT "FK_f00b52d74fe11838e1fe086deea" FOREIGN KEY ("taskVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_0468a9dc35597314e641d4722aa" FOREIGN KEY ("agentId") REFERENCES "agents" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_0e2f8bf92a7a9c88b89670f701c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_agent_execution_threads_owner" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON DELETE SET NULL)
CREATE TABLE "agent_execution_threads" ("id" varchar(128) PRIMARY KEY NOT NULL, "agentId" varchar(36) NOT NULL, "agentName" varchar(255) NOT NULL, "projectId" varchar(255) NOT NULL, "sessionNumber" integer NOT NULL DEFAULT (0), "totalPromptTokens" integer NOT NULL DEFAULT (0), "totalCompletionTokens" integer NOT NULL DEFAULT (0), "totalCost" real NOT NULL DEFAULT (0), "totalDuration" integer NOT NULL DEFAULT (0), "title" varchar(255), "emoji" varchar(8), "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')), "taskId" varchar(32), "taskVersionId" varchar(36), "parentThreadId" varchar(128), "parentAgentId" varchar(36), "ownerId" varchar, "accessScope" varchar(16) NOT NULL DEFAULT ('user'), "taskStop" TEXT, CONSTRAINT "CHK_agent_execution_threads_accessScope" CHECK ("accessScope" IN ('user', 'project')), CONSTRAINT "FK_f00b52d74fe11838e1fe086deea" FOREIGN KEY ("taskVersionId") REFERENCES "agent_history" ("versionId") ON DELETE SET NULL ON UPDATE NO ACTION, CONSTRAINT "FK_0468a9dc35597314e641d4722aa" FOREIGN KEY ("agentId") REFERENCES "agents" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_0e2f8bf92a7a9c88b89670f701c" FOREIGN KEY ("projectId") REFERENCES "project" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_agent_execution_threads_owner" FOREIGN KEY ("ownerId") REFERENCES "user" ("id") ON DELETE SET NULL)
```
</details>
@ -27,6 +27,7 @@ CREATE TABLE "agent_execution_threads" ("id" varchar(128) PRIMARY KEY NOT NULL,
| projectId | varchar(255) | | false | | [project](project.md) | |
| sessionNumber | INTEGER | 0 | false | | | |
| taskId | varchar(32) | | true | | | |
| taskStop | TEXT | | true | | | |
| taskVersionId | varchar(36) | | true | | [agent_history](agent_history.md) | |
| title | varchar(255) | | true | | | |
| totalCompletionTokens | INTEGER | 0 | false | | | |
@ -84,6 +85,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -111,6 +111,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -121,6 +121,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -19,7 +19,7 @@ CREATE TABLE "agent_plan" ("id" varchar PRIMARY KEY NOT NULL, "threadId" varchar
| createdAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | |
| data | TEXT | | false | | | |
| formatVersion | INTEGER | | false | | | |
| id | varchar | | false | [agent_plan_history](agent_plan_history.md) | | |
| id | varchar | | false | [agent_background_job](agent_background_job.md) [agent_plan_history](agent_plan_history.md) | | |
| revision | INTEGER | 1 | false | | | |
| threadId | varchar(128) | | false | | [agent_execution_threads](agent_execution_threads.md) | |
| updatedAt | datetime(3) | STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW') | false | | | |
@ -47,6 +47,7 @@ CREATE TABLE "agent_plan" ("id" varchar PRIMARY KEY NOT NULL, "threadId" varchar
```mermaid
erDiagram
"agent_background_job" }o--o| "agent_plan" : "FOREIGN KEY (planId) REFERENCES agent_plan (id) ON UPDATE NO ACTION ON DELETE SET NULL MATCH NONE"
"agent_plan_history" |o--|| "agent_plan" : "FOREIGN KEY (planId) REFERENCES agent_plan (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
"agent_plan" }o--|| "agent_execution_threads" : "FOREIGN KEY (threadId) REFERENCES agent_execution_threads (id) ON UPDATE NO ACTION ON DELETE CASCADE MATCH NONE"
@ -60,6 +61,31 @@ erDiagram
varchar_128_ threadId FK
datetime_3_ updatedAt
}
"agent_background_job" {
varchar_36_ childExecutionId
varchar_128_ childThreadId
datetime_3_ createdAt
TEXT error
varchar_36_ id PK
varchar_16_ kind
datetime_3_ notifiedAt
varchar_36_ parentAgentId FK
varchar_64_ parentPrincipalHash
varchar_255_ parentResourceId
varchar_128_ parentThreadId
varchar pauseRequestId
varchar planId FK
varchar planTaskId
TEXT result
datetime_3_ settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
datetime_3_ timeoutAt
varchar_255_ title
datetime_3_ updatedAt
varchar_36_ workflowId
}
"agent_plan_history" {
datetime_3_ closedAt
datetime_3_ createdAt
@ -81,6 +107,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -61,6 +61,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -121,8 +121,11 @@ erDiagram
varchar_255_ parentResourceId
varchar_128_ parentThreadId
varchar pauseRequestId
varchar planId FK
varchar planTaskId
TEXT result
datetime_3_ settledAt
varchar_36_ sourceExecutionId FK
varchar_16_ status
varchar_36_ subAgentId
datetime_3_ timeoutAt
@ -204,6 +207,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -136,6 +136,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -184,6 +184,7 @@ erDiagram
varchar_255_ projectId FK
INTEGER sessionNumber
varchar_32_ taskId
TEXT taskStop
varchar_36_ taskVersionId FK
varchar_255_ title
INTEGER totalCompletionTokens

View File

@ -1,6 +1,9 @@
import type { AgentPlanTaskRef } from './task-cancellation';
import type { AgentBuilderOpenSuspension } from './types';
export interface AgentBackgroundJobDto {
planTask?: AgentPlanTaskRef;
id: string;
title: string;
/** Source of the background job. */

View File

@ -457,3 +457,7 @@ export interface VectorStoreTestResult {
export class AgentChatCancelDto extends Z.class({
scope: z.enum(['foreground', 'all']).optional(),
}) {}
export class AgentTaskCancellationDto extends Z.class({
planId: z.string().uuid().toLowerCase().nullable(),
}) {}

View File

@ -48,3 +48,5 @@ export {
type CancellationResumeData,
} from '../agent-builder-interactive';
export * from './agent-interaction.schema';
export type * from './task-cancellation';

View File

@ -0,0 +1,59 @@
export interface AgentPlanTaskRef {
planId: string;
planTaskId: string;
}
/** Runtime state does not change the saved task statuses. */
export interface AgentPlanWorkState {
stoppedTaskIds: string[];
stoppingTaskIds: string[];
fullyStopped: boolean;
stoppedAt: string | null;
taskStateAvailable: boolean;
}
export interface AgentPlanItemDto {
id: string;
title: string;
description?: string;
resultSummary?: string;
status: 'pending' | 'in_progress' | 'done' | 'failed' | 'cancelled';
dependsOn: string[];
}
export interface AgentPlanSnapshotDto {
workState?: AgentPlanWorkState;
readiness: { ready: string[]; blocked: Array<{ id: string; blockedBy: string[] }> };
planId: string;
revision: number;
closed: boolean;
startedAt: string | null;
finishedAt?: string | null;
closedAt: string | null;
document: {
title: string;
description?: string;
presentation?: { label: string; detail?: string };
items: Array<
| (AgentPlanItemDto & { kind: 'task'; fallbackFor?: string })
| (AgentPlanItemDto & {
kind: 'group';
tasks: Array<AgentPlanItemDto & { kind: 'task'; fallbackFor?: string }>;
})
>;
};
}
export interface AgentTaskStopFailure {
jobId: string;
title: string;
}
export interface AgentTaskCancellationState {
planId: string | null;
status: 'stopping' | 'failed' | 'stopped' | 'resumed';
reportFailed?: boolean;
requestedAt: string;
failures: AgentTaskStopFailure[];
plan: AgentPlanSnapshotDto | null;
}

View File

@ -0,0 +1,28 @@
import type { MigrationContext, ReversibleMigration } from '../migration-types';
export class AddSourceExecutionToAgentBackgroundJob1792083059203 implements ReversibleMigration {
async up({
schemaBuilder: { addColumns, addForeignKey, column, createIndex },
}: MigrationContext) {
await addColumns(
'agent_background_job',
[column('sourceExecutionId').varchar(36).comment('Execution that dispatched this job')],
{ recreatesOnSqlite: true },
);
await addForeignKey(
'agent_background_job',
'sourceExecutionId',
['agent_execution', 'id'],
undefined,
'SET NULL',
);
await createIndex('agent_background_job', ['sourceExecutionId']);
}
async down({ schemaBuilder: { dropColumns, dropIndex } }: MigrationContext) {
await dropIndex('agent_background_job', ['sourceExecutionId']);
await dropColumns('agent_background_job', ['sourceExecutionId'], {
recreatesOnSqlite: true,
});
}
}

View File

@ -0,0 +1,28 @@
import type { MigrationContext, ReversibleMigration } from '../migration-types';
export class AddTaskStopToAgentExecutionThreads1792083059437 implements ReversibleMigration {
async up({
schemaBuilder: { addColumns, column },
isSqlite,
escape,
runQuery,
}: MigrationContext) {
if (isSqlite) {
await runQuery(
`ALTER TABLE ${escape.tableName('agent_execution_threads')} ADD COLUMN ${escape.columnName('taskStop')} TEXT`,
);
return;
}
await addColumns(
'agent_execution_threads',
[column('taskStop').json.comment('Latest task stop boundary and unresolved stop errors')],
{ recreatesOnSqlite: true },
);
}
async down({ escape, runQuery }: MigrationContext) {
await runQuery(
`ALTER TABLE ${escape.tableName('agent_execution_threads')} DROP COLUMN ${escape.columnName('taskStop')}`,
);
}
}

View File

@ -0,0 +1,31 @@
import type { MigrationContext, ReversibleMigration } from '../migration-types';
export class AddPlanTaskToAgentBackgroundJob1792083059654 implements ReversibleMigration {
async up({
schemaBuilder: { addColumns, column, addForeignKey, createIndex },
}: MigrationContext) {
await addColumns(
'agent_background_job',
[
column('planId').uuid.comment('Plan that owns this work'),
column('planTaskId').uuid.comment('Task ID in the saved plan document'),
],
{ recreatesOnSqlite: true },
);
await addForeignKey(
'agent_background_job',
'planId',
['agent_plan', 'id'],
undefined,
'SET NULL',
);
await createIndex('agent_background_job', ['planId', 'planTaskId']);
}
async down({ schemaBuilder: { dropIndex, dropColumns } }: MigrationContext) {
await dropIndex('agent_background_job', ['planId', 'planTaskId']);
await dropColumns('agent_background_job', ['planId', 'planTaskId'], {
recreatesOnSqlite: true,
});
}
}

View File

@ -1,6 +1,8 @@
import type { AgentPlan } from './agent-plan.entity';
import type { AgentExecution } from './agent-execution.entity';
import type { AgentBackgroundJobDto } from '@n8n/api-types';
import { DateTimeColumn, WithTimestampsAndStringId } from '@n8n/db';
import { Column, Entity, Index } from '@n8n/typeorm';
import { Column, Entity, Index, ManyToOne, JoinColumn, type Relation } from '@n8n/typeorm';
export type AgentBackgroundJobKind = AgentBackgroundJobDto['kind'];
export type AgentBackgroundJobStatus = AgentBackgroundJobDto['status'];
@ -15,11 +17,35 @@ export type AgentBackgroundJobStatus = AgentBackgroundJobDto['status'];
@Entity({ name: 'agent_background_job' })
@Index(['parentThreadId', 'status'])
@Index(['parentAgentId'])
@Index(['sourceExecutionId'])
@Index(['planId', 'planTaskId'])
@Index(['settledAt'])
@Index(['childExecutionId'], { unique: true, where: '"childExecutionId" IS NOT NULL' })
@Index(['timeoutAt'], { where: "\"status\" IN ('running', 'suspended')" })
@Index(['parentThreadId'], { where: '"status" <> \'running\' AND "notifiedAt" IS NULL' })
export class AgentBackgroundJob extends WithTimestampsAndStringId {
@Column({
type: 'varchar',
length: 36,
nullable: true,
comment: 'Execution that dispatched this job',
})
sourceExecutionId: string | null;
@ManyToOne('AgentExecution', { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'sourceExecutionId' })
sourceExecution: Relation<AgentExecution> | null;
@Column({ type: 'uuid', nullable: true, comment: 'Plan that owns this work' })
planId: string | null;
@Column({ type: 'uuid', nullable: true, comment: 'Task ID in the saved plan document' })
planTaskId: string | null;
@ManyToOne('AgentPlan', { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'planId' })
plan: Relation<AgentPlan> | null;
@Column({ type: 'varchar', length: 16 })
kind: AgentBackgroundJobKind;

View File

@ -1,9 +1,10 @@
import { AGENT_TASK_ID_MAX_LENGTH } from '@n8n/api-types';
import { Project, User, WithTimestampsAndStringId } from '@n8n/db';
import { JsonColumn, Project, User, WithTimestampsAndStringId } from '@n8n/db';
import { Column, Entity, Index, JoinColumn, ManyToOne, type Relation } from '@n8n/typeorm';
import { AgentHistory } from './agent-history.entity';
import { Agent } from './agent.entity';
import type { StoredAgentTaskStop } from '../types/agent-task-stop';
/**
* One conversation between a user and an agent. Aggregates per-session
@ -20,6 +21,9 @@ import { Agent } from './agent.entity';
*/
@Entity({ name: 'agent_execution_threads' })
export class AgentExecutionThread extends WithTimestampsAndStringId {
@JsonColumn({ nullable: true, comment: 'Latest task stop boundary and unresolved stop errors' })
taskStop: StoredAgentTaskStop | null;
@ManyToOne(() => User, { nullable: true, onDelete: 'SET NULL' })
@JoinColumn({ name: 'ownerId', foreignKeyConstraintName: 'FK_agent_execution_threads_owner' })
owner: Relation<User> | null;

View File

@ -0,0 +1,28 @@
import type { AgentTaskStopFailure } from '@n8n/api-types';
/** Keep the stop and its acknowledgement available between jobs and across restarts. */
export interface AgentTaskStop {
planId: string | null;
requestedAt: string;
/** Unfinished leaf tasks and empty groups captured when Stop was requested. */
stoppedTaskIds?: string[];
/** Explicitly selected workflow tasks that still need a replacement job. */
restartTaskIds?: string[];
planRevision?: number;
generation: { executionIds: string[]; jobIds: string[]; threadIds: string[] };
failures: AgentTaskStopFailure[];
/** Reuse background checkpoints and deliver one report for this stop. */
pause: {
id: string;
reportExecutionId?: string;
reportedAt?: string;
reportFailed?: boolean;
/** Chat admission only. This does not resume stopped tasks. */
resumedAt?: string;
};
}
/** Earlier stop records have no pause coordination. */
export type StoredAgentTaskStop = Omit<AgentTaskStop, 'pause'> & {
pause?: AgentTaskStop['pause'];
};

View File

@ -0,0 +1,128 @@
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 = 'AddSourceExecutionToAgentBackgroundJob1792083059203';
describe('Agent background job source migration', () => {
const threadId = randomUUID();
const executionId = randomUUID();
const jobId = randomUUID();
async function withContext<T>(fn: (context: TestMigrationContext) => Promise<T>): Promise<T> {
const context = createTestMigrationContext(Container.get(DataSource));
try {
return await fn(context);
} finally {
await context.queryRunner.release();
}
}
async function insert(
context: TestMigrationContext,
table: string,
values: Record<string, string>,
) {
const columns = Object.keys(values);
await context.runQuery(
`INSERT INTO ${context.escape.tableName(table)}
(${columns.map(context.escape.columnName).join(', ')})
VALUES (${columns.map((name) => `:${name}`).join(', ')})`,
values,
);
}
beforeAll(async () => {
await Container.get(DbConnection).init();
await withContext(async (context) => await context.queryRunner.clearDatabase());
await initDbUpToMigration(migrationName);
await withContext(async (context) => {
const projectId = randomUUID();
const agentId = randomUUID();
await insert(context, 'project', { id: projectId, name: 'Stop test project', type: 'team' });
await insert(context, 'agents', {
id: agentId,
projectId,
name: 'Stop test agent',
integrations: '[]',
tools: '{}',
skills: '{}',
});
await insert(context, 'agent_execution_threads', {
id: threadId,
projectId,
agentId,
agentName: 'Stop test agent',
});
await insert(context, 'agent_execution', { id: executionId, threadId, status: 'success' });
await insert(context, 'agent_background_job', {
id: jobId,
parentAgentId: agentId,
parentThreadId: threadId,
parentResourceId: 'draft-chat:test',
parentPrincipalHash: 'principal',
title: 'Saved result',
kind: 'workflow',
status: 'completed',
result: '{"completed":true}',
});
});
});
afterAll(async () => await Container.get(DbConnection).close());
it('preserves jobs and supports source deletion, rollback, and reapplication', async () => {
await runSingleMigration(migrationName);
await withContext(async ({ escape, runQuery, queryRunner, tablePrefix }) => {
const table = await queryRunner.getTable(`${tablePrefix}agent_background_job`);
expect(table?.findColumnByName('sourceExecutionId')).toMatchObject({ isNullable: true });
expect(
table?.foreignKeys.filter((key) => key.columnNames.includes('sourceExecutionId')),
).toHaveLength(1);
expect(
table?.indices.some((index) => index.columnNames.join(',') === 'sourceExecutionId'),
).toBe(true);
await runQuery(
`UPDATE ${escape.tableName('agent_background_job')} SET ${escape.columnName('sourceExecutionId')} = :executionId WHERE ${escape.columnName('id')} = :jobId`,
{ executionId, jobId },
);
await runQuery(
`DELETE FROM ${escape.tableName('agent_execution')} WHERE ${escape.columnName('id')} = :executionId`,
{ executionId },
);
expect(
await runQuery(
`SELECT ${escape.columnName('sourceExecutionId')}, ${escape.columnName('result')} FROM ${escape.tableName('agent_background_job')}`,
),
).toEqual([{ sourceExecutionId: null, result: '{"completed":true}' }]);
});
await undoLastSingleMigration();
await withContext(async ({ escape, runQuery, queryRunner, tablePrefix }) => {
expect(
(await queryRunner.getTable(`${tablePrefix}agent_background_job`))?.findColumnByName(
'sourceExecutionId',
),
).toBeUndefined();
expect(
await runQuery(
`SELECT ${escape.columnName('result')} FROM ${escape.tableName('agent_background_job')}`,
),
).toEqual([{ result: '{"completed":true}' }]);
});
await runSingleMigration(migrationName);
await withContext(async ({ queryRunner, tablePrefix }) => {
const table = await queryRunner.getTable(`${tablePrefix}agent_background_job`);
expect(
table?.foreignKeys.filter((key) => key.columnNames.includes('sourceExecutionId')),
).toHaveLength(1);
});
});
});

View File

@ -0,0 +1,122 @@
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 = 'AddTaskStopToAgentExecutionThreads1792083059437';
describe('AddTaskStopToAgentExecutionThreads migration', () => {
const threadId = randomUUID();
const executionId = randomUUID();
async function withContext<T>(fn: (context: TestMigrationContext) => Promise<T>): Promise<T> {
const context = createTestMigrationContext(Container.get(DataSource));
try {
return await fn(context);
} finally {
await context.queryRunner.release();
}
}
async function insert(
context: TestMigrationContext,
table: string,
values: Record<string, string>,
) {
const columns = Object.keys(values);
await context.runQuery(
`INSERT INTO ${context.escape.tableName(table)}
(${columns.map(context.escape.columnName).join(', ')})
VALUES (${columns.map((name) => `:${name}`).join(', ')})`,
values,
);
}
async function readThreads() {
return await withContext(
async ({ escape, runQuery }) =>
await runQuery(`SELECT * FROM ${escape.tableName('agent_execution_threads')}`),
);
}
beforeAll(async () => {
await Container.get(DbConnection).init();
await withContext(async (context) => await context.queryRunner.clearDatabase());
await initDbUpToMigration(migrationName);
await withContext(async (context) => {
const projectId = randomUUID();
const agentId = randomUUID();
await insert(context, 'project', { id: projectId, name: 'Stop test project', type: 'team' });
await insert(context, 'agents', {
id: agentId,
projectId,
name: 'Stop test agent',
integrations: '[]',
tools: '{}',
skills: '{}',
});
await insert(context, 'agent_execution_threads', {
id: threadId,
projectId,
agentId,
agentName: 'Stop test agent',
});
await insert(context, 'agent_execution', { id: executionId, threadId, status: 'success' });
});
});
afterAll(async () => await Container.get(DbConnection).close());
it('preserves chats and their execution records through upgrade, rollback, and reapplication', async () => {
const original = await readThreads();
await runSingleMigration(migrationName);
await withContext(async ({ escape, runQuery, queryRunner, tablePrefix, isSqlite }) => {
expect(
(await queryRunner.getTable(`${tablePrefix}agent_execution_threads`))?.findColumnByName(
'taskStop',
),
).toMatchObject({
isNullable: true,
type: isSqlite ? 'text' : 'json',
});
expect(
await runQuery(
`SELECT ${escape.columnName('taskStop')} FROM ${escape.tableName('agent_execution_threads')}`,
),
).toEqual([{ taskStop: null }]);
await runQuery(
`UPDATE ${escape.tableName('agent_execution_threads')} SET ${escape.columnName('taskStop')} = :stop WHERE ${escape.columnName('id')} = :threadId`,
{ threadId, stop: JSON.stringify({ requestedAt: '2026-10-08T10:00:00.000Z' }) },
);
});
await undoLastSingleMigration();
expect(await readThreads()).toEqual(original);
await withContext(async ({ queryRunner, tablePrefix }) => {
expect(
(await queryRunner.getTable(`${tablePrefix}agent_execution_threads`))?.findColumnByName(
'taskStop',
),
).toBeUndefined();
});
await runSingleMigration(migrationName);
await withContext(async ({ escape, runQuery, queryRunner, tablePrefix }) => {
expect(
await runQuery(
`SELECT ${escape.columnName('id')} FROM ${escape.tableName('agent_execution')}`,
),
).toEqual([{ id: executionId }]);
expect(
(await queryRunner.getTable(`${tablePrefix}agent_execution_threads`))?.findColumnByName(
'taskStop',
),
).toMatchObject({ isNullable: true });
});
});
});

View File

@ -0,0 +1,134 @@
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 = 'AddPlanTaskToAgentBackgroundJob1792083059654';
describe('Agent background job plan task migration', () => {
const threadId = randomUUID();
const planId = randomUUID();
const planTaskId = randomUUID();
const jobId = randomUUID();
async function withContext<T>(fn: (context: TestMigrationContext) => Promise<T>): Promise<T> {
const context = createTestMigrationContext(Container.get(DataSource));
try {
return await fn(context);
} finally {
await context.queryRunner.release();
}
}
async function insert(
context: TestMigrationContext,
table: string,
values: Record<string, string>,
) {
const columns = Object.keys(values);
await context.runQuery(
`INSERT INTO ${context.escape.tableName(table)}
(${columns.map(context.escape.columnName).join(', ')})
VALUES (${columns.map((name) => `:${name}`).join(', ')})`,
values,
);
}
beforeAll(async () => {
await Container.get(DbConnection).init();
await withContext(async (context) => await context.queryRunner.clearDatabase());
await initDbUpToMigration(migrationName);
await withContext(async (context) => {
const projectId = randomUUID();
const agentId = randomUUID();
await insert(context, 'project', { id: projectId, name: 'Stop test project', type: 'team' });
await insert(context, 'agents', {
id: agentId,
projectId,
name: 'Stop test agent',
integrations: '[]',
tools: '{}',
skills: '{}',
});
await insert(context, 'agent_execution_threads', {
id: threadId,
projectId,
agentId,
agentName: 'Stop test agent',
});
await insert(context, 'agent_plan', { id: planId, threadId, formatVersion: '1', data: '{}' });
await insert(context, 'agent_background_job', {
id: jobId,
parentAgentId: agentId,
parentThreadId: threadId,
parentResourceId: 'draft-chat:test',
parentPrincipalHash: 'principal',
title: 'Saved result',
kind: 'workflow',
status: 'completed',
result: '{"completed":true}',
});
});
});
afterAll(async () => await Container.get(DbConnection).close());
it('preserves jobs and supports plan deletion, rollback, and reapplication', async () => {
await runSingleMigration(migrationName);
await withContext(async ({ escape, runQuery, queryRunner, tablePrefix }) => {
const table = await queryRunner.getTable(`${tablePrefix}agent_background_job`);
expect(table?.findColumnByName('planId')).toMatchObject({ isNullable: true });
expect(table?.findColumnByName('planTaskId')).toMatchObject({ isNullable: true });
expect(
table?.indices.some((index) => index.columnNames.join(',') === 'planId,planTaskId'),
).toBe(true);
expect(table?.foreignKeys.filter((key) => key.columnNames.includes('planId'))).toHaveLength(
1,
);
expect(table?.findColumnByName('detached')).toBeUndefined();
expect(
(await queryRunner.getTable(`${tablePrefix}agent_message_queue`))?.findColumnByName('held'),
).toBeUndefined();
await runQuery(
`UPDATE ${escape.tableName('agent_background_job')} SET ${escape.columnName('planId')} = :planId, ${escape.columnName('planTaskId')} = :planTaskId WHERE ${escape.columnName('id')} = :jobId`,
{ planId, planTaskId, jobId },
);
await runQuery(
`DELETE FROM ${escape.tableName('agent_plan')} WHERE ${escape.columnName('id')} = :planId`,
{ planId },
);
expect(
await runQuery(
`SELECT ${escape.columnName('planId')}, ${escape.columnName('result')} FROM ${escape.tableName('agent_background_job')}`,
),
).toEqual([{ planId: null, result: '{"completed":true}' }]);
});
await undoLastSingleMigration();
await withContext(async ({ escape, runQuery, queryRunner, tablePrefix }) => {
expect(
(await queryRunner.getTable(`${tablePrefix}agent_background_job`))?.findColumnByName(
'planId',
),
).toBeUndefined();
expect(
await runQuery(
`SELECT ${escape.columnName('result')} FROM ${escape.tableName('agent_background_job')}`,
),
).toEqual([{ result: '{"completed":true}' }]);
});
await runSingleMigration(migrationName);
await withContext(async ({ queryRunner, tablePrefix }) => {
const table = await queryRunner.getTable(`${tablePrefix}agent_background_job`);
expect(table?.foreignKeys.filter((key) => key.columnNames.includes('planId'))).toHaveLength(
1,
);
});
});
});