So now we have successfully developed our first executor open URL. But before we go on to creating the rest of the nodes and their respective executors, I think it's first time to completely finalize everything that a single node can do. And right now our open URL node can almost do everything we wanted to do. But one thing we didn't implement is data pass through. So when I connect open URL to open URL 2 to open URL 3 and when I click on open URL 3, what I would expect is to see all of the previous connections it has so I can interpolate the output data from the previous node and inject it into its values.
This is a crucial part of any workflow application Data pass-through. You might have noticed that in the previous few chapters we focused more on manual coding than on agentic coding. The reason behind that is because I wanted us to establish a very deterministic state of our code base where both you and I have the exact same code. And now that we have a fully functional executor and we have fully functional write toolbar, we can now build on top of all of those stable and deterministic components we've developed more reliably using agentic coding. Since some of the prompts in this chapter are going to be a bit longer, I don't want you to waste time pausing the video and retyping what I type, so I've prepared chapter 17 data pass-through inside of the specifications folder, which you can look at and then copy and paste inside of your coding agent so we have the exact same prompt.
Let's start with developing an interpolation helper whose purpose is going to be to transform a value like this into an actual value extracted from the output of the node to which this node is connected to and to any previous node that data was passing through in this workflow. So let's review the first prompt, which will be used to generate the interpolation helper. I want to let a workflow field pull in another node's output by writing a placeholder like this template or this template. To make that work, I need a small pure helper that takes one field's text plus a collection of every node's output from this run and make sure that all of those outputs are keyed by node ID. And returns the text with each placeholder swapped for the value that it actually points at from all of those previous node outputs.
If a placeholder resolves to nothing, let's simply replace it with an empty string. If it resolves to an object, let's drop in the JSON. It has to resolve nested paths. So lean on whatever small getByPath utility is cleanest. Put it in the workflow features helpers and call it interpolate.
So hopefully it's going to develop it inside of features lib right here. Let's go ahead and run this prompt. You should now have a new file called interpolate located inside of features workflows lib and its purpose is to transform placeholders like this template into actual field for values pulled from this runs node outputs which are keyed by node ID. Perfect. So now we have this interpolate function which accepts text and all of the previous outputs in this run and then it can replace the placeholder which we define using a regex so that's how it finds the double brackets and it injects whichever value is referenced from the output object.
Perfect. So I'm assuming your function looks somewhat similar. Now let's go ahead on to the next prompt. The second prompt is pass outputs through the run inside of the run workflow task. Let each node use the output of the nodes before it.
Right Now, we run nodes in order and throw their results away. So instead, keep each node's result as we go, keyed by its ID. Then, right before running a node, replace the placeholder in its values with the matching upstream data using the interpolate helper we just made. Nodes already run in dependency order. So anything a node references has produced its output by the time we get there.
Perfect. So a natural continuation of this interpolate helper which we just developed. So this interpolate helper by itself is just a pure function that could be used in any other project. So what we have to modify now is the actual run workflow task. What we do now is we pass the raw values from here.
So we should somehow transform these values by capturing all of the output that happened in the previous nodes and then run it through our new interpolate function. So let's go ahead and paste that prompt here and let's execute it. I would highly recommend doing this in the same conversation so your agent has context of what we are developing. There we go. So our run workflow task now has an accumulator as a constant called outputs, which is an empty object.
And that collects each executor's return keyed by node ID. We now interpolate before running. So each field in node.data.values, which was previously passed in raw, is now passed through interpolate. So if the node data values has any placeholder like this one, it will resolve to the upstream results just before the node executes. And at the end, we store the result to the accumulator keyed by the ID.
So await executor values getStageHand returns its value stored inside of the outputs ID object. And we didn't do anything else because Toposort already takes care of everything. So Let's just go through the code quickly. So we import our new interpolate util, we define the accumulator which is just a constant called outputs and then in the for loop of our nodes we go ahead and first check if we don't have an executor we manually continue but if we do have an executor we make sure that we don't just pass in the raw values like we previously did like no data values but instead we construct new values using our interpolate here and then we don't just randomly throw away the results of the executor. We actually store them inside of our accumulator right here so every subsequent step can properly interpolate using exact output.
Now let's do a few changes in the node registry which are simple enough for us to do by hand. In between node field and node definition, let's go ahead and add a new type node output. Then let's go inside of the node definition and after fields let's go ahead and make it so that each node definition also needs to have an output. So now inside of our node registry we're going to have to strictly define exactly what each node can return from its values. For example, the start node doesn't return absolutely anything.
So what does the open URL node return? Well, to figure that out, all we have to do is go inside of the open URL inside of our nodes. And in here we can see exactly what this node returns. So let's add URL and title inside of the open URL node registry outputs. Now let's construct a prompt that's going to develop the upstream outputs hook.
So I want people to reference an upstream nodes output without typing raw node IDs. Give me a hook that takes the node I currently have selected and returns every output that any node upstream of it produces, each as a ready-to-insert template. A friendly label like open URL 1 which is the name of the node which I'm inserting and then its value. And the source node's type so I can show its icon. Follow the connections all the way back up the graph, not just the node's direct parents, and recompute it as I connect and disconnect edges.
Each node already declares which output it exposes. Put it in the workflow features hooks and call it use upstream connections and follow the CloudMD rule or agents.md if you're not using cloud for using React flow. So what exactly is this for? Well right now with what we currently have we are actually able to preview the value of the previous node. The problem is we would have to manually type the template and we would have to know the ID of each previous node that came before us, which is not a good user experience.
So what we need is a hook that's going to serve as the business logic behind these chips here that the user can click on, which will automatically insert the value of the previous node in form of a token, in form of a placeholder. So let's go ahead and run this prompt in our existing conversation. Let's take a look at our new hook. So what does useUpstreamConnection return? It returns an array of upstream connections, which is one entry per output of every node upstream of the selected node.
Inside of the upstream connection, we have a token, which is a ready to insert placeholder like we expected, and it matches the syntax our interpolate helper resolves at runtime. We have a label which is displayed like this, so the name of the node and then the value which we are inserting, the upstream node's data title and then the output's registry label. We then have node type, the source nodes type. So the caller can render node registry node type icon, which is what we want for our chips components. So how does it work?
It reads the selected node, all nodes and all edges reactively from the React flow store via use store. The pattern is already used inside of the right sidebar. Since edges are part of the selection it recomputes on every connect and disconnect and on selection change. It works the whole upstream chain, not just direct parents. Perfect.
So let's actually take a look at the code here so we can see if anything is off even though just by this description I can see it's exactly what we wanted here. So this is the upstream connection type which it just talked about and then in here we have the actual hook. So what's interesting is that my agent generated a hook which doesn't accept any params. So if you created a hook which accepts params, it's perfectly fine. It doesn't need to be line for line, right, because we are writing prompts which simply explain the idea what we want.
They aren't necessarily, you know, very strict and made so that the next prompt that we do won't work unless your code is identical to mine. No, that's not going to happen. Don't worry. So whether you have props here or you just directly use React flow hooks inside, it's the same thing. So yes, technically, because we have access to use store, we don't need to pass any props.
We can just access a selected node from here. And then in here, It basically does exactly what we told it to do. It goes through the outputs of all the previous nodes and it returns a flat map of all the possible chips which we can insert for this selected node. Exactly what we wanted. So when I select a node, I need some hook to compute how many of these connections do I have.
So now that we have this, we can build the UI component which is actually going to render all of these connections. Now let's review prompt number five, connection chips in the inspector. So in the right sidebar under the selected nodes fields add a connections section that shows up whenever use upstream connections hook we just developed returns any outputs for that node. Render each available output as a small chip with the source node's icon and its label. When I click on a chip, insert its token into whichever field I was last editing or the first field I haven't touched one yet.
I want this for every connected node, not only the ones with a large text field. And since we're Once again using React flow, it's just a little helper here. So what does this mean? Well now is the time for us to build this UI. So we now have the backend, we now have the registry and we now have the client hook which provides us with a flat map, we can simply iterate over and display to the user these are all the connections you have and when you click on one of those it's simply going to insert it into whichever field you were last focused on.
So let's go ahead and run this prompt in our existing conversation. So let's read through the output. Here's what I added to right sidebar. Connections section. It renders under the selected nodes field.
Whenever our new hook use upstream connection returns any outputs. Because the hook reads selection from React flow store and walks all the ANGs ancestors, this appears for every connected node, exactly what we want, regardless of whether it has big text or just single line inputs. We also rendered the chips. So each available upstream output is now a small button showing the source node's registry icon using node icon and its label like open URL 1 and title, wrapping it in a flex row and truncating long labels. So we will see how this looks like.
We can always tweak the design easily. And we have click to insert. Clicking a chip appends its node ID path token to a target field. Field component now reports focus via an on focus prop. The inspector tracks active field key.
Okay, so this is the logic to determine which was the last field the user was typing in so when we click on the actual chip it knows in which field to insert. Beautiful. Reset on selection change. Yes, so every time you change the nodes using the key, it simply resets the entire component. Brilliant.
So just by this definition, I already know this is exactly what we want but still let's take a look at the code here. So right sidebar component now imports use upstream connections. Our field function now has on focus and it maintains on focus. If yours didn't do that, it's not a big mistake. This is just so the chips insertion is aware in which field you're currently working because some nodes can have like three input fields.
So when you click on like this is simple because we only have one field but what if we had three fields? How would it know into which field to insert the connection? So that's what it does here. It added the on focus prop here and then I assume that in the inspector or wherever the fields are rendered it changes the set active field key to that active field. And then the insert token knows where to insert into which field key.
In here we have update node data, which simply adds that token, which is the structured from use upstream connections, connections, beautiful. So very surgical change and this is where we render the actual connections we have available. So we'll see how this looks like. We'll compare to our design and see if we like this or not and the inspector now has the selected ID key added here simply so it destroys the whole component and rebuilds it. This is like an easy way of resetting whatever was loaded inside.
Very very clean change. I love this. Remember if any of your changes look different or if the end result which we're going to test now doesn't work, feel free to simply open, this is chapter 17 in my GitHub repository, find the branch and take a look at all of these files. Find them and perhaps discuss with your agent, discuss why something didn't work or what do we need to do to make it work like these files so you have a more interactive experience in a tutorial. Now let's review these changes in our app.
So right now I have one start node and one open URL node and nothing looks different. So if I go ahead and attach an open URL 2 to my open URL 1, There we go. I can now inject the value of this previous node into this node right here. One thing we could improve, maybe give yourself a homework and maybe this can be a challenge, try to make your fields actually display the value somehow. Because right now, we don't really care, right?
Because what's actually going to interpolate the result of this template is inside of our run workflow. That's why we have this interpolate here. So that is now going to receive this template and it's then going to transform that into the actual value of this output right here. So I think that in theory now, I should be able to access both of these nodes and both of these nodes should now open codewithantonio.com that is because the output of our open URL as you can see is the URL that we are currently on. So I think this should just translate to another version of HTTPS code with Antonio.com.
So make sure that you have trigger.dev running and execute this function. So if you now go into your latest session here, you will find that two pages were accessed. So first one was CodeWithAntonio.com and then the second one, once again CodeWithAntonio.com, meaning that our data interpolation officially works. And here's the best part. So if I go ahead and now chain open URL 3, I can now access open URL 2 and open URL 1.
So that's the whole point of building this data pass-through. And I think it was really fun developing this entire thing using agentic coding. So this is why I kind of postponed using prompts until now, because I wanted us to establish a very stable code base because now is when it starts to be fun to build with agents because as you can see the outputs are very deterministic it's almost exactly what we imagined if I look at my picture of course there might be some slight differences in these chips, but honestly I'm fine with them. I like them this way. If yours look drastically different, you can either, you know, modify them yourself or use this image and maybe take a screenshot of these images and then inside of the existing prompt here, go ahead and tell it make the chips look like this if your agent has visibility and can access the images.
Beautiful. I'm so happy about how this went because we now have proper data interpolation and data pass-through. Obviously this will be a bit more exciting once we have different nodes, something other than open URL, but I'm pretty satisfied with this. And now let's go ahead and stage, commit, and git push our changes.