Skip to main content
Glama

Define Sequence

define_sequence

Author (or replace) the campaign's flow sequence — a DAG of outreach nodes. Each node carries its own out-edges as typed fields, so the grammar is the schema: a connection request has accepted / already_connected / timeout slots, a send has replied / follow_up, a silent action and a manual step each have then, a decision has arms, a terminal has none. Derive the DAG from the agent's goal + triggers: one node per step, each natural-language fork a decision, and terminal only for resting end-states (auto-skip, human handoff, done). The flow must be acyclic, every node reachable from start, and every path capped by a terminal (only a terminal may dead-end) — all enforced.

The shape:

  • start (exactly one, the DAG head): {id, label, entry}. Holds prospects no send has reached (label it "Not started"). entry is the single first touch — {to} for an immediate open, or {to, after: {value, unit}} (units w/d/h/m) to hold that long from when each prospect is tracked. The flow fires it on its own: do NOT queue the first sends yourself on a sequenced campaign — track prospects and let the flow open.

  • connection_request node (LinkedIn): {id, label, kind, accepted, already_connected, timeout?, message_templates?}. message_templates holds its note (see below); without one the step's on_enter code or instructions decide (its minted default code sends no note). Routes BOTH send outcomes — accepted (invite taken) and already_connected (a CR onto an existing 1st-degree connection, a no-op), each a target node id; both required. Route already_connected to a terminal by default (surface the existing relationship for opt-in rather than cold-messaging it); point it at the message step only when the user says to reconnect with existing connections. timeout is an optional held advance {delay: {value, unit}, to} (e.g. withdraw after no accept).

  • send node (a repliable send — LinkedIn message / inmail, or an email): {id, label, kind, channel, action, replied, follow_up?, message_templates?}. channel is linkedin or email (so one sequence spans both); action is a LinkedIn verb for channel linkedin or email for channel email. Routes replied (required) so a reply advances the prospect off the node; follow_up is an optional held no-reply follow-on {delay, to}. message_templates holds the step's message (see below).

  • action node (a bodyless LinkedIn action — follow / withdraw / resolve / comment / reaction): {id, label, kind: "action", channel: "linkedin", action, then}. Nothing to reply to; then is its single advance — {to} fires the instant the action's send lands (a bodyless step flows straight into the next node with no hold), or {to, after} to hold first. A resolve action may add notify: true — the visible "View Profile" visit (LinkedIn notifies the person you viewed them), a warm-up touch typically placed later; the default silent resolve just reads profile data to branch on.

  • manual node (a step a HUMAN performs off-platform that the system can't enact — a call, a gift, a recorded note): {id, label, kind: "manual", action_description, then}. No channel. action_description is the fully-authored ask (no send is composed, no agent run boots — the step is purely a note for the user); entry queues it onto the user's home approval list. then is the next node's id (a bare string, not a {to} object — unlike a silent action's then): the prospect advances onto it when the user marks the queued step done, with no timed hold (completion is human-paced). Use manual ONLY when a human must act — never for a step the agent can do itself (that step never happens, since a manual node boots no run): an automatable side-effect (update a CRM, create a task in a connected tool, post a notification) is an automation node, and an automatable outreach touch is an action/send node.

  • automation node (an automated non-send side-effect the AGENT performs — update a CRM record, create a HubSpot task, post a Slack/webhook notification, tag a record): {id, label, kind: "automation", then}. No channel. then is the next node's id (a bare string, like a manual node's). Author the side-effect itself onto this node's on_enter trigger with update_trigger (a prompt, or code for a deterministic one) — the same way a terminal node's hook is authored, not a field here; the enactment run carries it out for each prospect then advances. Unlike manual it boots an agent run and needs no human; unlike a terminal hook it advances. Reach for this when the user wants a step that "does something" automatically mid-sequence and then continues.

  • decision node: {id, label, kind, rule, arms}. rule is the NL fork judgment; arms is a list of {case, to} — each case a non-blank condition, the default authored as one too (e.g. case: "else"). A decision has no timed edge and no bare advance — only arms. A case arm may target any enactable node — a send / action / automation step, another decision, or a manual step — or a terminal: the decision run moves each prospect onto its arm, and entering it fires that node's own on_enter enactment on its next run (a send/action composes its send, a nested decision judges its fork, an automation runs its side-effect, a manual step queues the human's task), never an inline send. Chain a downstream decision when several arms share one judgment (DAG reuse); use compound arms on one rule ("A and B" / "A and not B" / "else") when one prospect needs several independent conditions judged together; target a manual step to hand a judged arm off to a human.

  • terminal node: {id, label, kind}. A resting end-state, no out-edges.

Every timed hold — a start after, a connection_request timeout, a send follow_up, a silent action's held then — is {value, unit}. A d (day) unit counts BUSINESS days — Mon–Fri, skipping Saturday/Sunday — and is the DEFAULT for a day-count delay: a plain "follow up in 3 days" (like "3 business days") is {value: 3, unit: "d"}. Reach for cd (calendar day) ONLY when the user explicitly wants calendar days including weekends. w/h/m (week/hour/minute) are always calendar.

