<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Workday Developers</title>
    <description>The latest articles on DEV Community by Workday Developers (workdaydevelopers).</description>
    <link>https://dev.to/workdaydevelopers</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F15475%2Ffca5c9eb-3a64-4ddc-8c32-67dd01d7a99a.png</url>
      <title>DEV Community: Workday Developers</title>
      <link>https://dev.to/workdaydevelopers</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/workdaydevelopers"/>
    <language>en</language>
    <item>
      <title>Lawful Agents, Part 1: Your agent is reading an API that was never written for it</title>
      <dc:creator>Obinna Ekwuno</dc:creator>
      <pubDate>Wed, 21 Oct 2026 15:22:11 +0000</pubDate>
      <link>https://dev.to/workdaydevelopers/lawful-agents-part-1-your-agent-is-reading-an-api-that-was-never-written-for-it-30ng</link>
      <guid>https://dev.to/workdaydevelopers/lawful-agents-part-1-your-agent-is-reading-an-api-that-was-never-written-for-it-30ng</guid>
      <description>&lt;p&gt;If you have ever pointed an agent at an enterprise API, you will know how it goes. The response comes back five levels deep, the model spends half its context working out where the manager's name lives, it retries when a field is not where it expected, and eventually it fills the gap with something it made up. Then you spend the afternoon wondering whether the problem is your prompt.&lt;/p&gt;

&lt;p&gt;It usually is not your prompt. Most enterprise APIs were designed for a developer who reads the documentation once and writes code against it. Your agent does not get that luxury. It meets the API fresh on every call and has to work it out from whatever is in front of it.&lt;/p&gt;

&lt;p&gt;This series is about building agents you can actually trust with enterprise systems, the kind that stay inside the rules of the organisation they work for. I am starting with the tools those agents call, using &lt;a href="https://developer.workday.com/documentation/GUID-949559e7-10c3-438a-abb2-e452c23d9da8-enHYPHENus/AgentReadyMCPTools" rel="noopener noreferrer"&gt;Workday's Agent-Ready MCP tools&lt;/a&gt;, because how a tool is designed decides most of what happens next.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes when the tool is written for the model
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://developer.workday.com/documentation/GUID-949559e7-10c3-438a-abb2-e452c23d9da8-enHYPHENus/AgentReadyMCPTools" rel="noopener noreferrer"&gt;Agent-Ready tools&lt;/a&gt; cover areas such as Workforce, Financials, Talent, Payroll and Recruiting, and three choices make them behave differently from the APIs you are used to.&lt;/p&gt;

&lt;p&gt;The first is that the schemas are flat. Instead of something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"supervisoryOrganization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SO-1180"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"manager"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"worker"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;your agent gets one layer of clearly named fields. Less structure to navigate means fewer tokens spent parsing, faster responses and far less room for invention.&lt;/p&gt;

&lt;p&gt;The second is that every tool describes itself. Each one carries metadata about what it does and what each parameter expects, so when an agent sends &lt;code&gt;"Oct 1"&lt;/code&gt; to a field that wants an ISO-8601 date, the error gives it enough to fix the value on the next attempt instead of guessing again. I have written before about &lt;a href="https://oseloka.dev/blog/your-api-already-knows-what-is-wrong" rel="noopener noreferrer"&gt;APIs that already know what went wrong&lt;/a&gt;, and this is that idea applied to a reader that never gets tired of retrying.&lt;/p&gt;

&lt;p&gt;The third is that they speak a standard. The tools follow the MCP JSON-RPC 2.0 specification behind a single endpoint for your tenant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://{agentGatewayRegionEndpoint}/{tenantName}/mcp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your agent connects once, and the model handles tool selection and call syntax from there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bring your own agent
&lt;/h2&gt;

&lt;p&gt;This is the bit I find most interesting as a developer, because you do not have to build inside Workday to use any of this. If your agent lives in Claude, Cursor, Copilot Studio or Google Agentspace, it can call the same tools a Workday Custom Agent does. The difference is that you do the registration yourself.&lt;/p&gt;

&lt;p&gt;A Custom Agent built in Workday discovers and registers its tools automatically when you save and deploy. A third-party agent has to be registered in the Agent System of Record (ASOR) first, with the tools it is allowed to use listed in its agent definition. Here is a trimmed version of the documented example, a preferred name change agent built on Google Agentspace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Preferred Name Change Agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"provider"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SELF-BUILT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"platform"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"GOOGLE_AGENTSPACE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"workdayConfig"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"skillId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"preferred_name_change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"executionMode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Mode=Delegate"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"workdayResources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tool_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"create_worker_preferred_name_change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"agent_resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tool_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list_name_titles"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"agent_resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tool_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list_name_salutations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"agent_resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tool_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list_hereditary_name_suffixes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"agent_resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"tool_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"list_social_name_suffixes"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"agent_resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registering the agent gives you an Agent System User (ASU) token. Authentication is OAuth 2.1 with PKCE only, which most agent platforms and MCP clients already handle natively, so in practice connecting looks like adding any other remote MCP server to your client of choice and pointing it at your tenant's endpoint.&lt;/p&gt;

&lt;p&gt;You might be wondering why that example lists five tools when the agent only does one thing. That is down to prompt tools, which are worth understanding before you register anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tool type that will catch you out
&lt;/h2&gt;

&lt;p&gt;You will see three kinds of tool in the catalogue.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Basic&lt;/td&gt;
&lt;td&gt;Reads data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Advanced&lt;/td&gt;
&lt;td&gt;Writes data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt&lt;/td&gt;
&lt;td&gt;Reads the valid options a write tool needs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Lots of Workday fields only accept a value from a predefined list, and a write tool needs the Workday ID (WID) of the right option rather than a string the model came up with. So before &lt;code&gt;create_worker_preferred_name_change&lt;/code&gt; can submit anything, the agent calls the four prompt tools to fetch valid titles, salutations and suffixes, picks the right one and passes its WID along.&lt;/p&gt;

&lt;p&gt;The catch is that every prompt tool a write depends on has to be in your agent definition too. Leave one out and the write cannot complete. The MCP Explorer lists the required prompt tools in each tool's Details section, so check there before you register.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding the tools you need
&lt;/h2&gt;

&lt;p&gt;The MCP Explorer on &lt;a href="https://developer.workday.com" rel="noopener noreferrer"&gt;developer.workday.com&lt;/a&gt; is where I would start. You can filter by type, read each tool's description and schema, and copy the WIDs you need.&lt;/p&gt;

&lt;p&gt;If you would rather do this from a script or a build pipeline, the ASOR Agent Resource Search API gives you the same catalogue programmatically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="s2"&gt;"https://us.agent.workday.com/asor/agentResourceSearch/v1?searchString=time%20off&amp;amp;toolType=AGENT_READY&amp;amp;protocol=MCP"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer [token]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;searchString&lt;/code&gt; is required and matches partial, case-insensitive names. You can narrow results with &lt;code&gt;toolType&lt;/code&gt; (&lt;code&gt;AGENT_READY&lt;/code&gt; for native tools, or &lt;code&gt;AGENT&lt;/code&gt; to find Workday agents your agent can call as tools), &lt;code&gt;operationType&lt;/code&gt; (&lt;code&gt;CREATE&lt;/code&gt;, &lt;code&gt;PATCH&lt;/code&gt; or &lt;code&gt;VIEW&lt;/code&gt;) and &lt;code&gt;protocol=MCP&lt;/code&gt;. Each result includes the &lt;code&gt;wid&lt;/code&gt; that goes into &lt;code&gt;agent_resource.id&lt;/code&gt; when you register. You will need Extend Professional and a token for a user with the Setup: Agents domain, and depending on your subscription agreement the API may need enabling first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guardrails you do not have to write
&lt;/h2&gt;

&lt;p&gt;Good tools fix the performance problem. What gets an agent past a security review is knowing what stops it doing something it should not, and here none of that lives in your prompt.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;The question&lt;/th&gt;
&lt;th&gt;The answer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Who is it acting as?&lt;/td&gt;
&lt;td&gt;Its own Agent System User, authenticated with OAuth 2.1 and PKCE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whose data can it see?&lt;/td&gt;
&lt;td&gt;In delegate mode, never more than the person it is acting for&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What can it call?&lt;/td&gt;
&lt;td&gt;Only the tools in its agent definition, with security set per skill and per tool&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is the one I would point a security team at. You are not asking the model to please stay in its lane. The platform enforces identity, permissions and an explicit allowlist, whether the agent was built in Workday or in your own stack.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build for change, not for a version
&lt;/h2&gt;

&lt;p&gt;Read this section twice before you ship anything. Agent-Ready tools are one continuous, unversioned surface that Workday keeps optimising for how models use it. Names, schemas, field names and descriptions can change without notice, and the MCP Explorer updates every Tuesday with a CSV change log you can download.&lt;/p&gt;

&lt;p&gt;That is a fair trade for tools that keep getting better for agents, but it changes how you build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use them from agents only. Hard-coded UIs and standard integrations on these tools are not permitted, and anything that depends on a specific tool name, field or structure will eventually break.&lt;/li&gt;
&lt;li&gt;Keep tool names out of your prompts and let the agent discover tools at runtime from their descriptions.&lt;/li&gt;
&lt;li&gt;Know what counts as breaking. Workday keeps it narrow: a tool removed with no replacement, a validation you cannot satisfy, or a security change that takes away access you had. Those come with advance notice. Renames and automatic migrations do not.&lt;/li&gt;
&lt;li&gt;Expect your results to vary. The tools themselves run deterministically, but the model, client, instructions and prompt around them do not.&lt;/li&gt;
&lt;li&gt;Watch for long tool names, which some MCP hosts fail on. If one causes trouble, drop it from your registration and report it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also helps to know the limits up front, which are 2,000 concurrent requests, with a refresh rate and burst limit of 500 requests each. The server supports MCP protocol versions 2024-11-05, 2025-03-26, 2025-06-18 and 2025-11-25, so most current clients will connect without any fuss.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start here
&lt;/h2&gt;

&lt;p&gt;If you want to try this yourself, &lt;a href="https://developer.workday.com" rel="noopener noreferrer"&gt;join the Workday Developer Program&lt;/a&gt; to get access to the MCP Explorer, the docs and a community of people building agents on Workday.&lt;/p&gt;

&lt;p&gt;Try this with your own agent:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Browse the MCP Explorer and pick one read tool and one write tool.&lt;/li&gt;
&lt;li&gt;Note the WIDs, including every prompt tool the write needs.&lt;/li&gt;
&lt;li&gt;Register your agent in ASOR with those tools in its definition.&lt;/li&gt;
&lt;li&gt;Connect your MCP client to your tenant's endpoint and ask it to do something useful.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting part of building enterprise agents was never getting the model to call a tool. It is making sure the tool tells the model enough to get it right, and that the platform refuses when the model gets it wrong.&lt;/p&gt;

