fix: Preserve node positions in layoutWorkflowJSON for update paths#28902
fix: Preserve node positions in layoutWorkflowJSON for update paths#28902dariacodes wants to merge 1 commit intomasterfrom
layoutWorkflowJSON for update paths#28902Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
No issues found across 5 files
Architecture diagram
sequenceDiagram
participant AI as AI Agent / MCP Client
participant Tool as Workflow Tool (Build/Submit/Update)
participant SDK as Workflow SDK (layoutWorkflowJSON)
participant Dagre as Dagre Layout Engine
Note over AI,SDK: Workflow Update or Creation Request
AI->>Tool: Request workflow update (with code/JSON)
Tool->>Tool: Parse & validate workflow code
alt NEW: Workflow Update (workflowId exists)
Tool->>SDK: layoutWorkflowJSON(workflow, { preservePositions: true })
SDK->>SDK: NEW: Map existing [x, y] to GraphNode config
SDK-->>Tool: Return JSON (positions unchanged)
else Workflow Creation (workflowId missing)
Tool->>SDK: layoutWorkflowJSON(workflow, { preservePositions: false })
SDK->>Dagre: Calculate optimal node placement
Dagre-->>SDK: Generated coordinates
SDK-->>Tool: Return JSON (new auto-layout positions)
end
Note over Tool: Finalize WorkflowEntity
alt Success Path
Tool-->>AI: 200 OK (Workflow with preserved/new layout)
else Validation Error
Tool-->>AI: Error (Invalid workflow structure)
end
Performance ComparisonComparing current → latest master → 14-day baseline Memory consumption baseline with starter plan resources
docker-stats
Idle baseline with Instance AI module loaded
How to read this table
|
Instance AI Workflow Eval Results8/8 built | 5 run(s) | pass@5: 74% | pass^5: 15%
Failure detailsCreate a workflow that handles contact form submissions via a webhook. / happy-path — 4/5 passed
Create a workflow that handles contact form submissions via a webhook. / missing-fields — 4/5 passed
Create a workflow that handles contact form submissions via a webhook. / partial-action-failure — 0/5 passed
Create a workflow that handles contact form submissions via a webhook. / empty-message — 4/5 passed
Create a workflow that handles contact form submissions via a webhook. / invalid-email — 1/5 passed
Get all the Linear issues created in the last 2 weeks. Filter them for / happy-path — 2/5 passed
Get all the Linear issues created in the last 2 weeks. Filter them for / multi-team-creator — 0/5 passed
Get all the Linear issues created in the last 2 weeks. Filter them for / no-cross-team-issues — 4/5 passed
Get all the Linear issues created in the last 2 weeks. Filter them for / unknown-creator — 3/5 passed
Get all the Linear issues created in the last 2 weeks. Filter them for / api-error — 0/5 passed
Every day, get the posts made in the past day on 3 different Slack cha / happy-path — 4/5 passed
Every day, get the posts made in the past day on 3 different Slack cha / empty-channel — 3/5 passed
Every day, get the posts made in the past day on 3 different Slack cha / high-volume — 1/5 passed
Every day, get the posts made in the past day on 3 different Slack cha / channel-not-found — 0/5 passed
Every day, get the posts made in the past day on 3 different Slack cha / insufficient-permissions — 0/5 passed
Every day, fetch all open GitHub issues from repository 'acme-corp/bac / happy-path — 0/5 passed
Every day, fetch all open GitHub issues from repository 'acme-corp/bac / no-bugs — 1/5 passed
Create a workflow that receives webhook notifications with a JSON body / high-priority — 2/5 passed
Create a workflow that receives webhook notifications with a JSON body / medium-priority — 2/5 passed
Create a workflow that receives webhook notifications with a JSON body / low-priority — 2/5 passed
Fetch the latest posts from the JSONPlaceholder API (GET https://jsonp / happy-path — 3/5 passed
Fetch the latest posts from the JSONPlaceholder API (GET https://jsonp / empty-response — 4/5 passed
Fetch the latest posts from the JSONPlaceholder API (GET https://jsonp / all-filtered — 2/5 passed
Every hour, check the current weather for London, New York, and Tokyo / happy-path — 0/5 passed
Every hour, check the current weather for London, New York, and Tokyo / no-alerts — 1/5 passed
|
Summary
When updating a workflow through MCP (or the instance-ai deep agent), all node positions get recalculated from scratch, even when every node already has correct positions. This is most visible with sticky notes, which jump to completely wrong locations after a simple content edit:

The problem comes from
layoutWorkflowJSON(). When nodes are prepared for the layout, their existing positions are not kept. Because of this, the layout engine thinks every run is starting from scratch, so it moves all nodes again and can place things like sticky notes incorrectly.The fix adds a
preservePositionsoption tolayoutWorkflowJSON(). When it’s turned on, existing node positions are kept so the layout engine doesn’t move them again. We enable this for updates, while keeping the default behavior for new workflows so they still get properly arranged.To test, request an MCP client to update a workflow with a sticky note with a non-default position. E.g. "Update the sticky note in the workflow id=X to 'Hi', do not change its position".
Related Linear tickets, Github issues, and Community forum posts
Closes ADO-5104
Review / Merge checklist
Backport to Beta,Backport to Stable, orBackport to v1(if the PR is an urgent fix that needs to be backported)