Entering a node fires its enactment: a connection_request / send / action node composes and enqueues its step, a manual node queues its step onto the user's approval list, an automation node runs its authored side-effect and advances, a decision judges its fork and routes each prospect. Run a second channel alongside the first by chaining it off a step's held advance (a CR timeout, a send follow_up, or a silent action's held then) — not a second start entry: a prospect holds one flow position, so start takes exactly one entry.

A send node's message is its message_templates: a one-entry list holding the message itself, with each personalized part a {slot} — a named field ({first_name}, {company}) or a described slot ({a line about their recent post}); the enactment run fills every slot per prospect and, unless the step's prompt lets it rephrase, keeps the rest of the wording. The templates are saved with the step and come back on get_campaign_flow; omit message_templates to leave a step's templates as they are, or pass [] to remove them. To A/B test a step's message, add a second version to the list: prospects reaching the step alternate between the two, and get_message_experiments reports which gets more replies. At current volumes a test takes weeks and only separates meaningfully different messages (a different opener or ask, not a reworded phrase), so propose versions that differ that much. Changing either version's text restarts the test. To end a test, pass just the version you keep. A connection_request node's message_templates holds its note the same way (at most 300 characters, filled per prospect), where "" is a version sent with no note: ["", note] A/B tests a note against none, and get_message_experiments reports which gets more requests accepted. Anything else the run should know for that step (tone, who to mention, what to skip, whether it may rephrase the template, what to write when a slot can't be filled) goes in the step's on_enter trigger prompt via update_trigger — keep the message itself in message_templates, not in that prompt or in workspace.inputs, so the user sees and edits it on the step. When you first set a template on a step whose prompt already carries its message, rewrite that prompt down to the extra instructions. (workspace.inputs stays the home for a flowless campaign's templates and for per-agent data no single step owns — criteria, audience, exclude lists.)

Authoring (or replacing) a sequence on an agent that ALREADY holds untouched prospects enrolls that whole backlog into it — so before defining a sequence on an agent with an existing roster (a monitoring or engager-capture agent especially), confirm with the user that the accumulated prospects should all enter the campaign.

Re-authoring replaces the prior sequence (allowed on a live campaign); a prospect still resting on a dropped node returns under off_sequence_nodes (with a note on re-homing) rather than being lost. The node-owned on_enter triggers reconcile in the same call: every node kept in the new DAG (same id, any kind) keeps its on_enter row and the prompt/code on it, a new enactable node gets a fresh row auto-minted, and only a node dropped from the DAG entirely has its on_enter row deleted. A new resolve / connection_request / follow node is minted with a default deterministic enactment code (it enacts clean with no agent run — edit it for a non-standard step; a connection request's note goes in its message_templates, not the code); every other kind mints code-less. A reused id keeps its existing row — so after changing what that id's node does, review its on_enter content; an unmodified minted default is regenerated for the new kind automatically, but an edited one is left as you wrote it.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
agent_idYesID of the agent to attach the sequence to.
sequenceYesThe flow DAG (typed per-kind nodes). Validated for structural integrity, reachability, and acyclicity.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / sequence / properties / nodes / items / 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. Changed1 schema field changed
    • changedInput schema / properties / sequence / properties / nodes / items / 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. Changed4 schema fields changed
    • addedInput schema / properties / agent_id
      Added value: +{
      +  "description": "ID of the agent to attach the sequence to.",
      +  "type": "integer"
      +}
    • changedInput schema / properties / sequence / properties / nodes / items / 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 to attach the sequence to.",
      -  "type": "integer"
      -}
    • changedInput schema / required
      Previous value: -[
      -  "task_id",
      -  "sequence"
      -]New value: +[
      +  "agent_id",
      +  "sequence"
      +]
  4. Changed2 schema fields changed
    • changedInput schema / properties / sequence / properties / nodes / items / 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"
      +  }
      +]
    • changedInput schema / properties / sequence / properties / start / properties / entry / properties / after / anyOf
      Previous value: -[
      -  {
      -    "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"
      -  }
      -]New value: +[
      +  {
      +    "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"
      +  }
      +]
  5. First observed

TDQS

A4.7/5.0
Behavior4/5

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

Annotations only declare readOnlyHint=false / destructiveHint=false; the description adds substantial behavior beyond that — re-authoring replaces the prior sequence, dropped-node prospects return under off_sequence_nodes, on_enter rows are reconciled/deleted per kept-or-dropped node, and minted-default code is regenerated only if unedited. That said, it does not call out auth/permission requirements or rate limits, and 'replace the prior sequence' with per-node row deletion sits somewhat uneasily with destructiveHint=false (though prospects are explicitly preserved).

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?

It is front-loaded with the purpose and core derivation rule before the per-kind reference, and nearly every sentence carries operational detail. It is nonetheless very long for a description and repeats some rules (the business-day `d` default is stated more than once, as is the message_templates-holds-the-message rule), so it is dense but not maximally tight.

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?

No output schema exists, but the description explains where results surface (templates 'come back on get_campaign_flow', experiments on get_message_experiments) and covers enrollment side effects, re-authoring reconciliation, and structural validation constraints — everything an agent needs to author the DAG correctly.

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

Parameters5/5

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

Schema coverage is 100%, but the description goes far beyond it, defining the semantics, defaults, and interaction rules for nearly every field: the `entry.after` vs bare advance distinction, business-day `d` vs calendar `cd` units, `message_templates` as an input-only A/B list including `['', note]`, decision arms requiring non-blank cases, and where a step's message belongs (message_templates vs on_enter prompt vs workspace.inputs).

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?

The opening states a specific verb+resource ('Author (or replace) the campaign's flow sequence — a DAG of outreach nodes') and immediately frames the grammar as the schema. It is clearly distinguishable from siblings like update_node (single node), get_campaign_flow (read), and the setup_*_sequence tools.

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?

It gives explicit selection rules across alternatives: manual vs automation vs action/send ('Use `manual` ONLY when a human must act... an automatable side-effect is an `automation` node'), when to use a decision vs chained decisions, how to run a second channel (via a held advance, not a second start), and a pre-condition to confirm with the user before enrolling an existing prospect backlog.

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