&lt;p&gt;In Part 2, I look at what happens when the job does not stop at Workday's edge, and how Custom Agent Actions let you turn your own workflows and third-party services into tools that carry the same guardrails.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>workday</category>
      <category>agents</category>
      <category>apigateway</category>
    </item>
    <item>
      <title>Your Workday orchestration in VS Code, drawn the way the builder draws it</title>
      <dc:creator>Obinna Ekwuno</dc:creator>
      <pubDate>Wed, 30 Sep 2026 14:05:22 +0000</pubDate>
      <link>https://dev.to/workdaydevelopers/your-workday-orchestration-in-vs-code-drawn-the-way-the-builder-draws-it-4loh</link>
      <guid>https://dev.to/workdaydevelopers/your-workday-orchestration-in-vs-code-drawn-the-way-the-builder-draws-it-4loh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;NOTE: This is a read-only viewer. It renders exported files for reference and clarity. Orchestration Builder remains the source of truth for what will or won't validate or compile the viewer never edits anything.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Workday Orchestration Builder is a browser-based, low-code drag-and-drop tool on the &lt;a href="https://developer.workday.com/" rel="noopener noreferrer"&gt;Workday Developer Site&lt;/a&gt; used to create real-time, event-driven workflows and integrations. You get a trigger at the top, steps flowing downward, branches fanning out, loops with their bodies tucked inside, and the whole thing validates and compiles as you go. Then you export the project from your app, open the folder in VS Code, click any &lt;code&gt;.orchestration&lt;/code&gt; file, and all of that visual clarity collapses into one line. &lt;/p&gt;

&lt;p&gt;Not one object —one line —thousands of characters long, every value wrapped in a &lt;code&gt;_type&lt;/code&gt; and &lt;code&gt;_value&lt;/code&gt; envelope, and your editor dutifully renders it as an infinite horizontal scroll. You know there is a flow in there. You built it. You just cannot see it from here.&lt;/p&gt;

&lt;p&gt;Workday already bridges part of this with Local Disk Sync, which saves your app to a local folder and keeps the builder and disk in step. That gets the files into git. It doesn't help when you're reading a colleague's branch, reviewing a pull request, or working somewhere Chrome isn't open.&lt;/p&gt;

&lt;p&gt;The builder is where a flow gets made, and the repo is where it gets reviewed, versioned and shared, and I wanted the second place to be as readable as the first. &lt;/p&gt;

&lt;p&gt;So I built a small extension that carries the builder's picture of a flow into the editor. It is called &lt;a href="https://marketplace.visualstudio.com/items?itemName=ekwuno.orchestrate-flow-viewer" rel="noopener noreferrer"&gt;Orchestrate Flow Viewer&lt;/a&gt;, it works in VS Code and Cursor, and it renders &lt;code&gt;.orchestration&lt;/code&gt; and &lt;code&gt;.suborchestration&lt;/code&gt; files as interactive flow diagrams drawn the way the builder draws them. &lt;/p&gt;

&lt;p&gt;This post is the &lt;em&gt;what&lt;/em&gt;, the &lt;em&gt;why&lt;/em&gt;, and enough of the &lt;em&gt;how&lt;/em&gt; that you could build something similar yourself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4p1tp6x7or6ada1666ym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4p1tp6x7or6ada1666ym.png" alt="an .orchestration file open in VS Code showing the diagram view" width="800" height="622"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does
&lt;/h2&gt;

&lt;p&gt;Any Workday app can be exported so a developer can see its underlying folder structure. Choose any app that has an orchestration in it, export it, and open it up using this tool (any app with &lt;code&gt;appManifest.json&lt;/code&gt; and an &lt;code&gt;orchestration/&lt;/code&gt; directory). &lt;/p&gt;

&lt;p&gt;Click a flow file, and you get the same picture the builder would show you, right there in your editor. The &lt;strong&gt;Start&lt;/strong&gt; or trigger step sits at the top and the steps flow downward along a spine. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Branch on Conditions&lt;/strong&gt; arms fan out side by side, in the same left to right order the builder evaluates them under little white pills and reconverges at a join. When it comes to &lt;strong&gt;Loop&lt;/strong&gt; bodies, they are nested under their loop card behind a chevron you can collapse. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Error&lt;/strong&gt; handlers are the one place I deliberately depart from the builder. There, local and global handlers open on their own canvas. A static diagram can't click through, so the viewer pulls them inline: the global handler as a detached region below the main flow, and local handlers as a badge on the step that owns them, borrowing the builder's lightning bolt icon.&lt;/p&gt;

&lt;p&gt;If you can read a flow in Orchestration Builder, you can read it here, because the visual language is the same on purpose.&lt;/p&gt;

&lt;p&gt;Selecting any step opens a details panel loaded with context straight from the builder:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Step Context: Review assignments, expressions, branch conditions, loop iterators, add integration message severity, and store-document settings.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Resource Resolution: Inspect CSV columns resolved directly from the flow's resources alongside suborchestration parameters.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Subflow Navigation: Click Call Subflow cards to instantly jump to target suborchestrations across your workspace without waiting for page loads.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Developer Environment Tools: Enjoy full editor support with pan, zoom, a minimap, live re-rendering on edit, theme matching, and a toggle for pretty-printed raw JSON.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why a viewer, and why read-only
&lt;/h2&gt;

&lt;p&gt;Orchestration Builder remains the place to create orchestrations—this project doesn't change that. But once you download the source, you're dealing with files. And files belong where developers already work: in repositories, branches, and pull requests ready for review.&lt;/p&gt;

&lt;p&gt;As Workday expands its developer platform, it brings an expectation that you can work in standard development environments. You build in Orchestrate, then review, version, and share in VS Code or GitHub just like any other code you ship. &lt;/p&gt;

&lt;p&gt;However, the file reads like it was written for the compiler, not for you. The IntelliJ plugin takes the same view, treating the orchestration folder as read-only and pointing you back to Orchestration Builder to edit. The viewer follows that convention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The goal is intentionally focused: make exported files readable right where they live—nothing more.&lt;/strong&gt; It isn't a secondary editor or a replacement for the builder. The viewer simply renders what is in the file, which doesn't guarantee the flow will validate or compile.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the file knows, and what the builder knows
&lt;/h2&gt;

&lt;p&gt;To reconstruct the builder's visual layout from an exported file, I first examined the underlying file structure. Two key characteristics stood out immediately. First, every single value is wrapped inside a type envelope, as shown when pretty-printing a flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CreateValues"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"String"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"InputFile"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"isDisabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Boolean"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"errorHandler"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Opt"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ErrorHandler"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ErrorHandler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"…"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"…"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Taking &lt;code&gt;errorHandler&lt;/code&gt; as an example, it is represented as an &lt;code&gt;["Opt", "ErrorHandler"]&lt;/code&gt; wrapper enclosing another inner wrapper. Optional fields effectively create nested envelopes, where an absent optional evaluates to an envelope wrapping null. Furthermore, certain exporter versions emit un-wrapped bare objects for group bodies, requiring the parser to handle both representation styles seamlessly.&lt;/p&gt;

&lt;p&gt;The second, more subtle realization influenced the entire architectural approach: diagram information exists within the Orchestration Builder interface rather than the source file itself. The exported file stores structural data rather than graphical layout—omitting explicit edges, node identifiers, or spatial coordinates. Execution sequence is inferred from array ordering, branching is established through element containment, and loop bodies are structured as nested lists. While this compact format is optimized for platform execution and compilation, re-creating the builder's interactive diagram within VS Code required translating these structural semantics back into a visual node graph.&lt;/p&gt;

&lt;p&gt;This led to a multi-stage pipeline architecture designed to make implicit relationships explicit, enforcing a clear separation of concerns at each phase:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.orchestration file (text)
        │
        ▼
  src/core/parse.ts      unwrap the envelopes → typed FlowModel
        │
        ▼
  src/core/graph.ts      synthesise nodes and edges → FlowGraph
        │
        ▼
  src/core/layout.ts     assign x/y/w/h to every node
        │
        ▼
  src/webview/…          render with React Flow inside VS Code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All components in &lt;code&gt;src/core/&lt;/code&gt; consist of pure TypeScript without any VS Code API dependencies. Consequently, the core pipeline can execute in standalone Vitest suites or standard browser environments. The VS Code extension itself acts as a lightweight wrapper around this core logic—a design boundary that significantly simplified testing and maintenance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teach the parser the envelope, once
&lt;/h2&gt;

