Skip to main content
Glama

Update Node

update_node

Because no node is added, dropped, or renamed, every prospect keeps its cursor and each node keeps its on_enter enactment (prompt, code, run history). The edited sequence is re-validated as a whole, so an edge repoint that would dangle a reference, orphan a node, or introduce a cycle is rejected rather than saved. To add or remove nodes, change a node's kind, or edit the start node, re-author the flow with define_sequence instead.

A forked-on condition is often restated in several places — a decision's rule, each arm.case, a downstream terminal's label, and the node's on_enter prompt. When you change one, change the others in the same turn (a further update_node for the node fields, update_trigger for the prompt) so they agree. Dict with success, agent_id, and node_id. Read the updated flow via get_campaign_flow.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nodeYesThe node's complete new definition — an omitted optional slot (a `follow_up`, a `timeout`) is dropped, not preserved, so carry forward every field the node keeps. A send or connection-request node's `message_templates` is the exception: omitted leaves the step's templates as they are. Its `id` must name an existing body node and its `kind` must match that node's current kind.
agent_idYesID of the agent whose flow holds the node.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed2 schema fields changed
    • changedInput schema / properties / node / description
      Previous value: -"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send node's `message_templates` is the exception: omitted leaves the step's\ntemplates as they are.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."New value: +"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send or connection-request node's `message_templates` is the exception: omitted\nleaves the step's templates as they are.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."
    • changedInput schema / properties / node / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
      -    "properties": {
      -      "accepted": {
      -        "type": "string"
      -      },
      -      "already_connected": {
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "connection_request",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "timeout": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      -            "properties": {
      -              "delay": {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              "to": {
      -                "type": "string"
      -              }
      -            },
      -            "required": [
      -              "delay",
      -              "to"
      -            ],
      -            "title": "TimedAdvance",
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "accepted",
      -      "already_connected"
      -    ],
      -    "title": "ConnectionRequestNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
      -    "properties": {
      -      "action": {
      -        "enum": [
      -          "message",
      -          "inmail",
      -          "email"
      -        ],
      -        "type": "string"
      -      },
      -      "channel": {
      -        "enum": [
      -          "linkedin",
      -          "email"
      -        ],
      -        "type": "string"
      -      },
      -      "follow_up": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      -            "properties": {
      -              "delay": {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              "to": {
      -                "type": "string"
      -              }
      -            },
      -            "required": [
      -              "delay",
      -              "to"
      -            ],
      -            "title": "TimedAdvance",
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "send",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "message_templates": {
      -        "anyOf": [
      -          {
      -            "items": {
      -              "type": "string"
      -            },
      -            "maxItems": 2,
      -            "type": "array"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      },
      -      "replied": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "channel",
      -      "action",
      -      "replied"
      -    ],
      -    "title": "SendNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
      -    "properties": {
      -      "action": {
      -        "enum": [
      -          "follow",
      -          "withdraw",
      -          "resolve",
      -          "comment",
      -          "reaction"
      -        ],
      -        "type": "string"
      -      },
      -      "channel": {
      -        "const": "linkedin",
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "action",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "notify": {
      -        "default": false,
      -        "type": "boolean"
      -      },
      -      "then": {
      -        "additionalProperties": false,
      -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
      -        "properties": {
      -          "after": {
      -            "anyOf": [
      -              {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              {
      -                "type": "null"
      -              }
      -            ],
      -            "default": null
      -          },
      -          "to": {
      -            "type": "string"
      -          }
      -        },
      -        "required": [
      -          "to"
      -        ],
      -        "title": "Advance",
      -        "type": "object"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "channel",
      -      "action",
      -      "then"
      -    ],
      -    "title": "SilentActionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
      -    "properties": {
      -      "action_description": {
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "manual",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "then": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "action_description",
      -      "then"
      -    ],
      -    "title": "ManualActionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
      -    "properties": {
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "automation",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "then": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "then"
      -    ],
      -    "title": "AutomationNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
      -    "properties": {
      -      "arms": {
      -        "items": {
      -          "additionalProperties": false,
      -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
      -          "properties": {
      -            "case": {
      -              "minLength": 1,
      -              "type": "string"
      -            },
      -            "to": {
      -              "type": "string"
      -            }
      -          },
      -          "required": [
      -            "case",
      -            "to"
      -          ],
      -          "title": "Arm",
      -          "type": "object"
      -        },
      -        "minItems": 1,
      -        "type": "array"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "decision",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "rule": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "rule",
      -      "arms"
      -    ],
      -    "title": "DecisionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
      -    "properties": {
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "terminal",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind"
      -    ],
      -    "title": "TerminalNode",
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).\n\n`message_templates` is input-only, as on a send node, and holds the request's note (at most\n300 characters): `''` is a version sent without one, so `['', note]` A/B tests a note\nagainst none.",
      +    "properties": {
      +      "accepted": {
      +        "type": "string"
      +      },
      +      "already_connected": {
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "connection_request",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "message_templates": {
      +        "anyOf": [
      +          {
      +            "items": {
      +              "maxLength": 300,
      +              "type": "string"
      +            },
      +            "maxItems": 2,
      +            "type": "array"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      },
      +      "timeout": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      +            "properties": {
      +              "delay": {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              "to": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "delay",
      +              "to"
      +            ],
      +            "title": "TimedAdvance",
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "accepted",
      +      "already_connected"
      +    ],
      +    "title": "ConnectionRequestNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
      +    "properties": {
      +      "action": {
      +        "enum": [
      +          "message",
      +          "inmail",
      +          "email"
      +        ],
      +        "type": "string"
      +      },
      +      "channel": {
      +        "enum": [
      +          "linkedin",
      +          "email"
      +        ],
      +        "type": "string"
      +      },
      +      "follow_up": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      +            "properties": {
      +              "delay": {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              "to": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "delay",
      +              "to"
      +            ],
      +            "title": "TimedAdvance",
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "send",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "message_templates": {
      +        "anyOf": [
      +          {
      +            "items": {
      +              "type": "string"
      +            },
      +            "maxItems": 2,
      +            "type": "array"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      },
      +      "replied": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "channel",
      +      "action",
      +      "replied"
      +    ],
      +    "title": "SendNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
      +    "properties": {
      +      "action": {
      +        "enum": [
      +          "follow",
      +          "withdraw",
      +          "resolve",
      +          "comment",
      +          "reaction"
      +        ],
      +        "type": "string"
      +      },
      +      "channel": {
      +        "const": "linkedin",
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "action",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "notify": {
      +        "default": false,
      +        "type": "boolean"
      +      },
      +      "then": {
      +        "additionalProperties": false,
      +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
      +        "properties": {
      +          "after": {
      +            "anyOf": [
      +              {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              {
      +                "type": "null"
      +              }
      +            ],
      +            "default": null
      +          },
      +          "to": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "to"
      +        ],
      +        "title": "Advance",
      +        "type": "object"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "channel",
      +      "action",
      +      "then"
      +    ],
      +    "title": "SilentActionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
      +    "properties": {
      +      "action_description": {
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "manual",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "then": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "action_description",
      +      "then"
      +    ],
      +    "title": "ManualActionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "automation",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "then": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "then"
      +    ],
      +    "title": "AutomationNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
      +    "properties": {
      +      "arms": {
      +        "items": {
      +          "additionalProperties": false,
      +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
      +          "properties": {
      +            "case": {
      +              "minLength": 1,
      +              "type": "string"
      +            },
      +            "to": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "case",
      +            "to"
      +          ],
      +          "title": "Arm",
      +          "type": "object"
      +        },
      +        "minItems": 1,
      +        "type": "array"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "decision",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "rule": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "rule",
      +      "arms"
      +    ],
      +    "title": "DecisionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "terminal",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind"
      +    ],
      +    "title": "TerminalNode",
      +    "type": "object"
      +  }
      +]
  2. Changed2 schema fields changed
    • changedInput schema / properties / node / description
      Previous value: -"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send node's `message_template` is the exception: omitted leaves its template as it is.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."New value: +"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send node's `message_templates` is the exception: omitted leaves the step's\ntemplates as they are.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."
    • changedInput schema / properties / node / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
      -    "properties": {
      -      "accepted": {
      -        "type": "string"
      -      },
      -      "already_connected": {
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "connection_request",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "timeout": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      -            "properties": {
      -              "delay": {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              "to": {
      -                "type": "string"
      -              }
      -            },
      -            "required": [
      -              "delay",
      -              "to"
      -            ],
      -            "title": "TimedAdvance",
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "accepted",
      -      "already_connected"
      -    ],
      -    "title": "ConnectionRequestNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_template` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's template lives in its own rows and is merged back in on read.",
      -    "properties": {
      -      "action": {
      -        "enum": [
      -          "message",
      -          "inmail",
      -          "email"
      -        ],
      -        "type": "string"
      -      },
      -      "channel": {
      -        "enum": [
      -          "linkedin",
      -          "email"
      -        ],
      -        "type": "string"
      -      },
      -      "follow_up": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      -            "properties": {
      -              "delay": {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              "to": {
      -                "type": "string"
      -              }
      -            },
      -            "required": [
      -              "delay",
      -              "to"
      -            ],
      -            "title": "TimedAdvance",
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "send",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "message_template": {
      -        "anyOf": [
      -          {
      -            "type": "string"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      },
      -      "replied": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "channel",
      -      "action",
      -      "replied"
      -    ],
      -    "title": "SendNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
      -    "properties": {
      -      "action": {
      -        "enum": [
      -          "follow",
      -          "withdraw",
      -          "resolve",
      -          "comment",
      -          "reaction"
      -        ],
      -        "type": "string"
      -      },
      -      "channel": {
      -        "const": "linkedin",
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "action",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "notify": {
      -        "default": false,
      -        "type": "boolean"
      -      },
      -      "then": {
      -        "additionalProperties": false,
      -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
      -        "properties": {
      -          "after": {
      -            "anyOf": [
      -              {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              {
      -                "type": "null"
      -              }
      -            ],
      -            "default": null
      -          },
      -          "to": {
      -            "type": "string"
      -          }
      -        },
      -        "required": [
      -          "to"
      -        ],
      -        "title": "Advance",
      -        "type": "object"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "channel",
      -      "action",
      -      "then"
      -    ],
      -    "title": "SilentActionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
      -    "properties": {
      -      "action_description": {
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "manual",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "then": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "action_description",
      -      "then"
      -    ],
      -    "title": "ManualActionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
      -    "properties": {
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "automation",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "then": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "then"
      -    ],
      -    "title": "AutomationNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
      -    "properties": {
      -      "arms": {
      -        "items": {
      -          "additionalProperties": false,
      -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
      -          "properties": {
      -            "case": {
      -              "minLength": 1,
      -              "type": "string"
      -            },
      -            "to": {
      -              "type": "string"
      -            }
      -          },
      -          "required": [
      -            "case",
      -            "to"
      -          ],
      -          "title": "Arm",
      -          "type": "object"
      -        },
      -        "minItems": 1,
      -        "type": "array"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "decision",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "rule": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "rule",
      -      "arms"
      -    ],
      -    "title": "DecisionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
      -    "properties": {
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "terminal",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind"
      -    ],
      -    "title": "TerminalNode",
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
      +    "properties": {
      +      "accepted": {
      +        "type": "string"
      +      },
      +      "already_connected": {
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "connection_request",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "timeout": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      +            "properties": {
      +              "delay": {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              "to": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "delay",
      +              "to"
      +            ],
      +            "title": "TimedAdvance",
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "accepted",
      +      "already_connected"
      +    ],
      +    "title": "ConnectionRequestNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_templates` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's templates live in their own rows and are merged back in on read. A step\nwith two is running an A/B test.",
      +    "properties": {
      +      "action": {
      +        "enum": [
      +          "message",
      +          "inmail",
      +          "email"
      +        ],
      +        "type": "string"
      +      },
      +      "channel": {
      +        "enum": [
      +          "linkedin",
      +          "email"
      +        ],
      +        "type": "string"
      +      },
      +      "follow_up": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      +            "properties": {
      +              "delay": {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              "to": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "delay",
      +              "to"
      +            ],
      +            "title": "TimedAdvance",
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "send",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "message_templates": {
      +        "anyOf": [
      +          {
      +            "items": {
      +              "type": "string"
      +            },
      +            "maxItems": 2,
      +            "type": "array"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      },
      +      "replied": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "channel",
      +      "action",
      +      "replied"
      +    ],
      +    "title": "SendNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
      +    "properties": {
      +      "action": {
      +        "enum": [
      +          "follow",
      +          "withdraw",
      +          "resolve",
      +          "comment",
      +          "reaction"
      +        ],
      +        "type": "string"
      +      },
      +      "channel": {
      +        "const": "linkedin",
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "action",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "notify": {
      +        "default": false,
      +        "type": "boolean"
      +      },
      +      "then": {
      +        "additionalProperties": false,
      +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
      +        "properties": {
      +          "after": {
      +            "anyOf": [
      +              {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              {
      +                "type": "null"
      +              }
      +            ],
      +            "default": null
      +          },
      +          "to": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "to"
      +        ],
      +        "title": "Advance",
      +        "type": "object"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "channel",
      +      "action",
      +      "then"
      +    ],
      +    "title": "SilentActionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
      +    "properties": {
      +      "action_description": {
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "manual",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "then": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "action_description",
      +      "then"
      +    ],
      +    "title": "ManualActionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "automation",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "then": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "then"
      +    ],
      +    "title": "AutomationNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
      +    "properties": {
      +      "arms": {
      +        "items": {
      +          "additionalProperties": false,
      +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
      +          "properties": {
      +            "case": {
      +              "minLength": 1,
      +              "type": "string"
      +            },
      +            "to": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "case",
      +            "to"
      +          ],
      +          "title": "Arm",
      +          "type": "object"
      +        },
      +        "minItems": 1,
      +        "type": "array"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "decision",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "rule": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "rule",
      +      "arms"
      +    ],
      +    "title": "DecisionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "terminal",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind"
      +    ],
      +    "title": "TerminalNode",
      +    "type": "object"
      +  }
      +]
  3. Changed5 schema fields changed
    • addedInput schema / properties / agent_id
      Added value: +{
      +  "description": "ID of the agent whose flow holds the node.",
      +  "type": "integer"
      +}
    • changedInput schema / properties / node / description
      Previous value: -"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."New value: +"The node's complete new definition — an omitted optional slot (a `follow_up`, a\n`timeout`) is dropped, not preserved, so carry forward every field the node keeps.\nA send node's `message_template` is the exception: omitted leaves its template as it is.\nIts `id` must name an existing body node and its `kind` must match that node's\ncurrent kind."
    • changedInput schema / properties / node / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
      -    "properties": {
      -      "accepted": {
      -        "type": "string"
      -      },
      -      "already_connected": {
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "connection_request",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "timeout": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      -            "properties": {
      -              "delay": {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              "to": {
      -                "type": "string"
      -              }
      -            },
      -            "required": [
      -              "delay",
      -              "to"
      -            ],
      -            "title": "TimedAdvance",
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "accepted",
      -      "already_connected"
      -    ],
      -    "title": "ConnectionRequestNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
      -    "properties": {
      -      "action": {
      -        "enum": [
      -          "message",
      -          "inmail",
      -          "email"
      -        ],
      -        "type": "string"
      -      },
      -      "channel": {
      -        "enum": [
      -          "linkedin",
      -          "email"
      -        ],
      -        "type": "string"
      -      },
      -      "follow_up": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      -            "properties": {
      -              "delay": {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              "to": {
      -                "type": "string"
      -              }
      -            },
      -            "required": [
      -              "delay",
      -              "to"
      -            ],
      -            "title": "TimedAdvance",
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "send",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "replied": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "channel",
      -      "action",
      -      "replied"
      -    ],
      -    "title": "SendNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
      -    "properties": {
      -      "action": {
      -        "enum": [
      -          "follow",
      -          "withdraw",
      -          "resolve",
      -          "comment",
      -          "reaction"
      -        ],
      -        "type": "string"
      -      },
      -      "channel": {
      -        "const": "linkedin",
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "action",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "notify": {
      -        "default": false,
      -        "type": "boolean"
      -      },
      -      "then": {
      -        "additionalProperties": false,
      -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
      -        "properties": {
      -          "after": {
      -            "anyOf": [
      -              {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "cd",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              {
      -                "type": "null"
      -              }
      -            ],
      -            "default": null
      -          },
      -          "to": {
      -            "type": "string"
      -          }
      -        },
      -        "required": [
      -          "to"
      -        ],
      -        "title": "Advance",
      -        "type": "object"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "channel",
      -      "action",
      -      "then"
      -    ],
      -    "title": "SilentActionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
      -    "properties": {
      -      "action_description": {
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "manual",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "then": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "action_description",
      -      "then"
      -    ],
      -    "title": "ManualActionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
      -    "properties": {
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "automation",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "then": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "then"
      -    ],
      -    "title": "AutomationNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
      -    "properties": {
      -      "arms": {
      -        "items": {
      -          "additionalProperties": false,
      -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
      -          "properties": {
      -            "case": {
      -              "minLength": 1,
      -              "type": "string"
      -            },
      -            "to": {
      -              "type": "string"
      -            }
      -          },
      -          "required": [
      -            "case",
      -            "to"
      -          ],
      -          "title": "Arm",
      -          "type": "object"
      -        },
      -        "minItems": 1,
      -        "type": "array"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "decision",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "rule": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "rule",
      -      "arms"
      -    ],
      -    "title": "DecisionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
      -    "properties": {
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "terminal",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind"
      -    ],
      -    "title": "TerminalNode",
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
      +    "properties": {
      +      "accepted": {
      +        "type": "string"
      +      },
      +      "already_connected": {
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "connection_request",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "timeout": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      +            "properties": {
      +              "delay": {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              "to": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "delay",
      +              "to"
      +            ],
      +            "title": "TimedAdvance",
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "accepted",
      +      "already_connected"
      +    ],
      +    "title": "ConnectionRequestNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).\n\n`message_template` is input-only: excluded from every dump, so it never lands in the stored\nsequence — the step's template lives in its own rows and is merged back in on read.",
      +    "properties": {
      +      "action": {
      +        "enum": [
      +          "message",
      +          "inmail",
      +          "email"
      +        ],
      +        "type": "string"
      +      },
      +      "channel": {
      +        "enum": [
      +          "linkedin",
      +          "email"
      +        ],
      +        "type": "string"
      +      },
      +      "follow_up": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      +            "properties": {
      +              "delay": {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              "to": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "delay",
      +              "to"
      +            ],
      +            "title": "TimedAdvance",
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "send",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "message_template": {
      +        "anyOf": [
      +          {
      +            "type": "string"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      },
      +      "replied": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "channel",
      +      "action",
      +      "replied"
      +    ],
      +    "title": "SendNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
      +    "properties": {
      +      "action": {
      +        "enum": [
      +          "follow",
      +          "withdraw",
      +          "resolve",
      +          "comment",
      +          "reaction"
      +        ],
      +        "type": "string"
      +      },
      +      "channel": {
      +        "const": "linkedin",
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "action",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "notify": {
      +        "default": false,
      +        "type": "boolean"
      +      },
      +      "then": {
      +        "additionalProperties": false,
      +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
      +        "properties": {
      +          "after": {
      +            "anyOf": [
      +              {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              {
      +                "type": "null"
      +              }
      +            ],
      +            "default": null
      +          },
      +          "to": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "to"
      +        ],
      +        "title": "Advance",
      +        "type": "object"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "channel",
      +      "action",
      +      "then"
      +    ],
      +    "title": "SilentActionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
      +    "properties": {
      +      "action_description": {
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "manual",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "then": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "action_description",
      +      "then"
      +    ],
      +    "title": "ManualActionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a terminal node's hook — not a field here. Unlike a manual node\nit boots an agent run (rather than waiting on a human) and unlike a terminal hook it\nadvances; structurally it is a decision with one implicit 'always' arm — the run does the\nside-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "automation",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "then": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "then"
      +    ],
      +    "title": "AutomationNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
      +    "properties": {
      +      "arms": {
      +        "items": {
      +          "additionalProperties": false,
      +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
      +          "properties": {
      +            "case": {
      +              "minLength": 1,
      +              "type": "string"
      +            },
      +            "to": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "case",
      +            "to"
      +          ],
      +          "title": "Arm",
      +          "type": "object"
      +        },
      +        "minItems": 1,
      +        "type": "array"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "decision",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "rule": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "rule",
      +      "arms"
      +    ],
      +    "title": "DecisionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "terminal",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind"
      +    ],
      +    "title": "TerminalNode",
      +    "type": "object"
      +  }
      +]
    • removedInput schema / properties / task_id
      Removed value: -{
      -  "description": "ID of the task whose flow holds the node.",
      -  "type": "integer"
      -}
    • changedInput schema / required
      Previous value: -[
      -  "task_id",
      -  "node"
      -]New value: +[
      +  "agent_id",
      +  "node"
      +]
  4. Changed1 schema field changed
    • changedInput schema / properties / node / oneOf
      Previous value: -[
      -  {
      -    "additionalProperties": false,
      -    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
      -    "properties": {
      -      "accepted": {
      -        "type": "string"
      -      },
      -      "already_connected": {
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "connection_request",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "timeout": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      -            "properties": {
      -              "delay": {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              "to": {
      -                "type": "string"
      -              }
      -            },
      -            "required": [
      -              "delay",
      -              "to"
      -            ],
      -            "title": "TimedAdvance",
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "accepted",
      -      "already_connected"
      -    ],
      -    "title": "ConnectionRequestNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
      -    "properties": {
      -      "action": {
      -        "enum": [
      -          "message",
      -          "inmail",
      -          "email"
      -        ],
      -        "type": "string"
      -      },
      -      "channel": {
      -        "enum": [
      -          "linkedin",
      -          "email"
      -        ],
      -        "type": "string"
      -      },
      -      "follow_up": {
      -        "anyOf": [
      -          {
      -            "additionalProperties": false,
      -            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      -            "properties": {
      -              "delay": {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              "to": {
      -                "type": "string"
      -              }
      -            },
      -            "required": [
      -              "delay",
      -              "to"
      -            ],
      -            "title": "TimedAdvance",
      -            "type": "object"
      -          },
      -          {
      -            "type": "null"
      -          }
      -        ],
      -        "default": null
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "send",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "replied": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "channel",
      -      "action",
      -      "replied"
      -    ],
      -    "title": "SendNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
      -    "properties": {
      -      "action": {
      -        "enum": [
      -          "follow",
      -          "withdraw",
      -          "resolve",
      -          "comment",
      -          "reaction"
      -        ],
      -        "type": "string"
      -      },
      -      "channel": {
      -        "const": "linkedin",
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "action",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "notify": {
      -        "default": false,
      -        "type": "boolean"
      -      },
      -      "then": {
      -        "additionalProperties": false,
      -        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
      -        "properties": {
      -          "after": {
      -            "anyOf": [
      -              {
      -                "additionalProperties": false,
      -                "description": "A timed hold: `value` units of `unit` (w/d/h/m). Validated by construction — no\nfree-form string to parse — and stored JSON-natively as `{value, unit}`.",
      -                "properties": {
      -                  "unit": {
      -                    "enum": [
      -                      "w",
      -                      "d",
      -                      "h",
      -                      "m"
      -                    ],
      -                    "type": "string"
      -                  },
      -                  "value": {
      -                    "exclusiveMinimum": 0,
      -                    "type": "integer"
      -                  }
      -                },
      -                "required": [
      -                  "value",
      -                  "unit"
      -                ],
      -                "title": "EdgeDelay",
      -                "type": "object"
      -              },
      -              {
      -                "type": "null"
      -              }
      -            ],
      -            "default": null
      -          },
      -          "to": {
      -            "type": "string"
      -          }
      -        },
      -        "required": [
      -          "to"
      -        ],
      -        "title": "Advance",
      -        "type": "object"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "channel",
      -      "action",
      -      "then"
      -    ],
      -    "title": "SilentActionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
      -    "properties": {
      -      "action_description": {
      -        "type": "string"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "manual",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "then": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "action_description",
      -      "then"
      -    ],
      -    "title": "ManualActionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
      -    "properties": {
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "automation",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "then": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "then"
      -    ],
      -    "title": "AutomationNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
      -    "properties": {
      -      "arms": {
      -        "items": {
      -          "additionalProperties": false,
      -          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
      -          "properties": {
      -            "case": {
      -              "minLength": 1,
      -              "type": "string"
      -            },
      -            "to": {
      -              "type": "string"
      -            }
      -          },
      -          "required": [
      -            "case",
      -            "to"
      -          ],
      -          "title": "Arm",
      -          "type": "object"
      -        },
      -        "minItems": 1,
      -        "type": "array"
      -      },
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "decision",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      },
      -      "rule": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind",
      -      "rule",
      -      "arms"
      -    ],
      -    "title": "DecisionNode",
      -    "type": "object"
      -  },
      -  {
      -    "additionalProperties": false,
      -    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
      -    "properties": {
      -      "id": {
      -        "type": "string"
      -      },
      -      "kind": {
      -        "const": "terminal",
      -        "type": "string"
      -      },
      -      "label": {
      -        "type": "string"
      -      }
      -    },
      -    "required": [
      -      "id",
      -      "label",
      -      "kind"
      -    ],
      -    "title": "TerminalNode",
      -    "type": "object"
      -  }
      -]New value: +[
      +  {
      +    "additionalProperties": false,
      +    "description": "A LinkedIn connection request. Routes both send outcomes — `accepted` (invite\ntaken) and `already_connected` (a no-op CR on an existing 1st-degree connection) — and\nan optional `timeout` (a held advance, e.g. withdraw after no accept).",
      +    "properties": {
      +      "accepted": {
      +        "type": "string"
      +      },
      +      "already_connected": {
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "connection_request",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "timeout": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      +            "properties": {
      +              "delay": {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              "to": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "delay",
      +              "to"
      +            ],
      +            "title": "TimedAdvance",
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "accepted",
      +      "already_connected"
      +    ],
      +    "title": "ConnectionRequestNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A repliable send — a LinkedIn message/inmail, or an email. Routes `replied` (a reply\nadvances the prospect off the node) and an optional `follow_up` (a held no-reply canned\nfollow-on, itself just another send reached after the delay).",
      +    "properties": {
      +      "action": {
      +        "enum": [
      +          "message",
      +          "inmail",
      +          "email"
      +        ],
      +        "type": "string"
      +      },
      +      "channel": {
      +        "enum": [
      +          "linkedin",
      +          "email"
      +        ],
      +        "type": "string"
      +      },
      +      "follow_up": {
      +        "anyOf": [
      +          {
      +            "additionalProperties": false,
      +            "description": "A held follow-on anchored on this node's send — always delayed (no instant form).\n    ",
      +            "properties": {
      +              "delay": {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              "to": {
      +                "type": "string"
      +              }
      +            },
      +            "required": [
      +              "delay",
      +              "to"
      +            ],
      +            "title": "TimedAdvance",
      +            "type": "object"
      +          },
      +          {
      +            "type": "null"
      +          }
      +        ],
      +        "default": null
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "send",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "replied": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "channel",
      +      "action",
      +      "replied"
      +    ],
      +    "title": "SendNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A bodyless LinkedIn action (follow / withdraw / resolve / comment / reaction) — no\nreply to route. `then` is its single advance: bare (`then.after=None`) flows straight\ninto the next node when the send completes, or held for a delay first.\n\n`notify` applies only to `action='resolve'`: True makes the profile lookup a *visible*\nvisit (\"View Profile\") that notifies the person — a warm-up touch — instead of the\nsilent data lookup. Default False.",
      +    "properties": {
      +      "action": {
      +        "enum": [
      +          "follow",
      +          "withdraw",
      +          "resolve",
      +          "comment",
      +          "reaction"
      +        ],
      +        "type": "string"
      +      },
      +      "channel": {
      +        "const": "linkedin",
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "action",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "notify": {
      +        "default": false,
      +        "type": "boolean"
      +      },
      +      "then": {
      +        "additionalProperties": false,
      +        "description": "An unconditional advance onto `to`. `after=None` fires the instant the source send\ncompletes (the send-completion advance); `after` set holds for that delay first (a\ntimed advance anchored on the send's sent_at, or on tracking time off the start node).",
      +        "properties": {
      +          "after": {
      +            "anyOf": [
      +              {
      +                "additionalProperties": false,
      +                "description": "A timed hold: `value` units of `unit`. A `d` (day) hold counts BUSINESS days — Mon–Fri,\nweekends skipped — the outreach default; `cd` counts calendar days (weekends included), as\ndo `w`/`h`/`m`. Validated by construction (no free-form string to parse) and stored\nJSON-natively as `{value, unit}`.",
      +                "properties": {
      +                  "unit": {
      +                    "enum": [
      +                      "w",
      +                      "cd",
      +                      "d",
      +                      "h",
      +                      "m"
      +                    ],
      +                    "type": "string"
      +                  },
      +                  "value": {
      +                    "exclusiveMinimum": 0,
      +                    "type": "integer"
      +                  }
      +                },
      +                "required": [
      +                  "value",
      +                  "unit"
      +                ],
      +                "title": "EdgeDelay",
      +                "type": "object"
      +              },
      +              {
      +                "type": "null"
      +              }
      +            ],
      +            "default": null
      +          },
      +          "to": {
      +            "type": "string"
      +          }
      +        },
      +        "required": [
      +          "to"
      +        ],
      +        "title": "Advance",
      +        "type": "object"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "channel",
      +      "action",
      +      "then"
      +    ],
      +    "title": "SilentActionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A step a human performs off-platform that the system surfaces but never enacts (a\ncall, a gift, a recorded note). Entry queues a manual_action_queue row carrying\n`action_description`; the user marking that row done advances the prospect along `then`.\nNo channel and no reply to route; `then` is a single bare advance — completion is\nhuman-paced, so there's no timed hold to anchor on a send's sent_at.",
      +    "properties": {
      +      "action_description": {
      +        "type": "string"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "manual",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "then": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "action_description",
      +      "then"
      +    ],
      +    "title": "ManualActionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "An automated side-effect the agent performs on entry — update a CRM, post a\nnotification, tag a record — with no outreach send, then advances along `then`. The\nside-effect itself is authored onto the node's on_enter trigger (a `prompt`, or `code` for\na deterministic one), like a send node's template and a terminal node's hook — not a field\nhere. Unlike a manual node it boots an agent run (rather than waiting on a human) and unlike\na terminal hook it advances; structurally it is a decision with one implicit 'always' arm —\nthe run does the side-effect, then moves each prospect onto `then`. `then` is a single bare\nadvance: a side-effect completes on the run, not a send's sent_at, so there's no timed hold.",
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "automation",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "then": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "then"
      +    ],
      +    "title": "AutomationNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A natural-language judgment fork. `arms` are the judged routes — each a `case`\ncondition and a target (the default authored as an explicit arm too, e.g. case='else').",
      +    "properties": {
      +      "arms": {
      +        "items": {
      +          "additionalProperties": false,
      +          "description": "One judged decision arm: `case` is the natural-language condition (stripped,\nnon-empty — the decision judge needs something to evaluate), `to` its target node.",
      +          "properties": {
      +            "case": {
      +              "minLength": 1,
      +              "type": "string"
      +            },
      +            "to": {
      +              "type": "string"
      +            }
      +          },
      +          "required": [
      +            "case",
      +            "to"
      +          ],
      +          "title": "Arm",
      +          "type": "object"
      +        },
      +        "minItems": 1,
      +        "type": "array"
      +      },
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "decision",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      },
      +      "rule": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind",
      +      "rule",
      +      "arms"
      +    ],
      +    "title": "DecisionNode",
      +    "type": "object"
      +  },
      +  {
      +    "additionalProperties": false,
      +    "description": "A resting end-state with no further outreach — structurally a sink (no out-edge\nslots). Its human meaning lives in `label`. A terminal can still carry an on_enter hook —\na notify / tag / webhook side-effect on entry, no send and no movement — but that is just\nan on_enter Trigger added to the node with add_trigger, not a field here.",
      +    "properties": {
      +      "id": {
      +        "type": "string"
      +      },
      +      "kind": {
      +        "const": "terminal",
      +        "type": "string"
      +      },
      +      "label": {
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "id",
      +      "label",
      +      "kind"
      +    ],
      +    "title": "TerminalNode",
      +    "type": "object"
      +  }
      +]
  5. First observed

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare readOnlyHint=false and destructiveHint=false, and the description is consistent (in-place edit, nothing added/dropped). It goes well beyond them by disclosing what is preserved (each prospect keeps its cursor, each node keeps its on_enter enactment) and what is enforced on write (whole-sequence re-validation rejects dangling refs, orphaned nodes, cycles). That is exactly the behavioral context an agent needs before a mutation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core action and the alternative, then structured into preservation rules, then a cross-tool consistency note. It is long, and the final paragraph on forked-on conditions is somewhat elaborate, but each paragraph carries actionable information rather than filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with two rich parameters, full schema coverage, and no output schema, the description covers purpose, prerequisites, preservation semantics, validation behavior, the alternative tool, and a pointer to get_campaign_flow for reading back the result. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the baseline is 3. The description still adds meaning: it emphasizes that the node's full new definition must be passed, that `id` selects the node to replace, and that `kind` must match the current kind. This largely reinforces the schema's own notes ('omitted optional slot is dropped, not preserved') rather than introducing new semantics, so it is a modest lift over the structured data.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a precise verb and resource ('Edit one existing node of a campaign's flow in place') and immediately scopes it against the sibling it is not (`not define_sequence`). It even enumerates the concrete edits it covers (message, rule/arms, label, action_description, out-edges), so an agent can distinguish it from define_sequence without opening either schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit selection rule: use this when the set of nodes isn't changing; use `define_sequence` to add/remove nodes, change a node's kind, or edit the start node. It also specifies a prerequisite (id must name an existing body node, kind must match current kind) and cross-tool follow-through (update_trigger for prompts). No inference required.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources