Run python nodes out of process, with modules of their own

User code no longer execs in the engine. A pool of persistent worker
subprocesses speaks one JSON object per line; the controller installs a
proxy as the node's function, so every execution path funnels through it
and the pipeline is untouched. A crash costs one subprocess, a per-node
timeout is a kill, and cancelling from the canvas is that same kill.

The workers run a venv of the user's own on the data volume, filled from
a pip manifest versioned beside the flows. Applying it retires the
workers and rebuilds, so a package lands without restarting the engine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017MeiWk3Yq12n2pTvnQWYvt
This commit is contained in:
2026-08-16 21:43:36 +02:00
co-authored by Claude Fable 5
parent 979c9d3c1f
commit f300c43f3a
27 changed files with 1536 additions and 46 deletions
+108
View File
@@ -27,6 +27,36 @@ export const AlertsConfigSchema = {
description: 'The whole alerting setup, as stored and as the API sees it.'
} as const;
export const ApplyRequestSchema = {
properties: {
requirements: {
type: 'string',
title: 'Requirements',
default: ''
}
},
type: 'object',
title: 'ApplyRequest',
description: 'A pip manifest, one requirement per line.'
} as const;
export const ApplyResultSchema = {
properties: {
ok: {
type: 'boolean',
title: 'Ok'
},
output: {
type: 'string',
title: 'Output',
default: ''
}
},
type: 'object',
required: ['ok'],
title: 'ApplyResult'
} as const;
export const Body_login_login_access_tokenSchema = {
properties: {
grant_type: {
@@ -890,6 +920,58 @@ export const MessagesPublicSchema = {
title: 'MessagesPublic'
} as const;
export const ModulePackageSchema = {
properties: {
name: {
type: 'string',
title: 'Name'
},
version: {
type: 'string',
title: 'Version'
}
},
type: 'object',
required: ['name', 'version'],
title: 'ModulePackage',
description: 'One package installed in the venv node code runs on.'
} as const;
export const ModulesInfoSchema = {
properties: {
python_version: {
type: 'string',
title: 'Python Version',
default: ''
},
venv_path: {
type: 'string',
title: 'Venv Path',
default: ''
},
requirements: {
type: 'string',
title: 'Requirements',
default: ''
},
packages: {
items: {
'$ref': '#/components/schemas/ModulePackage'
},
type: 'array',
title: 'Packages'
},
applied: {
type: 'boolean',
title: 'Applied',
default: false
}
},
type: 'object',
title: 'ModulesInfo',
description: 'The venv node code imports from, and the manifest that describes it.'
} as const;
export const NewPasswordSchema = {
properties: {
token: {
@@ -960,6 +1042,19 @@ export const NodeDef_InputSchema = {
}
],
title: 'Source Ref'
},
timeout: {
anyOf: [
{
type: 'number',
exclusiveMinimum: 0
},
{
type: 'null'
}
],
title: 'Timeout',
description: "Seconds this node's code may run before it is stopped. Above 60 the engine may deliver its work again while it is still running."
}
},
type: 'object',
@@ -1020,6 +1115,19 @@ export const NodeDef_OutputSchema = {
}
],
title: 'Source Ref'
},
timeout: {
anyOf: [
{
type: 'number',
exclusiveMinimum: 0
},
{
type: 'null'
}
],
title: 'Timeout',
description: "Seconds this node's code may run before it is stopped. Above 60 the engine may deliver its work again while it is still running."
}
},
type: 'object',