Andre
+2
May 20, 2026
visibility 556
star star star star star
(1 votes)

Optimizely Opal: How to Build Effective Workflow Agents

If you're building workflow agents in Optimizely Opal, this post covers how specialized agents pass context to each other, why keeping agents small and focused matters, and a useful trick for handling situations where you're not sure how to proceed.

I recently decided to put Optimizely Opal through its paces. The experiment: build a simple mock API, feed external data into Opal, and see if a workflow agent could take it from there and autonomously publish content to the CMS, with no human intervention required.

The workflow agent I built pulls data from an external mock API on a schedule, analyzes it, checks it against existing content in the CMS, makes a judgement call on whether a new article is warranted, and if it is, writes and publishes it to the CMS, all without any human intervention.

It is made up of 5 specialized agents, each with a single responsibility, and a condition that branches the workflow based on whether a new article is warranted.

My example consists of the following:

  • An agent that retrieves route information from the mock AviationStack API
  • An agent that compares the route info with articles published in the CMS, and makes a judgement call on whether the info from the API call warrants a new article in the CMS
  • An agent that writes an article based on the info received from the external mock AviationStack API
  • An agent that publishes the new article to the CMS
  • An agent that gracefully ends the workflow if no new article is needed


image.png

The full workflow agent. A scheduler triggers the process, data flows through each specialized agent in sequence, and a condition branches the workflow: TRUE continues to writing and publishing, FALSE ends the workflow cleanly

 
If you want to build a similar workflow agent yourself and apply some of the tips here in practice, here’s what you need:

  • Optimizely Opal
  • Access to Optimizely CMS
  • A mock API with a Custom Tool configured in Opal
  • Basic familiarity with creating specialized agents in Opal

Here's what I learned


There’s no prompting between specialized agents

Each agent passes its output directly to the next and runs completely on its own, with no human in the loop. This keeps the workflow consistent and predictable, but it also means you need to be deliberate about what each agent outputs, because the next agent depends on it entirely.

Agent 1 output:

image.png

The first specialized agent outputs a simple JSON object containing the route information retrieved from the mock AviationStack API


Agent 2 Input:

image.png

image.png

That same "routes" object becomes the input for the next agent, defined as a required variable and referenced directly in the prompt template. This is what "output becomes input" looks like in practice.

Create as many specialized agents as you need

Don't hesitate to create as many agents as your workflow needs, and be as granular as necessary. Each agent should do one thing well, not many things adequately.

Developers will recognize this as the Single Responsibility Principle (SRP), and it applies just as well here. For those less familiar with the term, the idea is simple: don't create one agent that retrieves external data, analyzes it, writes an article, and publishes it. That's too much for one agent to handle reliably, and you'll end up with bloated instructions. Split those responsibilities across separate agents instead, and let them pass context to each other.

image.png

A single agent handling too many responsibilities. This is harder to debug, harder to maintain, and more likely to produce inconsistent results.

image.png

The same workflow split across three focused agents, each with a single responsibility, and each passing its output to the next.

If you run into a gray area and you're not sure how to handle it, create a specialized agent

This is simpler than it sounds. When you hit a question like "How do I get it to write an article?", you create a specialized agent for it. "How do I get it to publish to the CMS?", you also create a specialized agent for it. Most problems in a workflow agent have the same answer: a focused, purpose-built specialized agent.

A good example from my own workflow: when the condition evaluates to false, meaning the external data doesn't warrant a new article, I needed the workflow to simply stop cleanly. There's no built-in "do nothing" option, but the answer was straightforward: create an "End Workflow" agent whose only job is to acknowledge the decision and exit gracefully.

image.png

The "End Workflow" agent keeps it simple by design. No tools, no content creation, no external calls. Its only job is to end the workflow cleanly when no action is needed, which is exactly the kind of focused single-responsibility agent your workflow will thank you for later.

Don't assume an agent has context from earlier in the workflow unless you explicitly pass it

This is an important one. Just because multiple specialized agents live inside the same workflow agent doesn't mean they automatically share context. If Agent 4 needs data that Agent 1 retrieved, you need to explicitly pass it through each agent's inputs and outputs along the way, it won't be there by default.

In my workflow, by the time the article-writing agent needs to do its job, it has no automatic awareness of the route data retrieved at the start. If I want it to have that context, I need to explicitly include it in the outputs of the agents before it, and specify it as an input to the agents that follow.

image.png

The output schema of my "Gap Analyzer" agent explicitly carries the "routes" object forward, alongside its own "reason" and "publishDecision" fields. Without "routes" being defined here as an output, the article-writing agent downstream would have no access to the original API data, even though it was retrieved earlier in the same workflow.

That's about it for now! Hope this helps someone else out. If you've built something similar or have any questions or suggestions, I'd love to hear about it in the comments. Thanks for reading!



May 20, 2026

Comments

error Please login to comment.
Latest blogs
Add more scheduled job settings from the Optimizely CMS 12 admin UI -- with OptiScheduledJob.ExtraParameters

  Optimizely (EPiServer) CMS 12 ships a great scheduled-jobs framework, but it has one frustrating gap: a job has nowhere to store its own...

Binh Nguyen Thi | Jun 25, 2026

Automated Search & Navigation to Graph Migration with Claude Code

A Claude Code plugin that scans your S&N codebase, applies Graph SDK transformations, and validates the result. Install once, run one command. CMS ...

Connor Fortin | Jun 24, 2026

Migrating from Find to Graph: Lessons Learned from a Real CMS 13 Project

While migrating a search solution from Optimizely Search & Navigation (Find) to Optimizely Graph in CMS 13, I encountered several issues that were...

Binh Nguyen Thi | Jun 24, 2026

Optimizely: Upgrade Opti-ID and .NET 10 in CMS 12

Many Optimizely customers are planning their roadmap around a future migration to Optimizely CMS 13. As a result, upgrades such as Opti ID adoption...

Madhu | Jun 23, 2026 |

Understanding Optimizely Graph: Caching, Webhooks & Avoiding Stale Content (Optimizely SaaS CMS)

📌 Scope: This post covers Optimizely CMS (SaaS) only — using the official @optimizely/cms-sdk and @optimizely/cms-cli packages with Next.js 15. If...

Kiran Patil | Jun 23, 2026 |

Optimizely Content APIs: the Setup the Docs Don't Walk You Through

CMS 13 is pushing things firmly in the direction of Optimizely Graph, but plenty of teams are still running on older CMS versions, or have good...

Andre | Jun 22, 2026