&lt;p&gt;Before building the parser, I established a clear vocabulary for interpreting the format: eight utility functions in &lt;code&gt;src/core/unwrap.ts&lt;/code&gt; designed to handle any output variation from the exporter. The core work is handled by peel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="cm"&gt;/**
 * Peel wrapper-of-wrapper layers (e.g. Opt&amp;lt;ErrorHandler&amp;gt; whose _value is the
 * wrapped ErrorHandler) down to the innermost wrapper. A null Opt stays put,
 * so callers see unwrap(peel(v)) === null for absent optionals.
 */&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;peel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;isWrapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nf"&gt;isWrapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cur&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;cur&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Building on this foundation are &lt;code&gt;field&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt;, &lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;num&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, and &lt;code&gt;record&lt;/code&gt;. Each helper safely evaluates wrapped or bare objects, returning &lt;code&gt;undefined&lt;/code&gt; or an empty list for unexpected shapes instead of throwing an error. Routing all property access through these utilities encapsulates envelope parsing entirely. This enforces a core architectural rule across the project: malformed input degrades gracefully rather than crashing the application.&lt;/p&gt;

&lt;p&gt;Assume Workday will ship something you have not seen&lt;/p&gt;

&lt;p&gt;Workday releases new Orchestrate components regularly. This means the parser will inevitably encounter step types it hasn't seen before—and it will meet them in real user projects, not just in test fixtures. The &lt;code&gt;default&lt;/code&gt; case of &lt;code&gt;parseStep&lt;/code&gt; gracefully handles these situations using a two-tiered approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Unseen component types must never break the diagram. If the payload&lt;/span&gt;
  &lt;span class="c1"&gt;// carries a body of child steps (loop-like components such as Batch&lt;/span&gt;
  &lt;span class="c1"&gt;// Loop / Join Loop), render it as a container so the children survive.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;groupRaw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hasGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;groupRaw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;directNodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;hasGroup&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;directNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown container type "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rawType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) — rendered generically.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;hasGroup&lt;/span&gt;
      &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nf"&gt;parseGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;groupRaw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;childPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;directNodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;parseStep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;childPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;genericContainer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;leafEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;group&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nodes&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;warnings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Unknown step type "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;rawType&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;" (&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;) — rendered generically.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;generic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;leafEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a component I haven't written a parser for contains child steps, it is parsed as a generic container. This allows any nested steps to be parsed normally and displayed as functional cards. Any other unrecognized structure becomes a generic step, displaying a raw dump of its fields in the details panel for complete visibility. Both paths increment a warning counter in the toolbar. While an unseen component might lack a custom icon, it will never break the visual diagram.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I wrote 150 lines instead of using dagre
&lt;/h2&gt;

&lt;p&gt;When it came to the layout stage, using an existing graph library like &lt;a href="https://github.com/dagrejs/dagre" rel="noopener noreferrer"&gt;Dagre&lt;/a&gt; or &lt;a href="https://github.com/kieler/elkjs" rel="noopener noreferrer"&gt;ELK&lt;/a&gt; seemed like the obvious choice. However, I decided to build a custom solution instead. General-purpose layout engines tend to produce organic structures that shift dynamically as the graph changes. In contrast, the Orchestration Builder interface relies on a consistent, highly predictable layout: a single vertical spine flanked by neatly aligned branch arms.  &lt;/p&gt;

&lt;p&gt;Branch order matters too,  Orchestrate evaluates Branch on Conditions arms left to right and runs the first that's true, so a layout engine that reorders arms is misreporting execution order. A custom algorithm guarantees this exact structure every time—ensuring identical input always yields identical output.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;layoutGraph&lt;/code&gt; operates using two recursive tree passes. The first pass measures bottom-up, memoizing each subtree's bounding box so branch arms know their required width before placement. The second pass places elements top-down, centering items along the spine and advancing a y-cursor. Branch arms are centered as rows beneath their root node, and joins are placed at the measured bottom so all paths reconverge seamlessly. &lt;/p&gt;

&lt;p&gt;Centralizing node dimensions in a single lookup table makes global visual adjustments simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SIZES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;NodeKind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;118&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;220&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;branchRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;globalError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;84&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;pill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;132&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;expander&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;join&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deterministic layout also makes the visual structure fully unit-testable. For instance, a key test suite iterates through every node pair across all test fixtures to verify that no elements overlap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;has no overlapping nodes&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;overlaps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; overlaps &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a measurement pass undercounts a subtree, overlapping cards immediately fail the test with the precise node IDs responsible. This test caught several real layout bugs during development—an advantage made possible solely by deterministic positioning, which third-party layout engines cannot reliably guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rest is plumbing
&lt;/h2&gt;

&lt;p&gt;The webview component renders via React Flow. Since the layout phase computes absolute node positions in advance, the webview primarily maps graph nodes directly to locked React Flow elements—ideal for a read-only viewer. &lt;/p&gt;

&lt;p&gt;Edges use right-angle elbow connectors rather than bezier curves, categorized by function so horizontal segments route exactly where the Orchestration Builder would place them. Furthermore, because all color styles leverage VS Code's CSS variables, light and dark themes work seamlessly out of the box without requiring custom styling code.&lt;/p&gt;

&lt;p&gt;On the extension side, a &lt;code&gt;CustomTextEditorProvider&lt;/code&gt; allows VS Code to handle document state, text editing, and file watching natively, leaving the extension focused entirely on rendering. Communication between the host and webview relies on a lightweight, typed protocol. Incoming edits stream in with debouncing so side-by-side editing won't trigger heavy re-layouts on every keystroke. Additionally, a workspace index scans orchestration IDs, enabling Call Suborchestration cards to display target names and open corresponding files on click.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;If you're building a similar extension or tool, here are three core principles worth applying:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Identify the core problem first:&lt;/strong&gt; The goal was never just to render raw JSON, but rather to bring Orchestration Builder's visual clarity directly into the editor environment. Defining this goal made designing the processing pipeline straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep core logic decoupled:&lt;/strong&gt; Avoiding VS Code API dependencies in the core pipeline enabled comprehensive unit testing against exported files, allowed fast diagram iteration in a standalone browser harness, and minimized webview complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Design for future schema changes:&lt;/strong&gt; Implementing a two-tiered fallback mechanism alongside a warning counter ensures that newly released Workday components render gracefully without breaking the layout achieved in about thirty lines of code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start here
&lt;/h2&gt;

&lt;p&gt;Getting started takes only a couple of minutes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the extension: &lt;a href="https://marketplace.visualstudio.com/items?itemName=ekwuno.orchestrate-flow-viewer" rel="noopener noreferrer"&gt;Orchestrate Flow Viewer on VS Code Marketplace&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Open a downloaded: Select any project folder containing an &lt;code&gt;appManifest.json&lt;/code&gt; and an &lt;code&gt;orchestration/&lt;/code&gt; directory. &lt;/li&gt;
&lt;li&gt;View the flow: Click any flow file and toggle the JSON view once to compare the formatted diagram against raw source data.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The source code is available on GitHub under the MIT license at &lt;a href="https://github.com/Ekwuno/workday-orchestrate-viewer" rel="noopener noreferrer"&gt;https://github.com/Ekwuno/workday-orchestrate-viewer&lt;/a&gt; . Upcoming roadmap features include breadcrumb navigation for subflow drill-ins and PNG/SVG export options. If you encounter rendering issues with an exported flow, please open an issue in the repo &lt;/p&gt;

&lt;p&gt;Orchestration Builder has always understood your flow's visual layout now your editor does too. Head to &lt;a href="https://developer.workday.com/about" rel="noopener noreferrer"&gt;developer.workday.com&lt;/a&gt; and join the Workday Developer Program, where you'll find the guides, the community and everything you need to start building.&lt;/p&gt;

</description>
      <category>workday</category>
      <category>api</category>
      <category>vscode</category>
      <category>devrel</category>
    </item>
  </channel>
</rss>
