<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">Blogs</title><link href="https://world.optimizely.com/blogs/" /><updated>2026-10-12T10:30:34.0000000Z</updated><id>https://world.optimizely.com/blogs/</id><generator uri="https://world.optimizely.com/blogs/" version="2.0">Optimizely World</generator><entry><title>cms13-scan: A Deterministic CMS 13 Readiness Scanner</title><link href="https://www.adnanzameer.com/2026/10/cms13-scan-optimizely-cms-13-readiness-scanner.html" /><id>https://www.adnanzameer.com/2026/10/cms13-scan-optimizely-cms-13-readiness-scanner.html</id><published>2026-10-12T10:09:07.0000000Z</published><updated>2026-10-12T10:30:34.0000000Z</updated><author><name>adnan.zameer@episerver.com</name></author><category term="Blog post" /><summary type="html">&lt;!--excerpt: A free, open-source, deterministic .NET global tool that scans an Optimizely CMS 12 codebase for CMS 13 breaking changes before you touch a single line of upgrade code. No AI, no tokens, no API keys, just static analysis you can run in CI.--&gt;

Every CMS 12 to CMS 13 upgrade I have looked at starts the same way. Someone opens the solution and starts hunting. A text search for the APIs</summary></entry><entry><title>Setting Up an Optimizely CMS 13 MCP Server Locally with Codex</title><link href="https://world.optimizely.com/blogs/mardy-abdelkareem/dates/2026/10/getting-started-with-optimizely-cms-13-mcp-server-in-vs-code-using-codex/" /><id>https://world.optimizely.com/blogs/mardy-abdelkareem/dates/2026/10/getting-started-with-optimizely-cms-13-mcp-server-in-vs-code-using-codex/</id><published>2026-10-12T04:41:05.0000000Z</published><updated>2026-10-12T04:41:05.0000000Z</updated><author><name>mardy.abdelkareem@firstlinesoftware.com</name></author><category term="Blog post" /><summary type="html">&lt;p&gt;Optimizely CMS 13 supports &lt;a href="https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro"&gt;Model Context Protocol (MCP)&lt;/a&gt;, allowing AI tools such as Claude and Codex to interact with CMS content and metadata through an MCP server.&lt;/p&gt;
&lt;p&gt;In this post, I'll show how I configured the MCP server in my local CMS 13 Alloy project, connected it to Codex in Visual Studio Code, and used it to explore content types, create pages, and build a Visual Builder experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Optimizely CMS 13 (I'm using Alloy with CMS 13.3.0).&lt;/li&gt;
&lt;li&gt;An MCP-compatible AI client (I'm using Codex in VS Code).&lt;/li&gt;
&lt;li&gt;A local CMS instance running over HTTPS, for example, https://localhost:5000.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Configure the MCP Server&lt;/h2&gt;
&lt;p&gt;Install the &lt;strong&gt;Optimizely.Cms.Opal.Tools&lt;/strong&gt; NuGet package (version 13.2.0 or later):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dotnet add package Optimizely.Cms.Opal.Tools --version 13.2.0&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Register the MCP server in &lt;strong&gt;Startup.cs&lt;/strong&gt; using client-secret authentication:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;services.AddCmsMcpServer()
    .WithClientSecretAuthentication();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then map the MCP endpoint and require client-secret authorization:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.UseEndpoints(endpoints =&amp;gt;
{
    endpoints.MapContent();

    endpoints.MapMcp("/mcp")
        .RequireClientSecretAuthorization();
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The MCP endpoint is now available at &lt;strong&gt;https://localhost:5000/mcp&lt;/strong&gt;, or your application's configured HTTPS address.&lt;/p&gt;
&lt;h2&gt;Configure Client-Secret Authentication&lt;/h2&gt;
&lt;p&gt;Make sure a client secret is configured for your local CMS application. We'll use it to authenticate Codex when connecting to the MCP server.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; Never commit your client secret to source control or expose it in screenshots.&lt;/p&gt;
&lt;h2&gt;Connect Codex in VS Code&lt;/h2&gt;
&lt;p&gt;Open &lt;strong&gt;Codex Settings &amp;rarr; MCP servers &amp;rarr; Add server&lt;/strong&gt; and select &lt;strong&gt;Streamable HTTP&lt;/strong&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Name:&lt;/strong&gt; optimizely-cms&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URL:&lt;/strong&gt; https://localhost:5000/mcp&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bearer token env var:&lt;/strong&gt; Leave empty.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Header:&lt;/strong&gt; Authorization, with your configured client secret as the value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alternatively, configure the server in Codex's &lt;strong&gt;config.toml&lt;/strong&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[mcp_servers.optimizely-cms]
type = "http"
url = "https://localhost:5000/mcp"

[mcp_servers.optimizely-cms.headers]
Authorization = "your-secret-value"&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace the placeholder with your actual secret and enable the server in Codex Settings. The secret is passed directly in the Authorization header, without a Bearer prefix.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;img style="width: 100%; max-width: 500px; height: auto;" src="/link/1b35b3874d3149f7a6065da7c50869d3.aspx" alt="Codex MCP server configuration" /&gt;&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;em&gt;Configuring the local Optimizely MCP server in Codex.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Verify the Integration&lt;/h2&gt;
&lt;p&gt;With the CMS running, open a new Codex chat in VS Code and try:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;List the available content types in my local Optimizely CMS instance using the MCP server.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Codex retrieved the registered content types, including &lt;strong&gt;StandardPage&lt;/strong&gt; and &lt;strong&gt;StandardExperienceData&lt;/strong&gt;, and could inspect their properties, locales, and available Visual Builder components.&lt;/p&gt;
&lt;p&gt;You can also retrieve existing content:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Find the published content items of type ProductTestPage and show their properties.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;These read-only operations confirm that Codex can access CMS content and metadata through MCP.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;img style="width: 100%; max-width: 650px; height: auto;" src="/link/7ff3dbdcef3d48c59233445efb77db39.aspx" alt="Codex discovering Optimizely CMS content types" /&gt;&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;em&gt;Codex retrieving content types from the local CMS instance.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Create Content Using Codex&lt;/h3&gt;
&lt;p&gt;Next, I asked Codex to create an unpublished page:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Create "MCP Demo Page" under Start using StandardPage. Add a heading and paragraph to MainBody, and save it as an English draft.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Codex created the page and updated its content through the MCP server.&lt;/p&gt;
&lt;p&gt;I also tested Visual Builder:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Create an unpublished "Alloy Demo Experience" using StandardExperienceData, with a BlankSection containing a VisualBuilderTestBlock.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Codex created the experience using the existing CMS content types and Visual Builder components. Both items were saved as drafts without publishing.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;img style="width: 100%; max-width: 750px; height: auto;" src="/link/058a28868a6d4b519226eaefffb487d1.aspx" alt="MCP Demo Page created in Optimizely CMS" /&gt;&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;em&gt;The MCP Demo Page created through Codex and saved as a draft.&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Troubleshooting Content Permissions&lt;/h3&gt;
&lt;p&gt;During content creation, I encountered an authorization error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Access denied: You do not have permission to create content in this location.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The MCP client authenticated successfully with a &lt;strong&gt;CmsAdmins&lt;/strong&gt; role claim, which was mapped to &lt;strong&gt;WebAdmins&lt;/strong&gt; in the application configuration. Although the Start page granted Create permission to WebAdmins, the MCP client was still denied access.&lt;/p&gt;
&lt;p&gt;The issue was resolved by explicitly granting the &lt;strong&gt;MCP Client&lt;/strong&gt; permissions on the Start page through CMS access rights. Afterward, Codex could create and update content.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;img style="width: 100%; max-width: 750px; height: auto;" src="/link/28ac25ea3076497792a6660ed63434cc.aspx" alt="MCP Client permissions on the Start page" /&gt;&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;em&gt;Granting the MCP Client access on the Start page resolved the create denial.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Security note:&lt;/strong&gt; For this local demo, all permissions were enabled, including Publish and Administer. In a real environment, grant only the permissions needed for the intended operations.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;With Codex connected to Optimizely CMS 13 through MCP, we were able to explore content types, create pages, and work with Visual Builder directly from VS Code.&lt;/p&gt;
&lt;p&gt;One thing worth noting is that connecting successfully doesn't necessarily mean the MCP client has permission to create or update content. You'll still need to configure the appropriate access rights in CMS.&lt;/p&gt;</summary></entry><entry><title>Handling SixLabors.ImageSharp vulnerability warnings in Optimizely CMS 12 (unofficial)</title><link href="https://world.optimizely.com/blogs/cuong-nguyen-inh/dates/2026/10/handling-sixlabors.imagesharp-vulnerability-warnings-in-optimizely-cms-12-unofficial" /><id>https://world.optimizely.com/blogs/cuong-nguyen-inh/dates/2026/10/handling-sixlabors.imagesharp-vulnerability-warnings-in-optimizely-cms-12-unofficial</id><published>2026-10-10T06:01:41.0000000Z</published><updated>2026-10-10T06:01:41.0000000Z</updated><author><name>cuong.nguyendinh@optimizely.com</name></author><category term="Blog post" /><summary type="html">&lt;p&gt;&lt;strong&gt;UPDATED: Optimizely is officially working with SixLabors to find a solution, if possible; the work ID is CMS-57231. An official decision/announcement might come soon.&lt;/strong&gt;&lt;em&gt;&lt;br /&gt;&lt;br /&gt;Written by Claude Opus 5.5 (Anthropic) on behalf of Cuong Nguyen, Optimizely Technical Support.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A note from Cuong:&lt;/strong&gt; Thanks to Optimizely for giving me access to Claude, which made this kind of deep dive possible within a support ticket.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SixLabors.ImageSharp&lt;/strong&gt; is one of the most popular image libraries in the .NET world. Chances are it's in your solution too, even if you never added it yourself. Then one morning you open the project, run a build like any other day, and the output is suddenly full of &lt;strong&gt;NU1902&lt;/strong&gt;/&lt;strong&gt;NU1903&lt;/strong&gt; warnings. Nothing changed in your code. If &lt;strong&gt;TreatWarningsAsErrors&lt;/strong&gt; is on, the build simply fails.&lt;/p&gt;
&lt;p&gt;That's the story behind a lot of tickets we've been getting in Optimizely Support lately. This post covers where the dependency comes from, why "just upgrade ImageSharp" doesn't work, and the four options you have today, including a SkiaSharp-based &lt;strong&gt;IImageService&lt;/strong&gt; sample.&lt;/p&gt;
&lt;h2&gt;1. What you'll see&lt;/h2&gt;
&lt;pre class="language-text"&gt;&lt;code&gt;$ dotnet list package --include-transitive --vulnerable

   Transitive Package          Resolved   Severity   Advisory URL
   &amp;gt; SixLabors.ImageSharp      3.1.11     Moderate   https://github.com/advisories/GHSA-gwg2-r3hj-4w44
                                          High       https://github.com/advisories/GHSA-j3p4-wp97-rph4
                                          High       https://github.com/advisories/GHSA-j9gm-c75j-xc9q
                                          Moderate   https://github.com/advisories/GHSA-wmxv-xphr-5c9g
                                          High       https://github.com/advisories/GHSA-jjfr-hcj7-qf5w
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'll also get &lt;strong&gt;NU1902&lt;/strong&gt;/&lt;strong&gt;NU1903&lt;/strong&gt; during restore and build. With &lt;strong&gt;TreatWarningsAsErrors&lt;/strong&gt; enabled, the build fails.&lt;/p&gt;
&lt;h2&gt;2. Where the dependency comes from&lt;/h2&gt;
&lt;p&gt;The CMS uses &lt;strong&gt;IImageService&lt;/strong&gt; for thumbnails and the image editor. The default implementation lives in &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt;, which depends on &lt;strong&gt;SixLabors.ImageSharp&lt;/strong&gt;:&lt;/p&gt;
&lt;pre class="language-text"&gt;&lt;code&gt;EPiServer.CMS 12.34.x
└── EPiServer.ImageLibrary.ImageSharp  (&amp;gt;= 1.0.6)   &amp;larr; NuGet picks the lowest matching version
    └── SixLabors.ImageSharp 2.1.11
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NuGet resolves dependencies with the &lt;em&gt;lowest applicable version&lt;/em&gt; rule (&lt;a href="https://learn.microsoft.com/nuget/concepts/dependency-resolution#lowest-applicable-version"&gt;docs&lt;/a&gt;). So &lt;strong&gt;EPiServer.CMS&lt;/strong&gt; 12.34.8 resolves &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt; &lt;strong&gt;1.0.6&lt;/strong&gt;, which brings in ImageSharp &lt;strong&gt;2.1.11&lt;/strong&gt;. This is standard NuGet behavior, not something specific to Optimizely packages.&lt;/p&gt;
&lt;p&gt;To get a newer version, reference the CMS components directly instead of the &lt;strong&gt;EPiServer.CMS&lt;/strong&gt; metapackage, and pin &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt; to 2.0.6:&lt;/p&gt;
&lt;pre class="language-xml"&gt;&lt;code&gt;&amp;lt;PackageReference Include="EPiServer.CMS.UI" Version="12.34.6" /&amp;gt;
&amp;lt;PackageReference Include="EPiServer.Hosting" Version="12.24.1" /&amp;gt;
&amp;lt;!-- ... --&amp;gt;
&amp;lt;PackageReference Include="EPiServer.ImageLibrary.ImageSharp" Version="2.0.6" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That moves you to ImageSharp 3.1.11, which clears the older advisories but not the five listed above.&lt;/p&gt;
&lt;h2&gt;3. Why not force a newer ImageSharp?&lt;/h2&gt;
&lt;p&gt;The advisories above are fixed in ImageSharp 4.x. Six Labors changed the licensing model in 4.x, and Release builds require a license key (&lt;a href="https://github.com/SixLabors/ImageSharp/issues/3205#issuecomment-6062121101"&gt;discussion&lt;/a&gt;). Forcing 4.x through &lt;strong&gt;Directory.Packages.props&lt;/strong&gt; therefore gives you a build error unless you have a Six Labors license. The 3.1.x line, the last one usable without a license, still carries the advisories.&lt;/p&gt;
&lt;h2&gt;4. Your options&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Remove &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No code, warnings disappear&lt;/td&gt;
&lt;td&gt;No image processing: thumbnails and the image editor stop working&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Provide your own &lt;a href="/CsClassLibraries/cms/EPiServer.ImageLibrary.IImageService?version=12"&gt;&lt;strong&gt;IImageService&lt;/strong&gt;&lt;/a&gt;, e.g. with &lt;a href="https://github.com/mono/SkiaSharp"&gt;SkiaSharp&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Clean audit, features kept&lt;/td&gt;
&lt;td&gt;Custom code that your team owns and maintains&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Suppress the audit warnings&lt;/td&gt;
&lt;td&gt;Quick&lt;/td&gt;
&lt;td&gt;The vulnerable package stays in your solution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Wait for an updated &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No custom code&lt;/td&gt;
&lt;td&gt;No committed timeline today&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;On risk:&lt;/strong&gt; the image service only processes media that authenticated editors upload through the CMS. It doesn't handle arbitrary anonymous input. That lowers the exposure, and editors validating images on upload lowers it further. Whether that's acceptable is a decision for your own security policy and threat model.&lt;/p&gt;
&lt;h2&gt;5. Option 2: a custom &lt;strong&gt;IImageService&lt;/strong&gt; with SkiaSharp&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;IImageService&lt;/strong&gt; is a public extension point with a single method:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;public interface IImageService
{
    void RenderImage(Stream input, Stream output, RenderRequest request);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The default implementation maps each &lt;strong&gt;ImageEditorCommand&lt;/strong&gt; like this, and a replacement should do the same:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;ImageEditorCommand&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;Default (ImageSharp)&lt;/th&gt;
&lt;th&gt;SkiaSharp equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grayscale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Grayscale()&lt;/strong&gt; (BT.709)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;SKColorFilter.CreateColorMatrix&lt;/strong&gt; with BT.709 coefficients&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Crop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Crop(rect)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;DrawBitmap(src, -left, -top)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;FlipX&lt;/strong&gt; / &lt;strong&gt;FlipY&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Flip(Horizontal/Vertical)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;canvas.Scale(-1, 1, cx, 0)&lt;/strong&gt; / &lt;strong&gt;Scale(1, -1, 0, cy)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resize&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ResizeMode.Stretch&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;DrawBitmap(src, destRect, Mitchell)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ResizeKeepScale&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;ResizeMode.BoxPad&lt;/strong&gt; + &lt;strong&gt;BackgroundColor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;fit the box, &lt;strong&gt;no upscaling&lt;/strong&gt;, center, &lt;strong&gt;Clear(bg)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rotate&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Rotate(deg)&lt;/strong&gt;, canvas expands&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Translate&lt;/strong&gt; + &lt;strong&gt;RotateDegrees&lt;/strong&gt; on an expanded canvas&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Packages&lt;/h3&gt;
&lt;p&gt;Remove &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt; and add SkiaSharp. If you reference the &lt;strong&gt;EPiServer.CMS&lt;/strong&gt; metapackage, switch to individual component references first (see section 2). The metapackage brings &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt; in transitively, so you can't remove it otherwise.&lt;/p&gt;
&lt;pre class="language-xml"&gt;&lt;code&gt;&amp;lt;!-- remove: &amp;lt;PackageReference Include="EPiServer.ImageLibrary.ImageSharp" Version="..." /&amp;gt; --&amp;gt;
&amp;lt;PackageReference Include="SkiaSharp" Version="4.153.1" /&amp;gt;
&amp;lt;!-- Required on Linux, containers and DXP: --&amp;gt;
&amp;lt;PackageReference Include="SkiaSharp.NativeAssets.Linux.NoDependencies" Version="4.153.1" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The service&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;ResizeKeepScale&lt;/strong&gt; matters most, because thumbnail generation calls it with &lt;strong&gt;BackgroundColor = "#00000000"&lt;/strong&gt;. The full implementation:&lt;/p&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;using EPiServer.ImageLibrary;
using SkiaSharp;

namespace YourSite.Business.Imaging;

/// &amp;lt;summary&amp;gt;
/// SkiaSharp-based replacement for EPiServer.ImageLibrary.ImageSharp's DefaultImageService,
/// which pulls in vulnerable SixLabors.ImageSharp versions. Mirrors the default behavior:
/// same commands, ResizeKeepScale = ImageSharp "BoxPad" + background color, ZoomFactor applied last.
/// &amp;lt;/summary&amp;gt;
/// &amp;lt;remarks&amp;gt;
/// Skia only encodes PNG, JPEG and WebP. GIF/BMP/TIFF output is not supported (NotSupportedException),
/// and animated GIF input is flattened to its first frame.
/// &amp;lt;/remarks&amp;gt;
public class SkiaImageService : IImageService
{
    private static readonly SKSamplingOptions ResizeSampling = new(SKCubicResampler.Mitchell);
    private static readonly SKSamplingOptions RotateSampling = new(SKFilterMode.Linear);
    private static readonly SKSamplingOptions CopySampling = new(SKFilterMode.Nearest);

    // ITU-R BT.709 luma, same coefficients as ImageSharp's Grayscale().
    private static readonly float[] GrayscaleMatrix =
    [
        0.2126f, 0.7152f, 0.0722f, 0, 0,
        0.2126f, 0.7152f, 0.0722f, 0, 0,
        0.2126f, 0.7152f, 0.0722f, 0, 0,
        0, 0, 0, 1, 0,
    ];

    public void RenderImage(Stream input, Stream output, RenderRequest request)
    {
        ArgumentNullException.ThrowIfNull(input);
        ArgumentNullException.ThrowIfNull(output);
        ArgumentNullException.ThrowIfNull(request);

        var image = Decode(input);
        try
        {
            foreach (var operation in request.Operations ?? [])
            {
                image = Replace(image, Apply(image, operation));
            }

            if (request.ZoomFactor != 1f)
            {
                var width = (int)Math.Floor(image.Width * request.ZoomFactor);
                var height = (int)Math.Floor(image.Height * request.ZoomFactor);
                image = Replace(image, Resize(image, width, height));
            }

            Encode(image, output, request);
        }
        finally
        {
            image.Dispose();
        }
    }

    private static SKBitmap Decode(Stream input)
    {
        using var codec = SKCodec.Create(input)
            ?? throw new NotSupportedException("The source image format is not supported.");

        var info = codec.Info
            .WithColorType(SKImageInfo.PlatformColorType)
            .WithAlphaType(SKAlphaType.Premul);

        return SKBitmap.Decode(codec, info)
            ?? throw new NotSupportedException("The source image could not be decoded.");
    }

    private static SKBitmap Replace(SKBitmap current, SKBitmap next)
    {
        current.Dispose();
        return next;
    }

    private static SKBitmap Apply(SKBitmap source, ImageOperation operation) =&amp;gt; operation.Command switch
    {
        ImageEditorCommand.Grayscale =&amp;gt; Grayscale(source),
        ImageEditorCommand.Crop =&amp;gt; Crop(source, operation),
        ImageEditorCommand.FlipX =&amp;gt; Draw(source.Width, source.Height, canvas =&amp;gt;
        {
            canvas.Scale(-1, 1, source.Width / 2f, 0);
            canvas.DrawBitmap(source, 0, 0, CopySampling);
        }),
        ImageEditorCommand.FlipY =&amp;gt; Draw(source.Width, source.Height, canvas =&amp;gt;
        {
            canvas.Scale(1, -1, 0, source.Height / 2f);
            canvas.DrawBitmap(source, 0, 0, CopySampling);
        }),
        ImageEditorCommand.Resize =&amp;gt; Resize(source, operation.Width, operation.Height),
        ImageEditorCommand.ResizeKeepScale =&amp;gt; ResizeKeepScale(source, operation),
        ImageEditorCommand.Rotate =&amp;gt; Rotate(source, operation.Angle),
        _ =&amp;gt; throw new InvalidOperationException($"Unknown command {operation.Command}"),
    };

    private static SKBitmap Draw(int width, int height, Action&amp;lt;SKCanvas&amp;gt; draw)
    {
        var bitmap = new SKBitmap(width, height, SKImageInfo.PlatformColorType, SKAlphaType.Premul);
        using var canvas = new SKCanvas(bitmap);
        canvas.Clear(SKColors.Transparent);
        draw(canvas);
        return bitmap;
    }

    private static SKBitmap Grayscale(SKBitmap source)
    {
        using var filter = SKColorFilter.CreateColorMatrix(GrayscaleMatrix);
        using var paint = new SKPaint { ColorFilter = filter };
        return Draw(source.Width, source.Height, canvas =&amp;gt; canvas.DrawBitmap(source, 0, 0, CopySampling, paint));
    }

    private static SKBitmap Crop(SKBitmap source, ImageOperation operation)
    {
        var area = SKRectI.Create(operation.Left, operation.Top, operation.Width, operation.Height);
        if (area.IsEmpty || !SKRectI.Create(source.Width, source.Height).Contains(area))
        {
            throw new ArgumentOutOfRangeException(nameof(operation), $"Crop area {area} is outside the image bounds.");
        }

        return Draw(area.Width, area.Height, canvas =&amp;gt; canvas.DrawBitmap(source, -area.Left, -area.Top, CopySampling));
    }

    private static SKBitmap Resize(SKBitmap source, int width, int height)
    {
        var size = TargetSize(source, width, height);
        return Draw(size.Width, size.Height, canvas =&amp;gt;
            canvas.DrawBitmap(source, SKRect.Create(size.Width, size.Height), ResizeSampling));
    }

    /// &amp;lt;summary&amp;gt;ImageSharp BoxPad: fit inside the box without upscaling, center, fill the rest with the background color.&amp;lt;/summary&amp;gt;
    private static SKBitmap ResizeKeepScale(SKBitmap source, ImageOperation operation)
    {
        var box = TargetSize(source, operation.Width, operation.Height);
        var scale = Math.Min(1f, Math.Min(box.Width / (float)source.Width, box.Height / (float)source.Height));
        var width = Math.Max(1, (int)MathF.Round(source.Width * scale));
        var height = Math.Max(1, (int)MathF.Round(source.Height * scale));
        var destination = SKRect.Create((box.Width - width) / 2, (box.Height - height) / 2, width, height);
        var background = ParseColor(operation.BackgroundColor);

        return Draw(box.Width, box.Height, canvas =&amp;gt;
        {
            canvas.Clear(background);
            canvas.DrawBitmap(source, destination, ResizeSampling);
        });
    }

    private static SKBitmap Rotate(SKBitmap source, double degrees)
    {
        var radians = degrees * Math.PI / 180;
        var cos = Math.Abs(Math.Cos(radians));
        var sin = Math.Abs(Math.Sin(radians));
        // Round before Ceiling so right angles don't grow by a pixel from floating point noise (50.0000001 -&amp;gt; 51).
        var width = (int)Math.Ceiling(Math.Round(source.Width * cos + source.Height * sin, 3));
        var height = (int)Math.Ceiling(Math.Round(source.Width * sin + source.Height * cos, 3));

        return Draw(width, height, canvas =&amp;gt;
        {
            canvas.Translate(width / 2f, height / 2f);
            canvas.RotateDegrees((float)degrees);
            canvas.DrawBitmap(source, -source.Width / 2f, -source.Height / 2f, RotateSampling);
        });
    }

    /// &amp;lt;summary&amp;gt;Same as ImageSharp: a 0 dimension is derived from the other one, keeping the aspect ratio.&amp;lt;/summary&amp;gt;
    private static SKSizeI TargetSize(SKBitmap source, int width, int height)
    {
        if (width == 0 &amp;amp;&amp;amp; height &amp;gt; 0)
        {
            width = Math.Max(1, (int)MathF.Round(source.Width * height / (float)source.Height));
        }

        if (height == 0 &amp;amp;&amp;amp; width &amp;gt; 0)
        {
            height = Math.Max(1, (int)MathF.Round(source.Height * width / (float)source.Width));
        }

        if (width &amp;lt;= 0 || height &amp;lt;= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(width), $"Invalid target size {width}x{height}.");
        }

        return new SKSizeI(width, height);
    }

    /// &amp;lt;summary&amp;gt;Parses ImageSharp-style hex colors (#RGB, #RGBA, #RRGGBB, #RRGGBBAA). Empty means white.&amp;lt;/summary&amp;gt;
    private static SKColor ParseColor(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return SKColors.White;
        }

        var hex = value.TrimStart('#');
        // Skia expects the alpha first (#ARGB / #AARRGGBB).
        var argb = hex.Length switch
        {
            4 =&amp;gt; hex[3..] + hex[..3],
            8 =&amp;gt; hex[6..] + hex[..6],
            _ =&amp;gt; hex,
        };

        return SKColor.TryParse(argb, out var color)
            ? color
            : throw new ArgumentException($"Invalid background color '{value}'.", nameof(value));
    }

    private static void Encode(SKBitmap image, Stream output, RenderRequest request)
    {
        using var pixmap = image.PeekPixels();
        var quality = Math.Clamp(request.Quality, 0, 100);

        var encoded = request.MimeType switch
        {
            "image/png" or "image/x-icon" =&amp;gt;
                pixmap.Encode(output, new SKPngEncoderOptions(SKPngEncoderFilterFlags.AllFilters, PngZLibLevel(request.Quality))),
            "image/jpg" or "image/jpe" or "image/jpeg" or "image/pjpeg" =&amp;gt;
                pixmap.Encode(output, new SKJpegEncoderOptions(quality)),
            "image/webp" =&amp;gt;
                pixmap.Encode(output, new SKWebpEncoderOptions(SKWebpEncoderCompression.Lossy, quality)),
            _ =&amp;gt; throw new NotSupportedException($"Not supported image format [{request.MimeType}] when creating images."),
        };

        if (!encoded)
        {
            throw new InvalidOperationException($"Failed to encode image as [{request.MimeType}].");
        }
    }

    /// &amp;lt;summary&amp;gt;Same quality -&amp;gt; compression mapping as the default service: 100 = level 0 ... &amp;amp;lt;20 = level 9.&amp;lt;/summary&amp;gt;
    private static int PngZLibLevel(int quality) =&amp;gt; quality &amp;lt; 0 ? 6 : Math.Clamp(10 - quality / 10, 0, 9);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Two details that are easy to miss:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Channel order&lt;/strong&gt; (&lt;strong&gt;ParseColor&lt;/strong&gt;). ImageSharp parses &lt;strong&gt;#RRGGBBAA&lt;/strong&gt;, Skia parses &lt;strong&gt;#AARRGGBB&lt;/strong&gt;. Rotate the hex string before parsing, or &lt;strong&gt;#00FF0080&lt;/strong&gt; comes out as a different color.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;90&amp;deg; rotation&lt;/strong&gt; (&lt;strong&gt;Rotate&lt;/strong&gt;). &lt;strong&gt;cos(90&amp;deg;)&lt;/strong&gt; evaluates to &lt;strong&gt;6e-17&lt;/strong&gt;, not 0, so &lt;strong&gt;Math.Ceiling(50.00000000000001)&lt;/strong&gt; gives 51 and the output gains an extra pixel. Round before taking the ceiling.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Registration&lt;/h3&gt;
&lt;pre class="language-csharp"&gt;&lt;code&gt;using EPiServer.ImageLibrary;
using YourSite.Business.Imaging;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddCmsAspNetIdentity&amp;lt;ApplicationUser&amp;gt;()
            .AddCms();

        // Replaces EPiServer.ImageLibrary.ImageSharp for thumbnails and the CMS image editor.
        services.AddSingleton&amp;lt;IImageService, SkiaImageService&amp;gt;();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The CMS only registers a fallback &lt;strong&gt;IImageService&lt;/strong&gt; when none exists (&lt;strong&gt;TryAddSingleton&lt;/strong&gt;), so your implementation takes precedence. Without any registration, that fallback throws &lt;strong&gt;InvalidOperationException&lt;/strong&gt; the first time it's resolved.&lt;/p&gt;
&lt;pre class="language-text"&gt;&lt;code&gt;$ dotnet list package --include-transitive --vulnerable
The given project `Alloy` has no vulnerable packages given the current sources.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Known limitations&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Skia encodes PNG, JPEG and WebP only. GIF, BMP or TIFF output throws &lt;strong&gt;NotSupportedException&lt;/strong&gt;, so the image editor won't work for those formats. Thumbnails aren't affected, because the CMS always stores them as &lt;strong&gt;.png&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Skia can't decode TIFF, so TIFF uploads get no thumbnail. The upload itself still succeeds, because the CMS handles thumbnail errors.&lt;/li&gt;
&lt;li&gt;Animated GIFs are flattened to the first frame.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; this sample is not an officially supported Optimizely package. It's provided as guidance, and you own it once it's in your solution. Test it against your own media library before you deploy.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;6. Option 3: suppress the warnings&lt;/h2&gt;
&lt;pre class="language-xml"&gt;&lt;code&gt;&amp;lt;ItemGroup&amp;gt;
  &amp;lt;NuGetAuditSuppress Include="https://github.com/advisories/GHSA-gwg2-r3hj-4w44" /&amp;gt;
  &amp;lt;NuGetAuditSuppress Include="https://github.com/advisories/GHSA-j3p4-wp97-rph4" /&amp;gt;
  &amp;lt;NuGetAuditSuppress Include="https://github.com/advisories/GHSA-j9gm-c75j-xc9q" /&amp;gt;
  &amp;lt;NuGetAuditSuppress Include="https://github.com/advisories/GHSA-wmxv-xphr-5c9g" /&amp;gt;
  &amp;lt;NuGetAuditSuppress Include="https://github.com/advisories/GHSA-jjfr-hcj7-qf5w" /&amp;gt;
&amp;lt;/ItemGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Suppressing only hides the warning, the vulnerability is still there. Record the decision (reason, owner, review date) wherever your team tracks accepted risks.&lt;/p&gt;
&lt;h2&gt;7. Option 4: wait for an updated package&lt;/h2&gt;
&lt;p&gt;An updated &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt; would remove the problem at the source. We can't share a timeline for that yet. If you go this route:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Watch the Optimizely release notes for a new &lt;strong&gt;EPiServer.ImageLibrary.ImageSharp&lt;/strong&gt; version, or check from the command line:&lt;/p&gt;
&lt;pre class="language-bash"&gt;&lt;code&gt;dotnet list package --outdated --include-transitive | grep -i imagelibrary
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Combine it with option 3 in the meantime, and add a review date so the suppression gets removed once the update ships:&lt;/p&gt;
&lt;pre class="language-xml"&gt;&lt;code&gt;&amp;lt;!-- TEMP: waiting for an EPiServer.ImageLibrary.ImageSharp update without the vulnerable ImageSharp.
     Review by: 2026-12-31. Owner: @team-cms --&amp;gt;
&amp;lt;NuGetAuditSuppress Include="https://github.com/advisories/GHSA-j3p4-wp97-rph4" /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you're on option 2, moving to the official package later means deleting your &lt;strong&gt;IImageService&lt;/strong&gt; implementation and its registration.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SixLabors/ImageSharp, 4.x license discussion: &lt;a href="https://github.com/SixLabors/ImageSharp/issues/3205#issuecomment-6062121101"&gt;https://github.com/SixLabors/ImageSharp/issues/3205#issuecomment-6062121101&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Optimizely &lt;strong&gt;IImageService&lt;/strong&gt; API: &lt;a href="/CsClassLibraries/cms/EPiServer.ImageLibrary.IImageService?version=12"&gt;https://world.optimizely.com/CsClassLibraries/cms/EPiServer.ImageLibrary.IImageService?version=12&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;SkiaSharp: &lt;a href="https://github.com/mono/SkiaSharp"&gt;https://github.com/mono/SkiaSharp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NuGet dependency resolution: &lt;a href="https://learn.microsoft.com/nuget/concepts/dependency-resolution"&gt;https://learn.microsoft.com/nuget/concepts/dependency-resolution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;NuGet Audit: &lt;a href="https://learn.microsoft.com/nuget/concepts/auditing-packages"&gt;https://learn.microsoft.com/nuget/concepts/auditing-packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Advisories: &lt;a href="https://github.com/advisories/GHSA-gwg2-r3hj-4w44"&gt;GHSA-gwg2-r3hj-4w44&lt;/a&gt;, &lt;a href="https://github.com/advisories/GHSA-j3p4-wp97-rph4"&gt;GHSA-j3p4-wp97-rph4&lt;/a&gt;, &lt;a href="https://github.com/advisories/GHSA-j9gm-c75j-xc9q"&gt;GHSA-j9gm-c75j-xc9q&lt;/a&gt;, &lt;a href="https://github.com/advisories/GHSA-wmxv-xphr-5c9g"&gt;GHSA-wmxv-xphr-5c9g&lt;/a&gt;, &lt;a href="https://github.com/advisories/GHSA-jjfr-hcj7-qf5w"&gt;GHSA-jjfr-hcj7-qf5w&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</summary></entry><entry><title>Optimizely CMS 12 on .NET 10: Fixing a Siteimprove TypeMismatchException</title><link href="https://madhuanbalagan.com/?p=5093" /><id>https://madhuanbalagan.com/?p=5093</id><published>2026-10-09T21:58:00.0000000Z</published><updated>2026-10-12T02:30:54.0000000Z</updated><author><name>madhumidha.a@infosys.com</name></author><category term="Blog post" /><summary type="html">&lt;p&gt;Recently, while upgrading an Optimizely CMS 12 solution from .NET 8 to .NET 10, we encountered a frustrating exception that initially looked like a CMS&amp;#46;&amp;#46;&amp;#46;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://madhuanbalagan.com/optimizely-cms-12-on-net-10-fixing-a-siteimprove-typemismatchexception"&gt;Optimizely CMS 12 on .NET 10: Fixing a Siteimprove TypeMismatchException&lt;/a&gt; appeared first on &lt;a href="https://madhuanbalagan.com"&gt;Madhu Anbalagan&amp;#039;s Blog&lt;/a&gt;.&lt;/p&gt;
</summary></entry><entry><title>What made Scheduled Jobs faster in Optimizely CMS 13.3.0</title><link href="https://www.gulla.net/en/blog/what-made-scheduled-jobs-faster-in-optimizely-cms-13.3.0/" /><id>https://www.gulla.net/en/blog/what-made-scheduled-jobs-faster-in-optimizely-cms-13.3.0/</id><published>2026-10-09T06:00:00.0000000Z</published><updated>2026-10-09T07:30:11.0000000Z</updated><author><name>tomas.h.gulla@novacare.no</name></author><category term="Blog post" /><summary type="html">A look under the hood at the update that made scheduled jobs faster.</summary></entry><entry><title>New DDS explorer addon for Optimizely CMS</title><link href="https://www.gulla.net/en/blog/new-dds-explorer-addon-for-optimizely-cms/" /><id>https://www.gulla.net/en/blog/new-dds-explorer-addon-for-optimizely-cms/</id><published>2026-10-08T18:27:59.0000000Z</published><updated>2026-10-08T19:30:22.0000000Z</updated><author><name>tomas.h.gulla@novacare.no</name></author><category term="Blog post" /><summary type="html">I wanted to be able to manage Dynamic Data Store from within Optimizely CMS, so I created another addon.</summary></entry><entry><title>Queryable or Searchable? How indexing type changes your Optimizely Graph query</title><link href="http://optimizelyfoundation.com/?p=694" /><id>http://optimizelyfoundation.com/?p=694</id><published>2026-10-08T13:06:39.0000000Z</published><updated>2026-10-08T13:30:45.0000000Z</updated><author><name>ckhanna@horizontal.com</name></author><category term="Blog post" /><summary type="html">The Property Indexing Type on a CMS field decides whether Optimizely Graph will return it, filter it, or run full-text search on it. Default, Queryable, Searchable, and Disabled each change the GraphQL query.</summary></entry><entry><title>How to Build Optimizely CMS 13 with Visual Builder in .NET</title><link href="https://www.technicaldogsbody.com/blog/how-to-build-optimizely-cms-13-with-visual-builder-in-net" /><id>https://www.technicaldogsbody.com/blog/how-to-build-optimizely-cms-13-with-visual-builder-in-net</id><published>2026-10-08T11:30:15.7270000Z</published><updated>2026-10-08T11:30:15.7270000Z</updated><author><name>andy.blyth@msqdx.com</name></author><category term="Blog post" /><summary type="html">A code-first guide for .NET developers moving from Optimizely CMS 12 to CMS 13, covering Visual Builder: ExperienceData, Sections, Elements, tag helpers and styles.</summary></entry><entry><title>Supercharging Optimizely DXP Troubleshooting with the Azure Copilot Observability Agent</title><link href="https://world.optimizely.com/blogs/scott-reed/dates/2026/10/supercharging-optimizely-dxp-troubleshooting-with-the-azure-copilot-observability-agent/" /><id>https://world.optimizely.com/blogs/scott-reed/dates/2026/10/supercharging-optimizely-dxp-troubleshooting-with-the-azure-copilot-observability-agent/</id><published>2026-10-08T09:10:07.0000000Z</published><updated>2026-10-08T09:10:07.0000000Z</updated><author><name>scott.reed@niteco.com</name></author><category term="Blog post" /><summary type="html">&lt;p&gt;If you&amp;rsquo;ve logged into the Azure portal recently to check the Application Insights for your Optimizely DXP environment, you might have noticed a new toggle in the Logs UI labelled simply:&amp;nbsp;&lt;strong&gt;"Agent."&lt;/strong&gt; And as you create a new query, being prompted with help from the Observability Agent&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;img src="/link/e079e0af11884b5991b56be2b21969dc.aspx" width="698" height="326" /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Application Insights gives us many very useful tools, including recent updates such as Grafana dashboards that can help us view key information about our application. We technical specialists in the Optimizely DXP stack should be familiar with working within the investigate, monitoring, and usage areas.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;However, one key pain point has been working with and remembering Kusto Query Language (KQL) but with recent updates in Application Insights and this default enabled on the Optimise DXP, this can now be a lot easier.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Instead of writing complex Kusto Query Language (KQL) to figure out why your modern Optimizely Commerce 15 checkout is dragging, or why your CMS 12 editors are experiencing slow load times, you can now ask plain-English questions.&lt;/p&gt;
&lt;p&gt;In this post, we&amp;rsquo;ll explore how to leverage this AI agent specifically for web apps running on Optimizely DXP, focusing on the exact types of telemetry, anomalies, and performance metrics that matter most for modern CMS 12/13 and Commerce 14/15 solutions.&lt;/p&gt;
&lt;h2&gt;The Modern Optimizely DXP Telemetry Landscape&lt;/h2&gt;
&lt;p&gt;Before we talk to the AI, we need to understand the data. Optimizely DXP environments (Integration, Preproduction, Production) automatically wire up Application Insights. For applications running on modern .NET (like CMS 12/13 and Commerce 14/15), this captures:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Requests:&lt;/strong&gt; Server-side ASP.NET Core page loads, Content Delivery API calls, and Optimizely Graph queries.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependencies:&lt;/strong&gt; SQL Database queries, external API calls (payment gateways, ERPs), and internal Optimizely services.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exceptions:&lt;/strong&gt; Server-side errors and handled exceptions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom Events:&lt;/strong&gt; Business logic milestones captured via &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;TelemetryClient&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Historically, correlating a spike in &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;Exceptions&lt;/span&gt; with a slowdown in &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;Dependencies&lt;/span&gt; required mastery of KQL &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;join&lt;/span&gt; statements. Now, the Observability Agent handles the translation. Let's look at how this plays out across the platform.&lt;/p&gt;
&lt;h2&gt;Scenario 1: Debugging Commerce 14/15 Checkout and Asynchronous Operations&lt;/h2&gt;
&lt;p&gt;Optimizely Commerce 14 and 15 fully embrace asynchronous programming. A modern checkout process involves validating a serialised cart, recalculating promotions, calling an external tax API, communicating with a payment provider, and finally using &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;SaveAsPurchaseOrderAsync()&lt;/span&gt; to convert the cart.&lt;/p&gt;
&lt;p&gt;If conversion rates drop, the culprit is often a silent performance degradation in one of these external or asynchronous steps.&lt;/p&gt;
&lt;h3&gt;The Old Way (KQL)&lt;/h3&gt;
&lt;p&gt;To find the average duration of the order saving operation over the last 24 hours, you&amp;rsquo;d have to write:&lt;/p&gt;
&lt;pre style="background: #f4f6f8; color: #1a1a1a; border: 1px solid #d0d7de; border-radius: 4px; padding: 12px; overflow-x: auto; font-family: Consolas,Menlo,monospace; font-size: 14px; white-space: pre;"&gt;dependencies
| where name contains "SaveAsPurchaseOrder" or name contains "IOrderRepository.SaveAsync"
| where timestamp &amp;gt; ago(24h)
| summarise AvgDuration = avg(duration), Count = count() by bin(timestamp, 1h)
| render timechart
&lt;/pre&gt;
&lt;h3&gt;The Copilot Way&lt;/h3&gt;
&lt;p&gt;Toggle the "Agent" switch to &lt;strong&gt;On&lt;/strong&gt; and simply type:&lt;/p&gt;
&lt;blockquote style="border-left: 4px solid #0b6e4f; margin: 16px 0; padding: 8px 16px; background: #f7faf9;"&gt;
&lt;p&gt;&lt;em&gt;"Show me a timechart of the average duration for dependency calls related to IOrderRepository.SaveAsync over the last 24 hours, grouped by hour."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;What to Look For in Commerce 14/15:&lt;/h3&gt;
&lt;p&gt;When using the agent, focus your queries on the known bottlenecks of a modern e-commerce platform:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Cart Serialisation Latency:&lt;/strong&gt; Commerce 14/15 stores active carts as JSON in the &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;SerializableCart&lt;/span&gt; table. Ask the agent: &lt;em&gt;"Are there any SQL dependencies related to the SerializableCart table taking longer than 500ms today compared to yesterday?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Third-Party Payment Delays:&lt;/strong&gt; &lt;em&gt;"Show me the failure rate and average response time for external HTTP dependencies calling 'api.stripe.com' (or your payment provider) over the last 7 days."&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Promotion Engine Overhead:&lt;/strong&gt; &lt;em&gt;"Did the average request duration of the /checkout API endpoint increase after our deployment at 2 PM today?"&lt;/em&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can also ask the agent to find root causes: &lt;em&gt;"Why did checkout requests fail more frequently between 2 PM and 4 PM today?"&lt;/em&gt; The Copilot will analyse exceptions and dependency failures during that window and summarise the findings.&lt;/p&gt;
&lt;h2&gt;Scenario 2: Diagnosing CMS 12/13 Rendering and Headless Delivery&lt;/h2&gt;
&lt;p&gt;With CMS 12 and 13 running on ASP.NET Core, performance is incredibly fast out of the box. However, bottlenecks usually occur around custom block rendering, complex tag helpers, or heavy utilisation of Optimizely Graph and Content Delivery APIs in headless architectures.&lt;/p&gt;
&lt;h3&gt;Uncovering Headless and Search Bottlenecks&lt;/h3&gt;
&lt;p&gt;If your frontend Next.js application relies heavily on Optimizely Graph or Search &amp;amp; Navigation, a poorly optimised GraphQL query or &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;.Filter()&lt;/span&gt; statement will cause cascading slowdowns.&lt;/p&gt;
&lt;p&gt;Try prompting the Observability Agent with:&lt;/p&gt;
&lt;blockquote style="border-left: 4px solid #0b6e4f; margin: 16px 0; padding: 8px 16px; background: #f7faf9;"&gt;
&lt;p&gt;&lt;em&gt;"Identify the top 5 slowest outgoing HTTP requests to our Optimizely Graph or Search endpoints over the last 3 days. What CMS pages or API routes were users requesting when these slow calls happened?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The agent will automatically correlate the &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;dependencies&lt;/span&gt; table with the &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;requests&lt;/span&gt; table, saving you the hassle of writing complex cross-table joins.&lt;/p&gt;
&lt;h3&gt;Investigating ASP.NET Core Memory and Caching&lt;/h3&gt;
&lt;p&gt;CMS 12/13 utilises heavily optimised memory caching in .NET Core. If the cache is invalidated too frequently, you'll see a spike in SQL queries to the core CMS database.&lt;/p&gt;
&lt;blockquote style="border-left: 4px solid #0b6e4f; margin: 16px 0; padding: 8px 16px; background: #f7faf9;"&gt;
&lt;p&gt;&lt;em&gt;"Has there been an unexpected spike in dependency calls to the core CMS SQL database in the last 12 hours compared to the previous week? If so, what are the most common SQL commands being executed?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If an editor publishes a massive content change that clears a large portion of the cache, the Copilot can help you correlate that spike in database traffic directly to the time the editor hit "Publish."&lt;/p&gt;
&lt;h2&gt;Scenario 3: Post-Deployment Verification on DXP&lt;/h2&gt;
&lt;p&gt;Deploying modern .NET applications to Optimizely DXP involves swapping slots in Production. The moments immediately following a slot swap are critical. Instead of staring at a raw stream of logs, you can use the Copilot as an active monitor.&lt;/p&gt;
&lt;h3&gt;The Post-Deployment Checkup&lt;/h3&gt;
&lt;p&gt;After a deployment finishes, open the App Insights logs, activate the Agent, and ask:&lt;/p&gt;
&lt;blockquote style="border-left: 4px solid #0b6e4f; margin: 16px 0; padding: 8px 16px; background: #f7faf9;"&gt;
&lt;p&gt;&lt;em&gt;"Summarise the health of the application over the last 60 minutes. Have any new exception types appeared that were not present in the 24 hours prior?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The AI will parse the &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;exceptions&lt;/span&gt; table, filter out the normal background noise, and highlight genuinely new errors&amp;mdash;like a missed dependency injection in your &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;Startup.cs&lt;/span&gt; or a misconfigured &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;appsettings.json&lt;/span&gt; binding introduced by your latest Commerce 15 release.&lt;/p&gt;
&lt;p&gt;If it finds something, you can follow up conversationally:&lt;/p&gt;
&lt;blockquote style="border-left: 4px solid #0b6e4f; margin: 16px 0; padding: 8px 16px; background: #f7faf9;"&gt;
&lt;p&gt;&lt;em&gt;"For that new InvalidOperationException related to dependency injection, what was the exact stack trace?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Best Practices for AI-Assisted Telemetry&lt;/h2&gt;
&lt;p&gt;While the Azure Copilot Observability Agent is a game-changer for Optimizely developers, it works best when you guide it properly:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Enrich Your Telemetry:&lt;/strong&gt; Use &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;ITelemetryInitializer&lt;/span&gt; in your CMS 12/13 codebase to inject custom properties. Add the &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;CurrentLanguageBranch&lt;/span&gt; or &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;CustomerGroup&lt;/span&gt; to every request. You can then ask the agent: &lt;em&gt;"Are authenticated B2B users experiencing slower API response times than anonymous users?"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Track Custom Events:&lt;/strong&gt; Log specific business events like &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;TrackEvent("ItemAddedToCart")&lt;/span&gt; or &lt;span style="font-family: Consolas,Menlo,monospace; color: #0b6e4f;"&gt;TrackEvent("GraphQueryExecuted")&lt;/span&gt;. This allows you to ask the agent business-centric questions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Be Specific with Timeframes:&lt;/strong&gt; Vague questions yield vague answers. Always specify the time window (e.g., "between 10 AM and noon UTC yesterday").&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The addition of the Azure Copilot Observability Agent to Application Insights drastically lowers the barrier to entry for deep telemetry analysis in modern Optimizely environments. By focusing your natural language queries on the specific architectural patterns of CMS 12/13 (like ASP.NET Core caching and Graph API calls) and Commerce 14/15 (like asynchronous cart processing), you can dramatically reduce your mean time to resolution (MTTR) and ensure a blazingly fast digital experience.&lt;/p&gt;
&lt;h2&gt;How can Niteco help you?&lt;/h2&gt;
&lt;p&gt;Niteco is the largest global certified Optimizely CMS and Commerce partner, and we are experts and specialists in the Optimizely agent platform. We have recently attended Opticon New York 2026 as a sponsored partner, and we'll be coming back next week to London. Come and find us at our stand, or by looking for our yellow trousers.&amp;nbsp;&lt;/p&gt;
&lt;div style="margin: 20px 0 8px 0;"&gt;
&lt;div style="display: inline-block; vertical-align: top; box-sizing: border-box; width: 23%; min-width: 190px; margin: 0 1% 16px 0; padding: 16px; background: #f7f7f5; border-top: 4px solid #F8D749; border-radius: 4px;"&gt;
&lt;p style="margin: 0 0 4px 0; font-size: 44px; line-height: 1.1; font-weight: 300; color: #16171a;"&gt;#1&lt;/p&gt;
&lt;p style="margin: 0 0 10px 0; font-size: 13px; font-weight: bold; letter-spacing: 3px; text-transform: uppercase; color: #16171a;"&gt;Optimizely&lt;/p&gt;
&lt;p style="margin: 0; font-size: 15px; line-height: 1.5; color: #444;"&gt;The largest global partner. Powered by technical excellence and innovation. Accelerated by AI.&lt;/p&gt;
&lt;/div&gt;
&lt;div style="display: inline-block; vertical-align: top; box-sizing: border-box; width: 23%; min-width: 190px; margin: 0 1% 16px 0; padding: 16px; background: #f7f7f5; border-top: 4px solid #F8D749; border-radius: 4px;"&gt;
&lt;p style="margin: 0 0 4px 0; font-size: 44px; line-height: 1.1; font-weight: 300; color: #16171a;"&gt;400+&lt;/p&gt;
&lt;p style="margin: 0 0 10px 0; font-size: 13px; font-weight: bold; letter-spacing: 3px; text-transform: uppercase; color: #16171a;"&gt;AI-trained experts&lt;/p&gt;
&lt;p style="margin: 0; font-size: 15px; line-height: 1.5; color: #444;"&gt;Certified by leading platforms like Optimizely, Microsoft, Contentful &amp;amp; Adobe.&lt;/p&gt;
&lt;/div&gt;
&lt;div style="display: inline-block; vertical-align: top; box-sizing: border-box; width: 23%; min-width: 190px; margin: 0 1% 16px 0; padding: 16px; background: #f7f7f5; border-top: 4px solid #F8D749; border-radius: 4px;"&gt;
&lt;p style="margin: 0 0 4px 0; font-size: 44px; line-height: 1.1; font-weight: 300; color: #16171a;"&gt;2000+&lt;/p&gt;
&lt;p style="margin: 0 0 10px 0; font-size: 13px; font-weight: bold; letter-spacing: 3px; text-transform: uppercase; color: #16171a;"&gt;Successful projects&lt;/p&gt;
&lt;p style="margin: 0; font-size: 15px; line-height: 1.5; color: #444;"&gt;We have vast experience from years of delivering digital platforms for clients in diverse sectors.&lt;/p&gt;
&lt;/div&gt;
&lt;div style="display: inline-block; vertical-align: top; box-sizing: border-box; width: 23%; min-width: 190px; margin: 0 1% 16px 0; padding: 16px; background: #f7f7f5; border-top: 4px solid #F8D749; border-radius: 4px;"&gt;
&lt;p style="margin: 0 0 4px 0; font-size: 44px; line-height: 1.1; font-weight: 300; color: #16171a;"&gt;9&lt;/p&gt;
&lt;p style="margin: 0 0 10px 0; font-size: 13px; font-weight: bold; letter-spacing: 3px; text-transform: uppercase; color: #16171a;"&gt;Optimizely MVPs&lt;/p&gt;
&lt;p style="margin: 0; font-size: 15px; line-height: 1.5; color: #444;"&gt;Largest team with OMVPs. Meaning the best &amp;amp; most experienced Optimizely minds will work with you.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;As Optimizely's largest certified partner in Optimizely CMS, Commerce, and the agent platform, we can help you with any of your strategic initiatives globally around content, commerce, or your organisational AI.&lt;/p&gt;
&lt;p&gt;If you would like help getting more out of your Optimizely DXP telemetry, get in touch with us at Niteco: &lt;a href="https://niteco.com/contact-us/"&gt;https://niteco.com/contact-us/&lt;/a&gt;&lt;/p&gt;</summary></entry><entry><title>Visual Builder Without Graph: Server-Side MVC Rendering in CMS 13</title><link href="https://www.adnanzameer.com/2026/10/visual-builder-without-graph-mvc-rendering-cms-13.html" /><id>https://www.adnanzameer.com/2026/10/visual-builder-without-graph-mvc-rendering-cms-13.html</id><published>2026-10-07T09:51:36.0000000Z</published><updated>2026-10-07T10:30:47.0000000Z</updated><author><name>adnan.zameer@episerver.com</name></author><category term="Blog post" /><summary type="html">&lt;!--excerpt: Many teams assume moving to Optimizely CMS 13 requires rebuilding their frontend as a headless application or querying Optimizely Graph to render Visual Builder layouts. That is a myth. CMS 13 provides native ASP.NET Core MVC tag helpers that render Visual Builder experiences and sections server-side without a single Graph query. Here is how to configure and use them.--&gt;

When teams </summary></entry><entry><title>Writing a Catalog Entry the Low-Level Way. Part 2: The Writer</title><link href="https://szolkowski.github.io/2026/10/07/writing-a-catalog-entry-the-low-level-way-part-2-the-writer" /><id>https://szolkowski.github.io/2026/10/07/writing-a-catalog-entry-the-low-level-way-part-2-the-writer</id><published>2026-10-07T08:00:00.0000000Z</published><updated>2026-10-07T09:31:05.0000000Z</updated><author><name>stanislaw.szolkowski@firstlinesoftware.com</name></author><category term="Blog post" /><summary type="html">Upserting a catalog entry through CatalogContext and MetaDataPlus: ContentGuid, the meta class seam, response groups, and a create path that rolls back.</summary></entry><entry><title>Migrate Optimizely DAM Assets from CMS 12 to CMS 13</title><link href="https://world.optimizely.com/blogs/bartosz-sekula/dates/2026/10/dam-migration-from-cms12-to-cms13/" /><id>https://world.optimizely.com/blogs/bartosz-sekula/dates/2026/10/dam-migration-from-cms12-to-cms13/</id><published>2026-10-06T12:04:10.0000000Z</published><updated>2026-10-06T12:04:10.0000000Z</updated><author><name>Bartosz.Sekula@optimizely.com</name></author><category term="Blog post" /><summary type="html">&lt;p&gt;Sites that used Optimizely DAM in CMS 12 need their asset references moved to the new Graph-backed content sources after upgrading. CMS 13.3 includes a scheduled job for this. This post covers how to configure it, run it and later on verify the results.&lt;/p&gt;
&lt;p&gt;So, in CMS12 we supported DAM in the following properties:&lt;/p&gt;
&lt;p&gt;&lt;img src="/link/5d757ebab1ca48c18b10288b9e19ff34.aspx" alt="" width="828" height="985" /&gt;&lt;/p&gt;
&lt;p&gt;After upgrading your site to CMS you will see all those images, videos and files that came from DAM as broken or empty, both in edit mode and on the live site:&lt;/p&gt;
&lt;p&gt;&lt;img src="/link/998b02cfd18d4815a5c963d9aa35b3b3.aspx" alt="" width="1059" height="1006" /&gt;&lt;/p&gt;
&lt;p&gt;This happens because the content still points to the old dam content provider. In CMS 13, DAM assets come through Optimizely Graph instead. The old references need to be moved over to the new format.&lt;/p&gt;
&lt;p&gt;In CMS &lt;a title="DAM Migration package" href="https://nuget.optimizely.com/packages/episerver.cms.dammigration/13.3.0"&gt;13.3.0&lt;/a&gt; we added a scheduled job that does this for you. This post explains how to set it up and what to check along the way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What the job does&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The job is called &lt;strong&gt;Optimizely DAM Legacy Asset Migration&lt;/strong&gt;. It works in three steps:&lt;/p&gt;
&lt;p&gt;1. It reads all old DAM references from the database and works out the new ID for each asset.&lt;br /&gt;2. It goes through all content and updates the references. This covers published content, drafts, old versions, all languages and the trash.&lt;br /&gt;3. It removes the old tracking records in CMP and deletes the old mapping rows from the database.&lt;/p&gt;
&lt;p&gt;These property types are updated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ContentReference&lt;/li&gt;
&lt;li&gt;IList&amp;lt;ContentReference&amp;gt;&lt;/li&gt;
&lt;li&gt;ContentArea, including inline blocks&lt;/li&gt;
&lt;li&gt;Url&lt;/li&gt;
&lt;li&gt;LinkItem and LinkItemCollection&lt;/li&gt;
&lt;li&gt;XhtmlString&lt;/li&gt;
&lt;li&gt;Local blocks and block lists&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So all the properties defined in the below page type will be migrated successfully:&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;div&gt;
&lt;pre&gt;[ContentType(&lt;br /&gt;    GUID = "BA5FBE47-E44A-44CD-BFAF-3CC21B0A2F9C",&lt;br /&gt;    DisplayName = "DAM references test page",&lt;br /&gt;    Description = "Contains every property type that can hold a reference to a DAM asset",&lt;br /&gt;    GroupName = Globals.GroupNames.Specialized)]&lt;br /&gt;public class DamReferencesTestPage : PageData, IDamReferences&lt;br /&gt;{&lt;br /&gt;    [Display(Name = "Content Reference", GroupName = SystemTabNames.Content, Order = 10)]&lt;br /&gt;    public virtual ContentReference ContentReference1 { get; set; }&lt;br /&gt;&lt;br /&gt;    [Display(Name = "Content Area", GroupName = SystemTabNames.Content, Order = 20)]&lt;br /&gt;    public virtual ContentArea ContentArea1 { get; set; }&lt;br /&gt;&lt;br /&gt;    [Display(Name = "Content Area Item", GroupName = SystemTabNames.Content, Order = 30)]&lt;br /&gt;    [BackingType(typeof(PropertyContentAreaItem))]&lt;br /&gt;    public virtual ContentAreaItem ContentAreaItem1 { get; set; }&lt;br /&gt;&lt;br /&gt;    [Display(Name = "Link item", GroupName = SystemTabNames.Content, Order = 40)]&lt;br /&gt;    [BackingType(typeof(PropertyLinkItem))]&lt;br /&gt;    public virtual LinkItem LinkItem1 { get; set; }&lt;br /&gt;&lt;br /&gt;    [Display(Name = "Link item collection", GroupName = SystemTabNames.Content, Order = 50)]&lt;br /&gt;    public virtual LinkItemCollection LinkItemCollection1 { get; set; }&lt;br /&gt;&lt;br /&gt;    [Display(Name = "Url", GroupName = SystemTabNames.Content, Order = 60)]&lt;br /&gt;    public virtual Url Url1 { get; set; }&lt;br /&gt;&lt;br /&gt;    [Display(Name = "Url to image", GroupName = SystemTabNames.Content, Order = 70)]&lt;br /&gt;    [BackingType(typeof(PropertyImageUrl))]&lt;br /&gt;    public virtual Url ImageUrl1 { get; set; }&lt;br /&gt;&lt;br /&gt;    [Display(Name = "Image", GroupName = SystemTabNames.Content, Order = 80)]&lt;br /&gt;    [UIHint(UIHint.Image)]&lt;br /&gt;    public virtual ContentReference Image1 { get; set; }&lt;br /&gt;    &lt;br /&gt;    [Display(Name = "Block as property", GroupName = SystemTabNames.Content, Order = 90)]&lt;br /&gt;    public virtual DamReferencesTestBlock BlockAsProperty { get; set; }&lt;br /&gt;    &lt;br /&gt;    [Display(Name = "Block list as property", GroupName = SystemTabNames.Content, Order = 100)]&lt;br /&gt;    public virtual IList&amp;lt;DamReferencesTestBlock&amp;gt; BlocksAsProperty { get; set; }&lt;br /&gt;}&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Images, videos, documents and image and video renditions are all supported.&lt;/p&gt;
&lt;p&gt;The job updates versions in place. &lt;strong&gt;It does not create new versions, so your version history stays the same.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Before you start&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Make sure these things are in place first.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You are on CMS 13.3.0 or later.&lt;/li&gt;
&lt;li&gt;DAM features are activated&lt;/li&gt;
&lt;li&gt;&lt;img src="/link/3ece23070fdd448f9c35aa5ce9861950.aspx" alt="" width="973" height="400" /&gt;&lt;/li&gt;
&lt;li&gt;DAM assets have finished syncing from CMP to Graph. If Graph is not ready, editors cannot see or pick the assets after the migration.&lt;/li&gt;
&lt;li&gt;The Graph content sources for images, videos and files exist.&lt;/li&gt;
&lt;li&gt;CMP credentials are set up. Without them, the job still moves the references but skips the cleanup step.&lt;/li&gt;
&lt;li&gt;The Optimizely DAM Tracking Assets job runs without errors.&lt;/li&gt;
&lt;li&gt;You have a full database backup. The job cannot be undone from the admin UI.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Add the packages to your project.&lt;/p&gt;
&lt;p&gt;&amp;lt;PackageReference Include="EPiServer.Cms.DamMigration" Version="13.3.0" /&amp;gt;&lt;/p&gt;
&lt;p&gt;Register the services in Program.cs.&lt;/p&gt;
&lt;p&gt;builder.Services.AddDamMigration();&lt;/p&gt;
&lt;p&gt;Add the settings to appsettings.json. The job is turned off by default, so you need to turn it on.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;{&lt;br /&gt;&amp;nbsp; "Optimizely": {&lt;br /&gt;&amp;nbsp; &amp;nbsp; "Cmp": {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; "Client": {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "ClientId": "your-client-id",&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "ClientSecret": "your-client-secret"&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;&amp;nbsp; &amp;nbsp; },&lt;br /&gt;&amp;nbsp; &amp;nbsp; "Cms": {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; "DamUI": {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "TrackingEnabled": true&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; },&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; "DamMigration": {&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; "Enabled": true&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;br /&gt;&amp;nbsp; &amp;nbsp; }&lt;br /&gt;&amp;nbsp; }&lt;br /&gt;}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;There are more settings for batch sizes, concurrency and retries. The defaults should work for most sites.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Running the job&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;img src="/link/d365c8eb5cda4e30bcb4d6b6d8e7ce6a.aspx" alt="" width="813" height="648" /&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;1. Go to CMS Admin &amp;gt; Scheduled Jobs.&lt;br /&gt;2. Run Optimizely DAM Tracking Assets once to check that CMS can talk to CMP.&lt;br /&gt;3. Open Optimizely DAM Legacy Asset Migration and click Start.&lt;br /&gt;4. Wait for it to finish and read the message in the job history.&lt;/p&gt;
&lt;p&gt;A good result looks something like this.&lt;/p&gt;
&lt;p&gt;&lt;img src="/link/94119f48cad64433b083ecc6e28ef136.aspx" alt="" width="1041" height="313" /&gt;&lt;/p&gt;
&lt;p&gt;If some assets could not be moved, the message lists how many and why. The most common reasons are missing settings for an asset type or broken data in the old mapping table.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Check your content&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;When the job is done, open a few pages and blocks in edit mode. Look at images, videos, content areas and rich text. Check drafts and other languages too.&lt;/p&gt;
&lt;p&gt;&lt;img src="/link/748ed0d3b0c54f218020e62315568c7b.aspx" alt="" width="978" height="876" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When the job stops early&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The job is safe to run more than once. References that are already moved get skipped. If you stop the job halfway, everything done so far is kept, and the next run picks up the rest.&lt;/p&gt;
&lt;p&gt;Cleanup can stop on its own in a few cases.&lt;/p&gt;
&lt;p&gt;- No CMP credentials are configured.&lt;br /&gt;- Some content belongs to an external content provider. The job skips that content and leaves the old records alone, because they might still be used.&lt;br /&gt;- CMP returns an auth error or too many rate limit errors.&lt;/p&gt;
&lt;p&gt;In all of these cases the content changes stay in place. Only the cleanup is skipped or stopped.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;After the migration&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Set Optimizely:Cms:DamMigration:Enabled back to false. This hides the job so nobody runs it by mistake.&lt;/p&gt;
&lt;p&gt;Keep TrackingEnabled set to true. The Optimizely DAM Tracking Assets job then runs once a week and tells CMP which assets are used on your site.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A note on the trash&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There is no setting to skip the trash, and that is on purpose. Editors can restore content from the trash at any time. If the job skipped it, the cleanup step would delete old records that restored content still points to. Those references would then be broken for good.&lt;/p&gt;
&lt;p&gt;Please bear in mind that migration might take some time as we need to scan each content item version which holds a reference to a DAM asset.&lt;/p&gt;</summary></entry><entry><title>Scheduler Delegation: Using Optimizely DXP Infrastructure for Queued Processing</title><link href="https://world.optimizely.com/blogs/mike/dates/2026/10/scheduler-delegation-using-optimizely-dxp-infrastructure-for-queued-processing/" /><id>https://world.optimizely.com/blogs/mike/dates/2026/10/scheduler-delegation-using-optimizely-dxp-infrastructure-for-queued-processing/</id><published>2026-10-06T08:46:00.0000000Z</published><updated>2026-10-06T08:46:00.0000000Z</updated><author><name>mikhail.syvachenko@syzygy.de</name></author><category term="Blog post" /><summary type="html">&lt;div&gt;
&lt;div&gt;At SYZYGY Techsolutions, we support Optimizely DXP projects at scale, so continuously identifying unique ways to optimize the application is an essential part of how we work. One pattern that has paid off repeatedly is&amp;nbsp;&lt;strong&gt;scheduler delegation&lt;/strong&gt;: moving expensive, recurring or bursty background work off user-facing instances and onto the dedicated scheduler instance, using infrastructure that DXP already provides.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;This post walks through the problem, the architecture, the reasoning behind it, and the&amp;nbsp;code needed to apply it in your own solution.&lt;/div&gt;
&lt;h2&gt;The problem: expensive work triggered from the request path&lt;/h2&gt;
Imagine thousands of visitors performing a similar action at the same time, spread across several instances of your application. They might be clicking through a car configurator, where every choice of model, colour or engine asks which options can still be combined and which are available. Or they might simply be opening pages, each of which needs the website's translated texts in their language.&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;Resolving that action takes time. The answer depends on several data sources, each with its own rules: product rules, production planning and supplier stock for the configurator; the CMS, a translation management system and other applications for the texts. Yet the number of distinct questions is limited, and the answer does not need to be exact to the second. The final order confirms availability anyway, and a text that is a minute old is harmless.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;So you cache the result. That solves the speed, but introduces new problems:&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;1.&amp;nbsp;&lt;strong&gt;Cold starts.&lt;/strong&gt;&amp;nbsp;After a deployment, a restart or an expiry, the cache is empty and the first visitors get nothing or wait.&lt;/div&gt;
&lt;div&gt;2. &lt;strong&gt;Repeated work.&lt;/strong&gt;&amp;nbsp;Many visitors hit the same empty entry at once, and each request calculates the same answer, overloading the underlying systems.&lt;/div&gt;
&lt;div&gt;3.&amp;nbsp;&lt;strong&gt;Instances out of sync.&lt;/strong&gt; Every web instance has its own cache, recalculates on its own schedule, and may show a different answer than its neighbour.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Together these produce a cache stampede: the same heavy work done many times over, competing with visitor traffic for CPU, memory and connections to the source systems. Making visitors wait for the calculation only moves the problem onto them. The goal is to keep&amp;nbsp;&lt;strong&gt;the request path fast, whatever the state of the cache&lt;/strong&gt;.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Optimizely's built-in &lt;span style="font-family: 'courier new', courier, monospace;"&gt;ISynchronizedObjectInstanceCache&lt;/span&gt; is a great fit for data that changes infrequently. Under high load in a multi-instance setup, however, we observed clear limits of its caching model for this kind of workload. Each instance keeps its own in-memory copy, and only invalidations travel between instances. Every instance that recalculates an entry and stores it sends an invalidation to all the others, which drop their copies and recalculate in turn. The result is a stream of cache-update events from every side, the same stampede repeated on every instance, and moments when instances serve different values or overwrite each other's results.&lt;/div&gt;
&lt;h2&gt;What DXP gives you: a scheduler instance&lt;/h2&gt;
&lt;div&gt;On Preproduction and Production, Optimizely DXP customers can request a dedicated &lt;strong&gt;scheduler instance&lt;/strong&gt;, one per environment, next to the web instances that serve visitors. This instance:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;does not receive user-facing requests;&lt;/li&gt;
&lt;li&gt;is intended for backend work: scheduled jobs, background tasks and back-office operations performed by content editors;&lt;/li&gt;
&lt;li&gt;is part of the same deployment, so it runs your code, your configuration and your initialization modules.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;In other words, DXP already hands you a place where heavy work runs away from visitor traffic. Most teams use it only for classic scheduled jobs on a fixed timetable. Scheduler delegation extends it to&amp;nbsp;&lt;strong&gt;event-driven, queued work&lt;/strong&gt;: user-facing instances report that something is needed, and the scheduler instance does it.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;The rest of the pattern relies on platform features that every DXP project already has:&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Capability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Provided by&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Role in the pattern&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-instance messaging&lt;/td&gt;
&lt;td&gt;The CMS event system (&lt;span style="font-family: 'courier new', courier, monospace;"&gt;IEventRegistry&lt;/span&gt;), which uses Azure Service Bus on DXP&lt;/td&gt;
&lt;td&gt;Carries a small "something is missing" message from any instance to all instances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduler awareness&lt;/td&gt;
&lt;td&gt;&lt;span style="font-family: 'courier new', courier, monospace;"&gt;SchedulerOptions.Enabled&lt;/span&gt; (EPiServer.Scheduler), which is only &lt;span style="font-family: 'courier new', courier, monospace;"&gt;true&lt;/span&gt; on the scheduler instance&lt;/td&gt;
&lt;td&gt;Lets a handler decide whether it should act on a message&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scheduled jobs&lt;/td&gt;
&lt;td&gt;The CMS scheduled job engine (&lt;span style="font-family: 'courier new', courier, monospace;"&gt;ScheduledJobBase&lt;/span&gt;, &lt;span style="font-family: 'courier new', courier, monospace;"&gt;[ScheduledPlugIn]&lt;/span&gt;)&lt;/td&gt;
&lt;td&gt;Provides a recurring, restartable execution loop for batch processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared state&lt;/td&gt;
&lt;td&gt;Blob storage (&lt;span style="font-family: 'courier new', courier, monospace;"&gt;IBlobFactory&lt;/span&gt;), the CMS/Commerce databases, or a persisted synchronized cache&lt;/td&gt;
&lt;td&gt;Carries the finished result back to user-facing instances&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;
&lt;div&gt;Because all of this ships with DXP, the pattern runs entirely on infrastructure you already have.&lt;/div&gt;
&lt;h2&gt;Architecture overview&lt;/h2&gt;
&lt;p&gt;&lt;img src="/link/a6ead68713a0439c98e910ab124199f6.aspx" width="1233" height="790" /&gt;&lt;/p&gt;
&lt;div&gt;The flow, step by step:&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;1. &lt;strong&gt;Raise.&lt;/strong&gt; A user-facing instance reads the calculated result from shared storage. If it is missing or stale, it returns what it has (stale data or a fallback) and&amp;nbsp;&lt;strong&gt;raises an event&lt;/strong&gt;&amp;nbsp;that identifies what is needed. Raising is fire-and-forget and must never throw into the request.&lt;/div&gt;
&lt;div&gt;2. &lt;strong&gt;Deliver.&lt;/strong&gt;&amp;nbsp;The event travels over the platform's event bus and reaches every instance.&lt;/div&gt;
&lt;div&gt;3. &lt;strong&gt;Enqueue.&lt;/strong&gt;&amp;nbsp;Every instance receives the event, but only the scheduler instance acts on it: it places the item into an in-memory queue that keeps one entry per unique input.&lt;/div&gt;
&lt;div&gt;4. &lt;strong&gt;Batch.&lt;/strong&gt;&amp;nbsp;A scheduled job runs at a fixed interval (for example every minute), drains the queue, and processes each unique input once.&lt;/div&gt;
&lt;div&gt;5. &lt;strong&gt;Write back.&lt;/strong&gt;&amp;nbsp;The result is written to shared storage.&lt;/div&gt;
&lt;div&gt;6. &lt;strong&gt;Read.&lt;/strong&gt;&amp;nbsp;User-facing instances pick up the result on their next read, with no direct communication between the scheduler and the web instances.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;img src="/link/0e2a878fd9394d50bf63984746603a0e.aspx" width="1233" height="764" /&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;From the visitor's perspective, every request is answered immediately. The expensive work runs later on the scheduler instance, once per unique input.&lt;/div&gt;
&lt;h2&gt;Design decisions and the reasoning behind them&lt;/h2&gt;
&lt;div&gt;The code samples below are simplified C# against the Optimizely CMS 12 APIs. Logging, null checks and dependency injection details are trimmed for readability.&lt;/div&gt;
&lt;h3&gt;1. Events carry intent; data stays at the source&lt;/h3&gt;
&lt;div&gt;The event payload is only an identifier (for example &lt;span style="font-family: 'courier new', courier, monospace;"&gt;{ model, options }&lt;/span&gt;). It is small, cheap to serialize and well within message size limits. The scheduler fetches whatever data it needs from the source of truth when it processes the item. This avoids stale payloads and keeps the bus lightweight.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;The payload must be serializable by the event system, and its type must be registered as a known event type:&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;using System.Runtime.Serialization;
using EPiServer.Events;

[DataContract]
[EventsServiceKnownType]
public class RecalculationRequest
{
    [DataMember] public string Model { get; set; } = "";
    [DataMember] public string Options { get; set; } = "";
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. An in-memory queue on the scheduler is enough&lt;/h3&gt;
&lt;div&gt;A durable queue (a dedicated Service Bus queue, a database table) is the obvious place for pending work. Here the work is &lt;strong&gt;idempotent and re-calculable&lt;/strong&gt;, so a simple in-memory queue on the scheduler instance does the job:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;If the scheduler restarts and loses its queue, the next request that sees a missing or stale result raises the event again.&lt;/li&gt;
&lt;li&gt;Duplicate events are harmless because the queue keeps one entry per input.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Self-healing through the request path replaces durability. That removes persistence, retries, dead-lettering and cleanup from the design. This only holds if the work really is idempotent; for non-recoverable work, use a durable queue instead.&lt;/div&gt;
&lt;h3&gt;3. Batching and de-duplication give you load control&lt;/h3&gt;
&lt;div&gt;&lt;img src="/link/812931f526124590b55800d55924edf8.aspx" width="1234" height="617" /&gt;&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Because events are queued and drained on a timer, the cost of the system depends only on the number of &lt;strong&gt;unique inputs&lt;/strong&gt;. Request volume and instance count drop out of the equation: a burst of thousands of misses collapses into a handful of resource (re)generations. The job interval becomes a single dial that trades refresh latency against background load.&lt;/div&gt;
&lt;h3&gt;4. A de-duplicating queue keeps producers fast and memory bounded&lt;/h3&gt;
&lt;div&gt;The queue is written by event handler threads (producers) and drained by the job thread (consumer). Keying it by the inputs means a burst of identical events occupies a single entry, so memory stays bounded during a miss storm. Draining swaps in a fresh dictionary atomically, so producers never wait for the batch. The queue is registered as a singleton, so the event handler and the scheduled job share the same instance:&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;using System.Collections.Concurrent;
using EPiServer.ServiceLocation;

[ServiceConfiguration(Lifecycle = ServiceInstanceScope.Singleton)]
public class RecalculationQueue
{
    private ConcurrentDictionary&amp;lt;(string Model, string Options), RecalculationRequest&amp;gt; _pending = new();

    public void Add(RecalculationRequest request) =&amp;gt;
        _pending.TryAdd((request.Model, request.Options), request);

    public IReadOnlyCollection&amp;lt;RecalculationRequest&amp;gt; Drain()
    {
        // A write racing the swap can be lost; the next miss raises it again.
        var drained = Interlocked.Exchange(ref _pending, new());
        return drained.Values.ToList();
    }

    public bool IsEmpty =&amp;gt; _pending.IsEmpty;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;5. One event rail, two routing modes&lt;/h3&gt;
&lt;div&gt;Local development, CI and single-node environments usually run without a scheduler instance. A configuration flag routes the same event differently, so one code path covers both setups:&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;&lt;img src="/link/28e7cbdd7e704261a5ae2f3ed33c25e5.aspx" width="1236" height="710" /&gt;&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Delegation on&lt;/strong&gt; (Preproduction, Production): only the scheduler instance reacts, and it queues the item.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delegation off&lt;/strong&gt; (local, simple setups): only the instance that raised the event reacts, and it processes the item immediately on a background thread.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Both modes share the same read path and the same processing logic. Only the "who handles it, and when" decision changes, and that sits behind a configuration flag. This keeps the pattern easy to test locally and makes it safe to switch off.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;The routing is encapsulated in an abstract initialization module. Concrete features only supply the event identity and the processing logic (template method pattern):&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;using EPiServer.Events;
using EPiServer.Events.Clients;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Scheduler;
using EPiServer.ServiceLocation;
using Microsoft.Extensions.Options;

public abstract class DelegatingEventModule&amp;lt;TRequest&amp;gt; : IInitializableModule
    where TRequest : class
{
    protected abstract Guid EventId { get; }
    protected abstract Guid RaiserId { get; }
    protected abstract void HandleOnScheduler(TRequest request);
    protected abstract void HandleLocally(TRequest request);

    private bool DelegateToScheduler =&amp;gt;
        ServiceLocator.Current.GetInstance&amp;lt;IOptions&amp;lt;DelegationOptions&amp;gt;&amp;gt;().Value.Enabled;

    private static bool IsScheduler =&amp;gt;
        ServiceLocator.Current.GetInstance&amp;lt;IOptions&amp;lt;SchedulerOptions&amp;gt;&amp;gt;().Value.Enabled;

    public void Initialize(InitializationEngine context)
    {
        var evt = ServiceLocator.Current.GetInstance&amp;lt;IEventRegistry&amp;gt;().Get(EventId);
        evt.Raised += DelegateToScheduler ? OnSchedulerRoute : OnLocalRoute;
    }

    public void Uninitialize(InitializationEngine context)
    {
        var evt = ServiceLocator.Current.GetInstance&amp;lt;IEventRegistry&amp;gt;().Get(EventId);
        evt.Raised -= DelegateToScheduler ? OnSchedulerRoute : OnLocalRoute;
    }

    private void OnSchedulerRoute(object sender, EventNotificationEventArgs e)
    {
        if (!IsScheduler) return;                  // web instances ignore it
        if (e.Param is not TRequest request) return;
        HandleOnScheduler(request);                // typically: queue.Add(request)
    }

    private void OnLocalRoute(object sender, EventNotificationEventArgs e)
    {
        if (e.RaiserId != RaiserId) return;        // only handle what this instance raised
        if (e.Param is not TRequest request) return;
        Task.Run(() =&amp;gt; HandleLocally(request));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;A concrete module is discovered by the initialization engine through &lt;span style="font-family: 'courier new', courier, monospace;"&gt;[ModuleDependency]&lt;/span&gt;:&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;[ModuleDependency(typeof(EPiServer.Web.InitializationModule))]
public class ConfiguratorRecalculationModule : DelegatingEventModule&amp;lt;RecalculationRequest&amp;gt;
{
    internal static readonly Guid Id = new("00000000-0000-0000-0000-000000000001");
    internal static readonly Guid Raiser = Guid.NewGuid();

    protected override Guid EventId =&amp;gt; Id;
    protected override Guid RaiserId =&amp;gt; Raiser;

    protected override void HandleOnScheduler(RecalculationRequest request) =&amp;gt;
        ServiceLocator.Current.GetInstance&amp;lt;RecalculationQueue&amp;gt;().Add(request);

    protected override void HandleLocally(RecalculationRequest request) =&amp;gt;
        ServiceLocator.Current.GetInstance&amp;lt;ConfiguratorCalculator&amp;gt;().Recalculate(request);
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;Four details matter here:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;span style="font-family: 'courier new', courier, monospace;"&gt;IsScheduler&lt;/span&gt; reads &lt;span style="font-family: 'courier new', courier, monospace;"&gt;SchedulerOptions.Enabled&lt;/span&gt;, which the platform sets per instance. On DXP the scheduler is enabled only on the scheduler instance, so the check follows the real topology.&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family: 'courier new', courier, monospace;"&gt;DelegationOptions&lt;/span&gt; is your own options class, bound from &lt;span style="font-family: 'courier new', courier, monospace;"&gt;appsettings.json&lt;/span&gt;. It is the configuration flag that switches between the two routing modes.&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family: 'courier new', courier, monospace;"&gt;RaiserId&lt;/span&gt; is generated once per process (&lt;span style="font-family: 'courier new', courier, monospace;"&gt;Guid.NewGuid()&lt;/span&gt; in a static field). It lets an instance recognize its own events when delegation is off.&lt;/li&gt;
&lt;li&gt;&lt;span style="font-family: 'courier new', courier, monospace;"&gt;EventId&lt;/span&gt; is a fixed GUID. Every instance must use the same value to subscribe to the same event.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;6. The read path is defensive&lt;/h3&gt;
&lt;div&gt;Reading the result and requesting a recalculation live in one service on the web instances. It follows a simple rule, &lt;strong&gt;stale data beats no data&lt;/strong&gt;, and a failure to raise the event never reaches the visitor:&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;using EPiServer.Events.Clients;

public class ConfiguratorResultService(
    IEventRegistry eventRegistry,
    IResultStorage storage,
    ILogger&amp;lt;ConfiguratorResultService&amp;gt; logger)
{
    private static readonly TimeSpan Ttl = TimeSpan.FromMinutes(2);

    public ConfiguratorResult GetResult(RecalculationRequest request)
    {
        var entry = storage.TryRead(request);

        if (entry is null || entry.CalculatedAt &amp;lt; DateTime.UtcNow - Ttl)
        {
            RequestRecalculation(request);
        }

        return entry?.Value ?? ConfiguratorResult.Fallback;
    }

    private void RequestRecalculation(RecalculationRequest request)
    {
        try
        {
            eventRegistry.Get(ConfiguratorRecalculationModule.Id)
                ?.Raise(ConfiguratorRecalculationModule.Raiser, request);
        }
        catch (Exception ex)
        {
            // A failed raise only delays the refresh; the visitor still gets an answer.
            logger.LogWarning(ex, "Recalculation request for {Model} could not be raised", request.Model);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;7. The scheduled job is the batch processor&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;using EPiServer.PlugIn;
using EPiServer.Scheduler;

[ScheduledPlugIn(
    DisplayName = "Recalculate configurator results on demand",
    GUID = "00000000-0000-0000-0000-000000000002",
    IntervalType = ScheduledIntervalType.Minutes,
    IntervalLength = 1,
    DefaultEnabled = true,
    Restartable = true)]
public class RecalculateOnDemandJob : ScheduledJobBase
{
    private readonly RecalculationQueue _queue;
    private readonly ConfiguratorCalculator _calculator;
    private readonly ILogger&amp;lt;RecalculateOnDemandJob&amp;gt; _logger;
    private bool _stopRequested;

    public RecalculateOnDemandJob(
        RecalculationQueue queue,
        ConfiguratorCalculator calculator,
        ILogger&amp;lt;RecalculateOnDemandJob&amp;gt; logger)
    {
        _queue = queue;
        _calculator = calculator;
        _logger = logger;
        IsStoppable = true;
    }

    public override void Stop() =&amp;gt; _stopRequested = true;

    public override string Execute()
    {
        if (_queue.IsEmpty) return "Nothing to recalculate";

        var batch = _queue.Drain();

        var done = 0;
        foreach (var request in batch)
        {
            if (_stopRequested) break;
            try
            {
                _calculator.Recalculate(request);   // calculate and write to shared storage
                done++;
            }
            catch (Exception ex)
            {
                // One failing item must not abandon the batch; the next miss re-raises it.
                _logger.LogError(ex, "Recalculation failed for {Model}", request.Model);
            }
        }

        return $"Recalculated {done} of {batch.Count} items";
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;&lt;span style="font-family: 'courier new', courier, monospace;"&gt;Restartable = true&lt;/span&gt; makes the scheduler start the job again if the instance shuts down while it is running, and &lt;span style="font-family: 'courier new', courier, monospace;"&gt;IsStoppable&lt;/span&gt; together with &lt;span style="font-family: 'courier new', courier, monospace;"&gt;Stop()&lt;/span&gt; lets an operator end a long batch from the admin interface. Items dropped by a stop are simply requested again by the next miss.&lt;/div&gt;
&lt;h2&gt;Results in production&lt;/h2&gt;
&lt;div&gt;In one production implementation, the read path serves about 1,022,000 requests per day (absolute figures are scaled for confidentiality; the ratios are real):&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1,000,000 hits&lt;/strong&gt; (about 98%), answered from a fresh entry;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;22,000 stale reads&lt;/strong&gt; (about 2%), answered immediately with the previous result while the scheduler refreshes the entry;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;0 misses. &lt;/strong&gt;Misses only occur before the storage is first populated. After that, visitors receive the previous value of an expired entry until it is refreshed.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Before the pattern, each of those 22,000 stale reads would have been a miss: a visitor waiting for a synchronous recalculation, with several instances often recalculating the same entry at once. Stale reads now absorb that work, and the recalculation runs once per unique input on the scheduler instance. The exact numbers depend on traffic, the feature and the chosen TTL, but the shape stays the same: misses disappear and stale reads take their place.&lt;/div&gt;
&lt;h2&gt;What enables this architecture&lt;/h2&gt;
&lt;div&gt;The building blocks are the platform capabilities listed earlier. Three properties make them fit together:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Every instance runs the same deployment.&lt;/strong&gt; Initialization modules subscribe to the event on all instances, and a single platform setting decides which one acts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The control path and the data path are separate.&lt;/strong&gt; Events travel over the bus, results travel through shared storage, and the scheduler and web instances never call each other. Either path can be replaced independently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;There is a single writer.&lt;/strong&gt; Only the scheduler instance calculates and stores results, so every web instance reads the same value. Eventual consistency is confined to one place: the time until the next job tick.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The return path is a free choice&lt;/h2&gt;
&lt;div&gt;Any storage that every instance can read works as the return path. Blob storage is the simplest starting point and handles large results well. The CMS or Commerce database suits small structured results but adds load to a shared database. A synchronized cache gives the lowest read latency, provided the scheduler instance stays its only writer; an eviction then simply raises a new recalculation request. Whichever you choose, keep a short-lived local cache on the web instances so that shared storage is read once per interval rather than on every request.&lt;/div&gt;
&lt;h2&gt;Trade-offs and failure modes&lt;/h2&gt;
&lt;div&gt;Restarts, duplicate events, failing items and environments without a scheduler are covered by the design decisions above. Three trade-offs remain:&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concern&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Behavior&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Mitigation&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Eventual consistency&lt;/td&gt;
&lt;td&gt;A first-time miss is answered with a fallback until the next job tick&lt;/td&gt;
&lt;td&gt;Choose an interval that matches business tolerance; pre-warm after deployments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event delivery is best-effort&lt;/td&gt;
&lt;td&gt;A message may be missed in rare cases&lt;/td&gt;
&lt;td&gt;The next miss or stale read raises it again; the TTL bounds the impact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shared scheduler capacity&lt;/td&gt;
&lt;td&gt;The scheduler instance also runs every other scheduled job and the editors' back-office work, so a long batch competes with them&lt;/td&gt;
&lt;td&gt;Keep per-item work bounded, watch batch duration, and move truly heavy work to a dedicated service&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;br /&gt;
&lt;div&gt;Observability is worth building from day one: log every miss, every raise, and the batch size per tick. The ratio between events received and unique inputs processed is a direct measure of how much work the pattern is saving.&lt;/div&gt;
&lt;h2&gt;When to use it&lt;/h2&gt;
&lt;div&gt;The problem section already describes the shape that fits: a limited set of repeated questions, slow answers from several sources, tolerance for slightly outdated results, and work that can be recalculated at any time. Besides configurators and translated texts, the same shape appears in accessory compatibility per model, dealer and store details per region, and filter options per product category.&lt;/div&gt;
&lt;br /&gt;
&lt;div&gt;Two further conditions are easy to overlook:&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;The answer contains no financial values, personal data or access decisions.&lt;/strong&gt; Such data must be accurate and current on every request.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visitors ask only a fraction of all possible questions.&lt;/strong&gt; If every answer is needed anyway, a plain scheduled job that calculates everything is simpler.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;Cases that look similar but fail one of these conditions:&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Business case&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Why it does NOT fit&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prices, discounts, taxes, delivery costs&lt;/td&gt;
&lt;td&gt;Financial values shown to customers must be accurate at all times. An outdated value creates legal and reputational risk.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stock or slot reservation at checkout&lt;/td&gt;
&lt;td&gt;Visitors compete for the same item, which needs a synchronous, locked check.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personal recommendations or account data&lt;/td&gt;
&lt;td&gt;Inputs are unique per visitor, so de-duplication has nothing to merge, and personal data should stay out of shared storage.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Navigation or content filtered by permissions&lt;/td&gt;
&lt;td&gt;Access decisions must be current. A stale answer can expose content.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sitemaps and product feeds&lt;/td&gt;
&lt;td&gt;Every answer is needed and read by machines on a timetable. A plain scheduled job is simpler.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image renditions&lt;/td&gt;
&lt;td&gt;Serving the original as a fallback defeats the purpose, and the number of variants is large. Resize on demand at the CDN.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Getting started checklist&lt;/h2&gt;
&lt;div&gt;1. Request a scheduler instance for Preproduction and Production through Optimizely support.&lt;/div&gt;
&lt;div&gt;2. Identify one expensive, re-calculable operation currently done on the request path.&lt;/div&gt;
&lt;div&gt;3. Define a small event payload and a stable event identifier.&lt;/div&gt;
&lt;div&gt;4. Implement the read path: raise on miss or stale, never throw, always return something.&lt;/div&gt;
&lt;div&gt;5. Implement the delegating module with both routing modes behind a configuration flag.&lt;/div&gt;
&lt;div&gt;6. Add the de-duplicating queue and the scheduled batch job.&lt;/div&gt;
&lt;div&gt;7. Pick the return path and a TTL that match your consistency needs.&lt;/div&gt;
&lt;div&gt;8. Add logging and metrics for misses, raises and batch size.&lt;/div&gt;
&lt;div&gt;9. Disable delegation locally; enable it in Preproduction and Production.&lt;/div&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;div&gt;Scheduler delegation turns an under-used part of DXP into a small background processing system. Web instances report what they need over the event bus, the scheduler instance collects those requests and processes them in batches, and shared storage hands the results back. The request path stays fast, each heavy calculation runs once per unique input, and everything runs on infrastructure DXP already provides.&lt;/div&gt;
&lt;/div&gt;</summary></entry><entry><title>Thomas in the AI Discovery Era</title><link href="https://world.optimizely.com/blogs/ritu-madan/dates/2026/10/thomas-in-the-ai-discovery-era/" /><id>https://world.optimizely.com/blogs/ritu-madan/dates/2026/10/thomas-in-the-ai-discovery-era/</id><published>2026-10-06T02:48:33.0000000Z</published><updated>2026-10-06T02:48:33.0000000Z</updated><author><name>ritu.madan@valtech.com</name></author><category term="Blog post" /><summary type="html">&lt;p&gt;&lt;span class="ckymss ckya1h ckyjz7 ckymt4"&gt;Its been a while, but hope you haven't forgotten Thomas, our most loyal visitor, the one who suddenly stops showing up.&lt;br /&gt;Not because he lost interest. Because he asked an AI tool the question he used to Google, got his answer in thirty seconds, and never came to the site at all.&lt;br /&gt;That's the shift most brands haven't clocked yet. The front door moved. Search isn't the only way in anymore, and a lot of the traffic you're losing to AI never shows up as a loss in your analytics. It just quietly isn't there.&lt;br /&gt;But here's the part worth sitting with: AI isn't only taking visits. It's also sending them, to the brands it decides are worth citing. Which means the game isn't over. It just changed.&lt;br /&gt;I pulled apart what actually earns those citations, and the one tactic everyone's doubling down on that the 2026 data says barely moves the needle. Plus what it means for the visitor who lands on your site already halfway through a thought.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span class="ckymss ckya1h ckyjz7 ckymt4"&gt;Read the full article &lt;a href="https://www.linkedin.com/pulse/thomass-new-entry-point-ritu-madan-t1pze/"&gt;here&lt;/a&gt;.&lt;/span&gt;&lt;/p&gt;</summary></entry><entry><title>Gated Content on Optimizely Graph, Part 2: Keeping Graph in Step with the CMS</title><link href="https://pino-labs.com/blog/gated-content-optimizely-graph-part-2-in-step/" /><id>https://pino-labs.com/blog/gated-content-optimizely-graph-part-2-in-step/</id><published>2026-10-06T00:00:00.0000000Z</published><updated>2026-10-06T21:30:51.0000000Z</updated><author><name>piotr.n@rockwool.com</name></author><category term="Blog post" /><summary type="html">A correct ACL in the CMS is not enough: Graph evaluates access against the state available to it. That state can lag behind the CMS, and every cache between Graph and the browser adds a window of its own.</summary></entry><entry><title>Optimizely Agent Platform - October 2026 State of Play</title><link href="https://world.optimizely.com/blogs/scott-reed/dates/2026/10/optimizely-agent-platform---october-2025-state-of-play/" /><id>https://world.optimizely.com/blogs/scott-reed/dates/2026/10/optimizely-agent-platform---october-2025-state-of-play/</id><published>2026-10-05T10:45:21.0000000Z</published><updated>2026-10-05T10:45:21.0000000Z</updated><author><name>scott.reed@niteco.com</name></author><category term="Blog post" /><summary type="html">&lt;h2&gt;Overview&lt;/h2&gt;
&lt;p&gt;A month ago I wrote up my takeaways from Opticon New York (&lt;a href="/link/fecc835a60ef4678891f74e1fcbf06ea.aspx"&gt;Opticon New York &amp;amp; The Strategy on Upgrading to CMS 13 &amp;amp; Commerce 15&lt;/a&gt;). The headline was that Opal has grown up: the platform as a whole is now the &lt;strong&gt;Optimizely Agent Platform&lt;/strong&gt;, and the AI chat you work with day to day is now &lt;strong&gt;Mark&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The trouble is that the pace of delivery has been relentless. The 2026 release notes alone run to well over an hour of reading, with something shipping almost every week, and features have been renamed, moved, merged and superseded along the way. If you are a solution architect or technical lead trying to answer the simple question &lt;em&gt;"what does the platform actually do today, and how is it put together?"&lt;/em&gt;, it is genuinely hard to get a clean picture.&lt;/p&gt;
&lt;p&gt;So this post is my attempt at a &lt;strong&gt;state-of-play roundup as of early October 2026&lt;/strong&gt;, broken down by the core building blocks of the platform, with a focus on the architecture, configuration and governance details that matter when you are designing real solutions. Wherever I can, I call out &lt;strong&gt;what changed and when&lt;/strong&gt; using the official &lt;a href="https://docs.optimizely.com/agent-platform/release-notes"&gt;release notes&lt;/a&gt;, and I flag the places where the documentation is still catching up.&lt;/p&gt;
&lt;p&gt;&lt;span style="text-decoration: underline;"&gt;&lt;strong&gt;FYI, I know this is quite a long document, and it is mostly AI-generated, with my review added in. It should be a really good update for anyone who's used the platform, trying to keep up with all the changes. I'll probably do one of these every 6 months.&amp;nbsp;&lt;/strong&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;A note on sources.&lt;/strong&gt; Everything here is based on the &lt;a href="https://docs.optimizely.com/agent-platform/docs"&gt;Agent Platform documentation&lt;/a&gt;, the &lt;a href="https://docs.optimizely.com/agent-platform/release-notes"&gt;2026&lt;/a&gt; and &lt;a href="https://docs.optimizely.com/agent-platform/release-notes/2025-agent-platform-release-notes"&gt;2025&lt;/a&gt; release notes, and Optimizely's Opticon announcements, as they stood on 5 October 2026. Screenshots marked as from the documentation are reproduced with permission. Things move fast, so always double-check the docs before you commit to a design.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Also &lt;a href="https://drive.google.com/file/d/1VLziEiYH4mSGEkiC3VrGJOTP_pSIwF0-/view?usp=sharing"&gt;https://drive.google.com/file/d/1VLziEiYH4mSGEkiC3VrGJOTP_pSIwF0-/view?usp=sharing&lt;/a&gt; is a version of this document as a nice infographic for easier digestion&amp;nbsp;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;TL;DR for architects&lt;/h2&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;Area&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Where we are today&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Naming&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Opal &amp;rarr; &lt;strong&gt;Optimizely Agent Platform&lt;/strong&gt; (effective 1 September 2026). &lt;strong&gt;Mark&lt;/strong&gt; is the personified AI; &lt;strong&gt;Mark-1 / Mark AI&lt;/strong&gt; is the post-trained model family; &lt;strong&gt;Mark-IQ&lt;/strong&gt; is the context layer; &lt;strong&gt;Mark-Bench&lt;/strong&gt; is the open-source benchmark. The docs still say "Opal" almost everywhere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Models&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Six inference levels (Quick &amp;rarr; Code) mapped to Gemini or Claude models. Admins pick a default &lt;strong&gt;model provider&lt;/strong&gt; per instance; specialised agents can override both provider and level. Mark's own post-trained models are announced as "coming soon".&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Orchestration&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Chat automatically delegates to &lt;strong&gt;sub-agents&lt;/strong&gt; (Mini / Fast / Deep tiers) in isolated contexts, with parallel fan-out, no recursion and a per-turn dispatch budget.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Skills&lt;/strong&gt; (formerly Instructions), &lt;strong&gt;Brands&lt;/strong&gt;, &lt;strong&gt;Memory&lt;/strong&gt;, &lt;strong&gt;Personal files&lt;/strong&gt; and &lt;strong&gt;RAG&lt;/strong&gt; (CMP, Graph, and now your own artifacts).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;System tools, connector tools, &lt;strong&gt;remote MCP servers&lt;/strong&gt; (bring your own as of July) and &lt;strong&gt;custom tools&lt;/strong&gt; via SDKs for TypeScript, Python and C#. A large CMS 13 (PaaS) tool set landed from May onwards.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Agents&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Agent Directory (70+ prebuilt), Agent Library (curated launcher), specialised agents, workflow agents (now with Code steps, parallel loops, nested workflows, trigger priority and version history).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Autonomy&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Virtual Teammates&lt;/strong&gt; with their own Opti ID, memory and scheduled jobs, plus &lt;strong&gt;Team Messages&lt;/strong&gt; as the collaboration layer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Governance&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Input guardrails, the &lt;strong&gt;Quality&lt;/strong&gt; tab (Output Evaluation, Execution Guardrails, Execution Advisor), Safe URL Browsing, agent sharing/visibility, custom roles, exportable audit logs and version history.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Commercials&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Credits pooled across products, fixed complexity bands since March 2026, 200 complimentary credits per instance per month until the end of 2026.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;1. What's in a name? Opal, Agent Platform and Mark&lt;/h2&gt;
&lt;p&gt;Let's get the naming sorted first, because it is the single biggest source of confusion right now.&lt;/p&gt;
&lt;p&gt;Optimizely's support centre now labels the category as "Agent Platform (previously Opal)" and states that the rename to &lt;strong&gt;Optimizely Agent Platform&lt;/strong&gt; took effect on 1 September 2026, with &lt;strong&gt;Mark&lt;/strong&gt; (for Marketing) as the personification of the platform. At Opticon the brand was split into four parts:&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;Name&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Optimizely Agent Platform&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;The overall platform: agents, workflows, tools, skills, governance and the admin app.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Mark&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;The personified AI you talk to. In practice, this is what we used to call Opal Chat and the in-product assistant.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Mark AI / Mark-1&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;A family of post-trained, open-weight-based models built specifically for marketing tasks. Optimizely positions these as roughly 10x more cost-efficient than frontier models on the same marketing work. The press release states they are &lt;strong&gt;coming soon&lt;/strong&gt; to all Agent Platform customers, so treat these as announced rather than generally available.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Mark-IQ&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;The data and context layer the models draw on, including your experimentation history and web analytics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Mark-Bench&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;An open-source benchmark of 285 tasks across 15 marketing functions with more than 6,000 grading criteria.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;On Mark-Bench, it is worth being precise about the numbers, because two sets are in circulation. The Opticon press release reports Agent Platform at a 67% all-pass rate against 60% for Claude Code, at half the cost, on default configurations. Optimizely's own Camp Opticon write-up quotes 58% versus 52% at 2.1x cheaper. Both come from Optimizely running its own benchmark, so if you are using them in a business case, cite which figure and the configuration behind it.&lt;/p&gt;
&lt;h3&gt;The docs are mid-migration&lt;/h3&gt;
&lt;p&gt;As of today (early October 2026) the documentation still uses "Optimizely Opal" in nearly every page title, the release notes are still called "Optimizely Opal release notes", and the product UI in the doc screenshots still says Opal.&amp;nbsp;So you will see three names for the same thing depending on where you look. In this post I use &lt;strong&gt;Agent Platform&lt;/strong&gt; for the platform and &lt;strong&gt;Mark (Opal Chat)&lt;/strong&gt; for the chat experience, and I keep the documentation's feature names where they have not changed.&lt;/p&gt;
&lt;h3&gt;Other renames and retirements in 2026&lt;/h3&gt;
&lt;p&gt;The platform rename is not the only one. These are the changes I have seen trip people up:&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;When&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;What changed&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Impact&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;14 Jan 2026&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;The &lt;strong&gt;Connections&lt;/strong&gt; tab moved into &lt;strong&gt;Settings &amp;rarr; Product Connections&lt;/strong&gt;.&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Old guides point to the wrong place.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;14 Jan 2026&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;New tools and registries are &lt;strong&gt;disabled by default&lt;/strong&gt;; admins must enable them.&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;New system tools will not appear in agents until switched on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;5 Feb 2026&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Skills (then "Instructions") became &lt;strong&gt;inactive by default&lt;/strong&gt; on creation.&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;You must toggle &lt;strong&gt;Active&lt;/strong&gt; on.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;24 Feb 2026&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;"Threads" and "conversations" standardised to &lt;strong&gt;chats&lt;/strong&gt;.&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Cosmetic, but affects training material.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;18 Mar 2026&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;The &lt;strong&gt;creativity&lt;/strong&gt; (temperature) setting was removed from specialised agents because newer models no longer support it.&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Existing values are ignored, not errors.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;7 May 2026&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Instructions renamed to Skills&lt;/strong&gt;. Existing instructions now live under &lt;strong&gt;Context &amp;rarr; Skills&lt;/strong&gt;, with no behaviour change.&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;The biggest terminology change before the rebrand.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;7 May 2026&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;The 128-tool cap on dynamic tool lookups was removed.&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Bigger tool catalogues are now practical.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;4 Jun 2026&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;cms_geo_apply and cms_geo_analysis (CMS SaaS) were&amp;nbsp;&lt;strong&gt;decommissioned&lt;/strong&gt;, replaced by the general CMS content tools.&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Update any agents or workflows that reference them.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;2. How the platform processes a request&lt;/h2&gt;
&lt;p&gt;Before going into features, it helps to understand the runtime model, because almost every feature plugs into one of its stages. Each prompt goes through roughly the following pipeline:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prompt and intent extraction.&lt;/strong&gt; Mark works out what you are actually asking for.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skills.&lt;/strong&gt; Relevant skills are selected and injected into the system prompt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contextual intelligence.&lt;/strong&gt; Product data, content knowledge and workspace context are added.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool selection.&lt;/strong&gt; The right tools and agents are chosen for the job.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Input generation and validation.&lt;/strong&gt; Parameters are built and checked. If they are invalid, Mark asks you for clarification instead of calling the model, which saves credits.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LLM interaction.&lt;/strong&gt; Foundation models are called through business accounts, and your data is not used for training.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute and refine.&lt;/strong&gt; Tools and agents run, and the output is re-evaluated and iterated on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response.&lt;/strong&gt; The result comes back, often as a canvas or an action card.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/E5TkJugsaS9HVfUjUaaVK_how-opal-works-diagram.png?format=webp" alt="How Opal processes a request, from prompt to tailored response" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The way the docs describe the relationship between the building blocks is still the best mental model:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Skills&lt;/strong&gt; define &lt;em&gt;how&lt;/em&gt; the AI behaves and uses tools.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tools&lt;/strong&gt; provide &lt;em&gt;capabilities&lt;/em&gt;, such as reading data or taking action.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specialised agents&lt;/strong&gt; package tools and skills into one well-defined task.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workflow agents&lt;/strong&gt; chain agents together behind a trigger.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virtual Teammates&lt;/strong&gt; (new in August) sit on top as persistent, autonomous roles that own jobs over time.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Models and inference&lt;/h2&gt;
&lt;p&gt;This is the area that has changed most under the bonnet in 2026, and it has direct cost and quality implications.&lt;/p&gt;
&lt;h3&gt;Multi-model by design&lt;/h3&gt;
&lt;p&gt;The Agent Platform launched on Google Gemini and from&amp;nbsp;&lt;strong&gt;15 March 2026&lt;/strong&gt; Anthropic's Claude models (accessed via Google Vertex AI) joined the line-up, with &lt;strong&gt;Anthropic becoming a sub-processor&lt;/strong&gt; under the Optimizely DPA. If you are working with a client's legal or procurement team, that is the notice to point them to. Optimizely also states that the choice of model does not change how credits are consumed.&lt;/p&gt;
&lt;p&gt;On &lt;strong&gt;24 June 2026&lt;/strong&gt; model provider selection arrived at both the instance level and the specialised-agent level. Admins now choose a &lt;strong&gt;Default Model Provider&lt;/strong&gt; in &lt;strong&gt;Settings &amp;rarr; Chat&lt;/strong&gt;. The docs position Gemini as fast, cost-effective and good for creative writing, and Claude as stronger for complex tool orchestration and code generation. Specialised agents can override the instance default.&lt;/p&gt;
&lt;h3&gt;Inference levels&lt;/h3&gt;
&lt;p&gt;The inference level controls both the model and how much "thinking" it does before responding. &lt;strong&gt;Balanced&lt;/strong&gt; is the default.&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;Inference level&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Best for&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Google model&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Claude model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Quick&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Simple, fast tasks&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;gemini-3.5-flash-lite (medium thinking)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;claude-haiku-4-5 (medium thinking)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Standard&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Everyday tasks without deep reasoning&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;gemini-3.8-flash (low thinking)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;claude-sonnet-5 (no thinking)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Balanced&lt;/strong&gt; (default)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Everyday tasks that benefit from reasoning&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;gemini-3.8-flash (medium thinking)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;claude-sonnet-5 (high thinking)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Complex&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Multi-step planning and analysis&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;gemini-3.8-flash (high thinking)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;claude-sonnet-5 (x-high thinking)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Pro&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Nuanced evaluation and strategy&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;gemini-3.1-pro (medium thinking)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;claude-opus-5 (high thinking)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Code&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Code, plus anything needing maximum reasoning&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;gemini-3.1-pro (high thinking)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;claude-opus-5 (x-high thinking)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;em&gt;Model mapping as published in the &lt;a href="https://docs.optimizely.com/agent-platform/docs/inference-level"&gt;Inference level&lt;/a&gt; documentation (updated 2 October 2026).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The mapping is managed by Optimizely and changes without any action on your side. Two recent examples: on 14 August the Complex and Code levels were upgraded to claude-opus-5, and on 13 September the Standard, Balanced and Complex levels moved to Gemini 3.8 Flash. Note that the current table shows Complex on claude-sonnet-5 with x-high thinking rather than Opus, so the August note appears to have been superseded. &lt;strong&gt;Treat the inference-level page as the source of truth, not the release notes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Admins set the organisation-wide default for chat; agent creators set a level per specialised agent that overrides it.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: 368px;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/ishgeJd8GwqaqGXYYFgF3_inference-level.png?format=webp" alt="Setting the default inference level in Opal settings" width="860" /&gt;&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Sub-agents (released 17 September)&lt;/h3&gt;
&lt;p&gt;This is one of the most architecturally interesting releases of the year, and it has gone a bit under the radar. When chat (or a specialised agent) hits a self-contained sub-task, it now hands it off to a &lt;strong&gt;sub-agent&lt;/strong&gt; that runs in its own isolated context and returns only the final result. Intermediate tool calls and documents never enter your conversation.&lt;/p&gt;
&lt;p&gt;The motivation is classic context engineering: avoiding context bloat (re-reading huge tool outputs every turn) and split attention (one model juggling unrelated objectives).&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;Tier&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Inference level&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Extended thinking&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Typical use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Mini&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Quick&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;No&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Extraction, classification, reformatting&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Fast&lt;/strong&gt; (default)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Balanced&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Yes&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;General delegation needing judgement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Deep&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Pro&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Yes, with a higher token budget&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Sustained multi-step reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The key technical behaviours:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parallel fan-out.&lt;/strong&gt; Independent sub-tasks are dispatched concurrently; dependent ones run in sequence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-turn continuation.&lt;/strong&gt; A sub-agent can be resumed with its context intact.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No recursion.&lt;/strong&gt; Delegation tools are stripped from sub-agents, so there are no unbounded chains.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Per-turn dispatch budget.&lt;/strong&gt; Individual calls and fan-out slots share one cap per turn.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instance default provider.&lt;/strong&gt; Sub-agents never override the configured model provider.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Zero configuration.&lt;/strong&gt; They are installed and managed by Optimizely, cannot be edited or deleted, and do not appear in the Agent Directory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The practical gotcha is in your logs. One chat message can now produce several rows in &lt;strong&gt;Agent Activity Logs&lt;/strong&gt; (search for "sub"), each with its own credit total, attributed to the user who started the chat, with the model provider shown as &lt;strong&gt;Instance Default&lt;/strong&gt;. Rows sharing the same execution time indicate fan-out. If a client asks why one question produced five log entries, this is why, and Optimizely's position is that it is the same work routed more cheaply rather than extra work.&lt;/p&gt;
&lt;p&gt;&lt;img src="/link/172d765d80e040ed85c50dc98299aba6.aspx" width="1271" height="445" /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. The chat experience (Mark / Opal Chat)&lt;/h2&gt;
&lt;p&gt;Chat is still where most users meet the platform. It is available in the global navigation across Optimizely One and at &lt;a href="https://opal.optimizely.com"&gt;opal.optimizely.com&lt;/a&gt;. (likely to be changed soon)&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img src="/link/605e7e540ce44ee1b0d59f5c0f7ed5d7.aspx" width="1061" height="291" /&gt;&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img src="/link/c03135f0b4bf4c3da60a929b9d0bd0d3.aspx" width="1062" height="391" /&gt;&lt;/p&gt;
&lt;h3&gt;What's new in chat in 2026&lt;/h3&gt;
&lt;p&gt;The chat has matured from a simple prompt box into a proper agent console. In rough chronological order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;January.&lt;/strong&gt; CSV uploads, Markdown in the input box, a toggle for the history panel, and dark mode and font-size settings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;February.&lt;/strong&gt; Upload of .xlsx and .docx files, and an immediate "thinking&amp;hellip;" indicator.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;March.&lt;/strong&gt; &lt;strong&gt;Skill mentions&lt;/strong&gt; (type &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;/&lt;/span&gt; to apply a skill) and "save this as a skill" directly from a conversation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;April.&lt;/strong&gt; &lt;strong&gt;Message queuing&lt;/strong&gt; (keep typing while Mark works), pinned conversations, recency-sorted history and undo for pasted content.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;May.&lt;/strong&gt; &lt;strong&gt;Chat modes&lt;/strong&gt; to match output depth to the task, &lt;strong&gt;action cards&lt;/strong&gt; (rich, interactive inline components instead of plain text), &lt;strong&gt;chat pills&lt;/strong&gt; (one-click starter prompts), &lt;strong&gt;memory&lt;/strong&gt;, and &lt;strong&gt;multi-turn mode&lt;/strong&gt; for specialised agents.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;June.&lt;/strong&gt; A live &lt;strong&gt;activity stream&lt;/strong&gt; with inline tool status and "context pickup pills" showing what context was pulled in, Escape to stop a response, PowerPoint and Word generation (via the &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;code_execution&lt;/span&gt; tool) and an &lt;strong&gt;image editor&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;June 30.&lt;/strong&gt; The &lt;strong&gt;Agent Library&lt;/strong&gt; on the chat homepage and &lt;strong&gt;Personalize Opal&lt;/strong&gt;, a pinned onboarding agent that walks new users through connecting tools and building skills.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;July.&lt;/strong&gt; &lt;strong&gt;Conversation compaction&lt;/strong&gt; (automatic and manual checkpoints for long chats), &lt;strong&gt;editing sent messages&lt;/strong&gt;, rotating contextual tips, &lt;strong&gt;Agent Builder&lt;/strong&gt; and &lt;strong&gt;Skill Builder&lt;/strong&gt; inside chat, and &lt;strong&gt;Safe URL Browsing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;September.&lt;/strong&gt; &lt;strong&gt;RAG for artifacts&lt;/strong&gt;, so chat can draw on your own canvases.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For architects, compaction and sub-agents together are the important pair: they are Optimizely's answer to long-running, tool-heavy conversations getting slower, dumber and more expensive over time.&lt;/p&gt;
&lt;h3&gt;Chat everywhere: in-product and third-party surfaces&lt;/h3&gt;
&lt;p&gt;Chat is embedded across the stack, and that list keeps growing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CMS 13.&lt;/strong&gt; The Optimizely.Cms.OpalChat adds Opal to the platform under verson 13.2.0 but 13.2.0 and onwards have Mark chat is built directly in to the CMS (requires DXP and Opti ID)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CMS 12&lt;/strong&gt; (since October 2025, requires DXP and Opti ID), &lt;strong&gt;CMS SaaS&lt;/strong&gt;, &lt;strong&gt;Commerce Connect&lt;/strong&gt; (since September 2025), &lt;strong&gt;Configured Commerce&lt;/strong&gt;, &lt;strong&gt;PIM&lt;/strong&gt; (January 2026), &lt;strong&gt;CMP&lt;/strong&gt;, &lt;strong&gt;Web and Feature Experimentation&lt;/strong&gt;, &lt;strong&gt;Personalization&lt;/strong&gt;, &lt;strong&gt;Analytics&lt;/strong&gt;, &lt;strong&gt;ODP&lt;/strong&gt; and &lt;strong&gt;Product Recommendations&lt;/strong&gt; (beta from May 2026).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slack.&lt;/strong&gt; Now a verified app in the Slack Marketplace (February 2026).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microsoft Copilot&lt;/strong&gt; and &lt;strong&gt;Google Agentspace / Gemini Enterprise&lt;/strong&gt;, the latter using the Agent-to-Agent (A2A) protocol for cross-vendor agent collaboration (both since September 2025).&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. The context layer: Skills, Brands, Memory, Personal files and RAG&lt;/h2&gt;
&lt;p&gt;If tools are what the platform can &lt;em&gt;do&lt;/em&gt;, this layer is what it &lt;em&gt;knows&lt;/em&gt;. It is also the part that has evolved the most conceptually this year, and it effectively forms the user-facing side of what Optimizely now calls Mark-IQ.&lt;/p&gt;
&lt;h3&gt;Skills (formerly Instructions)&lt;/h3&gt;
&lt;p&gt;Skills are the reusable rules, context and behavioural guidelines that shape output, such as brand voice, product taxonomies, personas, compliance language or output formats. They come in two scopes: &lt;strong&gt;organisation skills&lt;/strong&gt; (admin-managed) and &lt;strong&gt;personal skills&lt;/strong&gt; (user-managed, since 30 March).&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: 1110px;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/l_stRVPc54VVzstPTlMGQ_components-of-a-skill.png?format=webp" alt="The components of a skill" width="752" /&gt;&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Each skill has a name, watchers, version history, an active flag, a &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;/shortcut&lt;/span&gt;, a scope, a &lt;strong&gt;When to use&lt;/strong&gt; activation trigger, the &lt;strong&gt;core skill&lt;/strong&gt; body (rich text or Markdown), attached reference files, and a &lt;strong&gt;Where to use&lt;/strong&gt; setting to scope it to a specific product instance.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/FmfPTLEdueHxalNaUtjnp_core-skill.png?format=webp" alt="Editing the core skill in Markdown or rich text" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How selection actually works&lt;/strong&gt; matters a lot when you are designing a skills library:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;All active skills available to the user are gathered.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool-triggered skills&lt;/strong&gt; are applied automatically whenever their tools are in play, with no further evaluation.&lt;/li&gt;
&lt;li&gt;The remaining skills are judged by a relevancy model that reads your latest message, the conversation history and each skill's &lt;strong&gt;Keywords or phrases&lt;/strong&gt; field.&lt;/li&gt;
&lt;li&gt;Matched skills are added to the system prompt.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Two consequences are easy to miss. First, the relevancy model looks &lt;strong&gt;only&lt;/strong&gt; at the &lt;em&gt;When to use&lt;/em&gt; field and ignores the skill's name and body during selection. Second, it is deliberately &lt;strong&gt;inclusive&lt;/strong&gt;: it would rather include a borderline skill than miss a relevant one, and it matches semantically. A skill about "marketing promotions" can fire for a prompt about strawberries on sale. The fix is to write tight activation criteria, using "ONLY use for&amp;hellip;", explicit "Do NOT use for&amp;hellip;" exclusions, synonyms for narrow concepts, and mutually exclusive boundaries between related skills. Leaving &lt;em&gt;When to use&lt;/em&gt; blank makes a skill shortcut-only.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/pe0Ve4qJMd5M5dXpWor55_tool-skills.png?format=webp" alt="Configuring a tool-triggered skill" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The skills toolchain has also grown up considerably:&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;When&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;5 Feb&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Status column added; skills inactive by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;30 Mar&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Personal skills; create skills from a chat; &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;/&lt;/span&gt; skill mentions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;14 Apr&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Redesigned &lt;strong&gt;Activation Trigger&lt;/strong&gt; section; better disambiguation when editing from chat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;7 May&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Instructions &amp;rarr; Skills&lt;/strong&gt; rename&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;26 May&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;File-explorer UI for skill files; PPTX/DOCX support in skills (with code execution)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;27 May&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Skills section in specialised agents, with a &lt;strong&gt;Choose skills&lt;/strong&gt; picker to insert skills at exact points in a prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;24 Jun&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Skill watching; &lt;strong&gt;import/export&lt;/strong&gt; across instances; scope switcher between personal and organisation; skill preview in the agent editor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;30 Jun / 29 Jul&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Skills builder&lt;/strong&gt; GA, then Skill Builder inside chat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Brands (25 August) - Not fully out yet&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Brands&lt;/strong&gt; are a structured record of a brand's assets, colours, typefaces and usage rules, managed under &lt;strong&gt;Context &amp;rarr; Brand&lt;/strong&gt;. The clever bit architecturally is that &lt;strong&gt;publishing a brand turns it into a skill&lt;/strong&gt;, so you get a single source of truth that image generation, decks, documents and social images all read from, instead of each feature holding its own copy. Brands move through Draft, Published and Default states, and an organisation can hold several brands with one default. Marketers can also bootstrap a brand on the fly when none exists.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/u99QJ0i969KoCcnlVnKgv_brand-overview.png?format=webp" alt="The Brand page under Context" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;For multi-brand clients this is a significant change. Previously you had to emulate this with a pile of brand-voice skills.&lt;/p&gt;
&lt;h3&gt;Memory (7 May) and Personal files (21 September)&lt;/h3&gt;
&lt;p&gt;Memory and personal files are now one system, and the implementation will look very familiar to anyone who has worked with coding agents: &lt;strong&gt;it is file-based.&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Personal files&lt;/strong&gt; are a persistent, per-user document store under &lt;strong&gt;Context &amp;rarr; Personal Files&lt;/strong&gt;, with a browser-based editor for text and Markdown files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Memory&lt;/strong&gt; lives in that same store. &lt;strong&gt;FACTS.md&lt;/strong&gt; holds short durable facts, named notes files hold broader context, and &lt;strong&gt;RECENTS.md&lt;/strong&gt; is a running summary of recent conversations and agent activity that Mark maintains. &lt;strong&gt;MEMORY.md&lt;/strong&gt; is generated by Mark and is read-only.&lt;/li&gt;
&lt;li&gt;Memories are captured either automatically or on request ("remember that&amp;hellip;").&lt;/li&gt;
&lt;li&gt;When &lt;strong&gt;Enable personal context&lt;/strong&gt; is turned on for a specialised agent, context files such as &lt;strong&gt;AGENTS.md&lt;/strong&gt; and &lt;strong&gt;MEMORY.md&lt;/strong&gt; are made available to the agent before it starts. You can restrict the agent to a &lt;strong&gt;working directory&lt;/strong&gt; (for example &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;/context/agents/support&lt;/span&gt;) rather than exposing the whole store.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/T3Mw2AkhKTW61HyS_Wt2p_view-memories.png?format=webp" alt="Viewing memory files in Personal Files" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/6TlhobVIXskXcKoj725RO_pesonal-files.png?format=webp" alt="The Personal Files editor" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The distinction the docs draw is useful when you explain it to clients: &lt;strong&gt;skills capture how work gets done; memory captures who you are and what you are working on.&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;Retrieval-augmented generation (RAG)&lt;/h3&gt;
&lt;p&gt;RAG has expanded in three steps this year:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;7 May. RAG for CMP.&lt;/strong&gt; Permission-aware semantic and keyword retrieval across campaigns, tasks, assets, work requests and briefs, exposed as &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;rag_retrieve_from_cmp&lt;/span&gt; (structured) and &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;rag_retrieve_from_cmp_as_csv&lt;/span&gt; (tabular). Alongside it came &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;rag_retrieve_from_optimizely_graph&lt;/span&gt; for CMS and CMP content via &lt;strong&gt;Optimizely Graph&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;24 June.&lt;/strong&gt; Folder and path filtering for CMP assets, &lt;strong&gt;relevancy explanations&lt;/strong&gt; for suggested assets, and &lt;strong&gt;EU-region RAG&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;17 September. RAG for artifacts.&lt;/strong&gt; Chat can now search your own canvases, respecting artifact permissions and including artifacts shared with you.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Retrieval, embedding and indexing all happen inside Optimizely's infrastructure, and every query respects the user's roles and permissions. Admins can turn RAG on or off for direct chat under &lt;strong&gt;Settings &amp;rarr; RAG Configuration&lt;/strong&gt;, but note that &lt;strong&gt;agents can use RAG regardless of that setting&lt;/strong&gt;, which is important if you are relying on the toggle for governance.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: 386px;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/2r8-QfX7E328za8VmjsrV_enable-rag-in-chat.png?format=webp" alt="The Enable RAG in Chat setting" width="1062" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This is exactly where the CMS 13 upgrade story from my last post comes back in: &lt;strong&gt;Graph is the bridge&lt;/strong&gt; that gets CMS and Commerce content into the agent platform's retrieval layer.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Tools: system, connector, remote MCP and custom&lt;/h2&gt;
&lt;p&gt;Tools are the actions. There are three families, and the year's big story is the arrival of &lt;strong&gt;MCP&lt;/strong&gt; as a first-class citizen.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: 1172px;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/w0geSGrNPUffwHrKbpJC3_tools-tab-numbered-list.png?format=webp" alt="The Tools tab, with registries, filters and chat enablement" width="903" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Tools area is now split into four tabs: &lt;strong&gt;Connectors&lt;/strong&gt; (each user links their own third-party accounts), &lt;strong&gt;Tools&lt;/strong&gt;, &lt;strong&gt;Registries&lt;/strong&gt; and &lt;strong&gt;External Providers&lt;/strong&gt; (remote MCP servers and auth providers). You can filter tools by type (&lt;strong&gt;Remote MCP&lt;/strong&gt; or &lt;strong&gt;Opal Tools&lt;/strong&gt;), control per registry whether tools are active and whether they are enabled in chat, and restore chat defaults. Note that agents started from chat can use a registry's tools even when that registry is disabled for chat.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: 510px;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/C5D5n7xk_DuEjYKRPyp7l_registeries-tab-numbered-list.png?format=webp" alt="The Registries tab with discovery URLs and sync" width="1053" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;System tools for Optimizely products&lt;/h3&gt;
&lt;p&gt;Optimizely-maintained tools now cover practically every product. The ones that matter most for CMS and Commerce architects:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;CMS 13 (PaaS)&lt;/strong&gt;, which arrived from 22 May:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Content types:&lt;/strong&gt; &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_list_content_types&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_get_content_type_details&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_create_content_type&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_update_content_type&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_delete_content_type&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Property groups:&lt;/strong&gt; &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_list_property_groups&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_create_property_group&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Content:&lt;/strong&gt; &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_get_content_data&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_update_content_item&lt;/span&gt; (creates a draft if no key is given), &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_delete_content_item&lt;/span&gt; (moves to the waste basket), &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_publish_content_item&lt;/span&gt; (immediate or scheduled), &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_create_content_variation&lt;/span&gt; (locale variations)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Preview:&lt;/strong&gt; &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_get_content_preview_url&lt;/span&gt;, which lets the agent fetch and analyse rendered HTML&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEO and GEO:&lt;/strong&gt; &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_seo_analysis&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_seo_edit&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_geo_analysis&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_geo_apply&lt;/span&gt; (writes JSON-LD into a &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;JsonLdTemplates&lt;/span&gt; property as a draft)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Display templates and media&lt;/strong&gt; (29 July): &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_list_display_templates&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_get_display_template&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_create_display_template&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_update_display_template&lt;/span&gt;, and &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;paas_cms_upload_media&lt;/span&gt; (pulls a file from a public URL into CMS media)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that the GEO tools were &lt;strong&gt;removed from CMS SaaS&lt;/strong&gt; in June but &lt;strong&gt;exist for CMS 13 PaaS&lt;/strong&gt;. Do not assume parity between the two tool sets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Optimizely Graph&lt;/strong&gt; (CMS SaaS, CMS 12 and CMS 13): &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;graph_content_type_schema&lt;/span&gt;, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;graph_content_graphql_executor&lt;/span&gt; and &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;graph_content_search_tool&lt;/span&gt; (January), plus &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;graph_pinned_result&lt;/span&gt; and &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;graph_synonyms&lt;/span&gt; (14 August), so agents can now &lt;strong&gt;tune search relevance&lt;/strong&gt;, not just query it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Commerce:&lt;/strong&gt; Commerce Connect became a &lt;strong&gt;product connection&lt;/strong&gt; on 13 March, alongside the &lt;strong&gt;Product Promotion&lt;/strong&gt; agent for Commerce Connect; Configured Commerce has system tools and a &lt;strong&gt;Restriction Group Creation&lt;/strong&gt; agent; and Product Recommendations has a growing &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;pr_*&lt;/span&gt; tool set for category, search, trend and algorithm performance reports and page management.&lt;/p&gt;
&lt;p&gt;Beyond that there are tool sets for CMP (work requests, campaigns, milestones, structured content, asset updates), Experimentation and Personalization (including ten &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;exp_ve_*&lt;/span&gt; Visual Editor DOM tools that power the AI variation development agent), Analytics, ODP, Campaign, PIM and Content Recommendations.&lt;/p&gt;
&lt;h3&gt;Connector tools and remote MCP&lt;/h3&gt;
&lt;p&gt;The connector catalogue exploded this year. Highlights include Microsoft 365 (Teams, Outlook, and from 18 September OneNote and &lt;strong&gt;sensitivity labels&lt;/strong&gt; on OneDrive and SharePoint), Google Workspace (Docs, Sheets, Slides, Calendar and Gmail), Google Ads, Google Search Console, GA4, LinkedIn Ads, OpenAI Ads, Adobe Analytics, Salesforce CRM, Amplitude, Mixpanel, WordPress, Zoom, Crunchbase and Figma.&lt;/p&gt;
&lt;p&gt;Remote MCP arrived in stages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;30 March.&lt;/strong&gt; Remote MCP connector tools (starting with Atlassian and Conductor), with &lt;strong&gt;user-level authentication&lt;/strong&gt;: the admin enables the server for the organisation, and each user authenticates their own account so actions carry their own identity and permissions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;7 May.&lt;/strong&gt; Notion, Contentsquare, Contentful, Gamma, GitHub, Sanity and ClickUp.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;29 July.&lt;/strong&gt; &lt;strong&gt;Bring your own remote MCP server&lt;/strong&gt;, plus HubSpot and ZoomInfo.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;18 September.&lt;/strong&gt; Braze and Fullstory, and &lt;strong&gt;Contentful Global and EU endpoints&lt;/strong&gt; for data residency.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;User-level authentication is the most important architectural point here. It gives you proper action attribution and per-user data scoping for third-party systems, rather than a single shared service account.&lt;/p&gt;
&lt;h3&gt;Custom tools&lt;/h3&gt;
&lt;p&gt;When nothing off the shelf fits, you build a custom tool. The contract is straightforward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;tool manifest&lt;/strong&gt; describing name, description, typed parameters, the execution endpoint and auth requirements.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;discovery endpoint&lt;/strong&gt; (typically &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;/discovery&lt;/span&gt;) that returns the manifest. If it is unreachable or malformed, the tools simply will not appear.&lt;/li&gt;
&lt;li&gt;An &lt;strong&gt;execution endpoint&lt;/strong&gt;, typically a POST with a JSON body returning JSON.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authentication.&lt;/strong&gt; Notably, the docs state that &lt;strong&gt;only Opti ID is supported for tool authentication&lt;/strong&gt; at the moment, with other providers in the works.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since 1 April, parameters support &lt;strong&gt;nested schemas&lt;/strong&gt; (objects within objects, arrays of objects and enums), which makes richer integrations far less awkward. Tools are hosted either on &lt;strong&gt;Optimizely Connect Platform (OCP)&lt;/strong&gt; or anywhere publicly reachable, and they are registered as a registry with a discovery URL that you can re-sync.&lt;/p&gt;
&lt;p&gt;Official SDKs:&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;Language&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Install&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;TypeScript / JavaScript&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;npm install @optimizely-opal/opal-tools-sdk&lt;/span&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Express middleware, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;@tool()&lt;/span&gt; decorators, parameter validation, auth helpers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Python&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;pip install optimizely-opal.opal-tools-sdk&lt;/span&gt; (import as &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;opal_tools_sdk&lt;/span&gt;)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;FastAPI integration, auto &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;/discovery&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;C# / .NET&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;See note below&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;ASP.NET Core integration, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;[Tool]&lt;/span&gt; and &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;[RequiresAuth]&lt;/span&gt; attributes, DI support&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A small but real gotcha for .NET teams: the docs currently show the install command as &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;Install-Package OptimizelyOpal.OpalToolsSDK&lt;/span&gt;, but link to a NuGet listing named &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;Optimizely.Opal.Tools&lt;/span&gt;. Check NuGet for the current package ID before you pin it in a project.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Agents&lt;/h2&gt;
&lt;h3&gt;The four kinds of agent&lt;/h3&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;Type&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Who builds it&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Agent Directory agents&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Optimizely&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Prebuilt, installable agents. There are now 70+ listed in the credits reference alone. Since &lt;strong&gt;25 August they auto-update&lt;/strong&gt; when Optimizely publishes a new version (on by default).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Agent Library&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Optimizely (curated)&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;A browsable launcher on the chat homepage with more than 45 agents runnable in one click, without installation (30 June).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Specialised agents&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;You&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;One well-defined task, with your prompt template, tools, skills, variables, model provider and inference level.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Workflow agents&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;You&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Multi-step automations of triggers, logic and agents. Still described in the docs as &lt;strong&gt;private GA and not available on all instances&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: 572px;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/riQIZv2U-uUAyVJt84u0R_agents-reusable-content-block.png?format=webp" alt="The Agents page in the Opal app" width="1208" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Directory agents worth knowing as a CMS or Commerce architect&lt;/h3&gt;
&lt;p&gt;The Directory has shifted from generic content helpers towards agents that act directly on the platform:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CMS 13 (PaaS):&lt;/strong&gt; Content Model Creation, Page Builder, Content Analysis, GEO Schema Optimization and SEO Metadata Implementation. The Content Analysis agent produces a Markdown instruction set for a content type that the Page Builder agent then uses, which is a neat pattern for on-model content generation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CMS SaaS&lt;/strong&gt; equivalents, plus Content Refresh Analysis and FAQ Creation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commerce:&lt;/strong&gt; Product Promotion (Commerce Connect) and Restriction Group Creation (Configured Commerce).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Experimentation:&lt;/strong&gt; Experiment Conflict Checker, Backlog Prioritization (PIE scoring), Value Estimator, Program Overview, Program Health Review, Feature Experimentation Governance, and the &lt;strong&gt;Feature Flag Implementation&lt;/strong&gt; agent, which outputs a portable &lt;strong&gt;SKILL.md&lt;/strong&gt; for implementing a flag in your codebase. That is a nice nod towards developer agent tooling.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI search and AEO:&lt;/strong&gt; GEO Auditor, the Conductor AEO Gap Finder, AI Brand Visibility and Competitive Share of Voice agents, Profound Citation Gap Analysis, and Agent Visibility Analytics Insights.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Specialised agents: what's new&lt;/h3&gt;
&lt;p&gt;The agent editor has quietly become quite sophisticated over 2026:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Versioning&lt;/strong&gt; with view, restore and branch-from-version (January).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prompt template shortcuts&lt;/strong&gt; for inserting variables, tools and skills inline; &lt;strong&gt;case-insensitive, unique variables&lt;/strong&gt;; &lt;strong&gt;array/list inputs&lt;/strong&gt; (May).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Example shots&lt;/strong&gt; in the prompt template (February) and &lt;strong&gt;preferred output examples&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JSON export and import&lt;/strong&gt; for moving agents between instances (April).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-turn interaction mode&lt;/strong&gt; in chat (May). Note that specialised agents always run single-turn inside workflows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Model provider and inference level per agent&lt;/strong&gt; (June).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sharing and visibility&lt;/strong&gt; (30 June): private, named people or the whole organisation, with view, run, edit and delete permission levels. New agents default to &lt;strong&gt;private&lt;/strong&gt; where the feature is enabled, and it is not available on all tiers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent Builder&lt;/strong&gt; in chat (29 July), so agents can be created and iterated on conversationally.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personal context and working directory&lt;/strong&gt; settings (September).&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;60-minute timeout&lt;/strong&gt; (since November 2025) to stop runaway executions burning credits.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Workflow agents: what's new&lt;/h3&gt;
&lt;p&gt;Workflow agents are made up of &lt;strong&gt;triggers&lt;/strong&gt; (chat, schedule, webhook and email), &lt;strong&gt;logic&lt;/strong&gt; (conditions, loops and code) and &lt;strong&gt;agents&lt;/strong&gt;.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/qRVER8BLyJNTg0gYmDQkh_review-workflow-components.png?format=webp" alt="The structure of a workflow agent" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;This has been the busiest area for releases:&lt;/p&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;When&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Change&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Jan&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Add a trigger first when building&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Mar&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Builder-first creation flow; &lt;strong&gt;multi-branch conditions&lt;/strong&gt; in a single node; email trigger improvements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Apr&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Webhook trigger redesign with product instance selection and default CMP auth headers; &lt;strong&gt;parameter generation logs&lt;/strong&gt;; helper messages for chat triggers; JSON download and copy; case-sensitive conditions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;May&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Workflow &lt;strong&gt;import/export&lt;/strong&gt;; loop validation; schedule start time and &lt;strong&gt;no end date&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Jun&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Execution-log export to CSV/Excel with date ranges&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;29 Jul&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Nested workflows&lt;/strong&gt;; &lt;strong&gt;static versus predicted parameters&lt;/strong&gt; (lock a variable or let the LLM fill it); &lt;strong&gt;Agent Update Warning&lt;/strong&gt; before editing an agent used by a workflow; &lt;strong&gt;Version History&lt;/strong&gt; for workflows; a new &lt;strong&gt;Execution Status Taxonomy&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;17 Sep&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Code step&lt;/strong&gt; (Python, JavaScript or Bash, with no external service needed); &lt;strong&gt;Run iterations in parallel&lt;/strong&gt; on loops; &lt;strong&gt;trigger priority&lt;/strong&gt; (Standard versus Background) so batch runs do not delay interactive ones&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src="/link/016a9f6001bc4762a3718cf3cb8653a2.aspx" width="696" height="1110" /&gt;&lt;/p&gt;
&lt;p&gt;The Code step is a big deal. Previously, any deterministic transformation, such as parsing, maths or reshaping JSON between agents, meant either burning an LLM call or standing up a custom tool. Now it can live inline in the workflow. Together with nested workflows and static parameters, workflow agents are becoming a reasonable low-code orchestration layer rather than just a chain of prompts.&lt;/p&gt;
&lt;p&gt;A credit note: a workflow's cost is &lt;strong&gt;the sum of the specialised agents it invokes, with no overhead for the workflow itself&lt;/strong&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Virtual Teammates and Team Messages&lt;/h2&gt;
&lt;p&gt;This was the headline Opticon launch, released on &lt;strong&gt;25 August (Not availible to everyone yet, especially partners)&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;What a Virtual Teammate is&lt;/h3&gt;
&lt;p&gt;A Virtual Teammate is a persistent AI worker built from the same components you already have, namely &lt;strong&gt;tools&lt;/strong&gt;, &lt;strong&gt;agents and workflows&lt;/strong&gt;, and &lt;strong&gt;jobs&lt;/strong&gt; (workflows it runs on its own, on a schedule or on events), wrapped in a persistent identity with memory, a persona and a defined role. The distinction from an agent is &lt;strong&gt;continuity&lt;/strong&gt;: an agent runs when you invoke it, whereas a teammate keeps working, holds context, improves from feedback and collaborates with other teammates.&lt;/p&gt;
&lt;p&gt;The launch roster:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Chief of Staff:&lt;/strong&gt; daily and weekly briefs, meeting prep and recaps, and competitive intelligence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Marketing Analyst:&lt;/strong&gt; scheduled, decision-ready GA4 reports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SEO &amp;amp; AI Search Analyst:&lt;/strong&gt; reasons across Search Console, GA4, Conductor and AI Visibility Analytics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRO Manager:&lt;/strong&gt; monitors behaviour, analyses competitor UX and &lt;strong&gt;stages experiment configurations&lt;/strong&gt; in Optimizely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Personalization Strategist:&lt;/strong&gt; builds and runs personalisation campaigns end to end.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Identity and access: the architect's checklist&lt;/h3&gt;
&lt;p&gt;This is the bit to get right in any implementation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Each teammate gets &lt;strong&gt;its own Opti ID account&lt;/strong&gt; (its own login and email, flagged as a virtual user). It shows up alongside people and fits into existing access and audit processes, and &lt;strong&gt;its usage is tracked separately from human usage&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It &lt;strong&gt;does not consume a product seat&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;It gets &lt;strong&gt;only the access you grant it&lt;/strong&gt;, with nothing inherited automatically from the person who hired it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inside Optimizely products it acts as itself. Outside Optimizely it acts through accounts a human connects on its behalf&lt;/strong&gt;, usually the person who hired it. It cannot authorise third-party connections on its own.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;CMS 13 and CMS SaaS&lt;/strong&gt; there is an extra step: after granting the teammate a role in the Opti ID Admin Center, you must also add it to the relevant content under &lt;strong&gt;Settings &amp;rarr; Set Access Rights&lt;/strong&gt; in CMS, with only the rights its jobs need.&lt;/li&gt;
&lt;/ul&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: 439px;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/rTp0e_qht88QPTmqoQQ70_access-rights.png?format=webp" alt="Granting a Virtual Teammate access rights in CMS" width="908" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;That last point is another reason &lt;strong&gt;Opti ID is non-negotiable in CMS 13&lt;/strong&gt;: Virtual Teammates are first-class principals in the identity layer, so the same governance you apply to people applies to them.&lt;/p&gt;
&lt;h3&gt;Autonomy controls&lt;/h3&gt;
&lt;p&gt;By default a teammate &lt;strong&gt;drafts and proposes, and a human approves&lt;/strong&gt; via Team Messages. As confidence builds you can allow specific actions to run automatically within boundaries you set, and you configure its persona, values, who it may talk to, and which tools it can read, write or run. Admins enable teammates for the organisation and can pause or deactivate them; users and agent builders hire and configure their own. You can even switch identity to work in the platform &lt;em&gt;as&lt;/em&gt; the teammate.&lt;/p&gt;
&lt;h3&gt;Team Messages&lt;/h3&gt;
&lt;p&gt;Team Messages is the real-time collaboration layer for people, teammates and agents, offering direct messages, group chats and channels, with sharing of files, agents and artifacts, mentions and persistent history. It follows you across products (an unread badge appears on the IM icon in CMP, Experimentation and elsewhere), and teammates also join meetings and share transcript summaries in Teams and Slack.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/Nf41dmYPbtQnknCTcpzNT_team-messages.png?format=webp" alt="Team Messages" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Alongside it sit the &lt;strong&gt;Tracker&lt;/strong&gt; and &lt;strong&gt;Notifications&lt;/strong&gt; (in-app tray, Notification Center, browser notifications since May, and skill-update and guardrail notification categories since June).&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Quality, safety and governance&lt;/h2&gt;
&lt;p&gt;If Virtual Teammates are the accelerator, this is the brake, and it is what makes autonomous operation defensible to a client's risk team.&lt;/p&gt;
&lt;h3&gt;Input guardrails (29 July)&lt;/h3&gt;
&lt;p&gt;You define rules in plain language about what users may ask, and the platform &lt;strong&gt;trains a classifier&lt;/strong&gt; to enforce them. This is useful for keeping usage on-topic and compliant without hand-maintaining block lists.&lt;/p&gt;
&lt;p&gt;&lt;img src="/link/9602487681be43c885477790206c12d5.aspx" width="925" height="755" /&gt;&lt;/p&gt;
&lt;h3&gt;The Quality tab for specialised agents (1 June)&lt;/h3&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/VU6IVwwlUlIrd7XwYC9En_quality-tab-overview.png?format=webp" alt="The Quality tab for a specialised agent" /&gt;&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The Quality tab gives you three mechanisms:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Output Evaluation.&lt;/strong&gt; An AI judge scores every run against &lt;strong&gt;up to 10 criteria&lt;/strong&gt;, a &lt;strong&gt;baseline score between 75% and 95%&lt;/strong&gt; (in 5% steps) and &lt;strong&gt;up to five examples&lt;/strong&gt;. This builds on the preferred-output examples from December 2025 and the ability (January 2026) to promote past runs to evals.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution Guardrails.&lt;/strong&gt; These learn what "normal" looks like for an agent and progress through &lt;strong&gt;Learning &amp;rarr; Watching &amp;rarr; Enforcing&lt;/strong&gt; as successful runs accumulate. You control sensitivity and whether flagged runs are stopped automatically.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution Advisor.&lt;/strong&gt; This has no configuration screen; it acts on what the other two detect. It &lt;strong&gt;recovers flagged runs&lt;/strong&gt; with corrective guidance, &lt;strong&gt;retries failed tool calls&lt;/strong&gt; with adjusted input, and &lt;strong&gt;reviews sensitive tool calls&lt;/strong&gt; (email, publish, delete) with inline &lt;strong&gt;Approved&lt;/strong&gt; or &lt;strong&gt;Blocked&lt;/strong&gt; verdicts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each run therefore carries two independent verdicts, an &lt;strong&gt;Evaluation Score&lt;/strong&gt; and a &lt;strong&gt;Guardrail Status&lt;/strong&gt;, and a run can pass one and fail the other. The docs suggest starting with Output Evaluation and adding guardrails once behaviour has stabilised, which matches how I would roll it out.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/JvGh28VWtIFjb4mIDrVNQ_access-the-quality-tab-4.png?format=webp" alt="Accessing Output Evaluation and Execution Guardrails" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;h3&gt;Safe URL Browsing (29 July)&lt;/h3&gt;
&lt;p&gt;Every URL is screened before the platform browses it. &lt;strong&gt;Standard&lt;/strong&gt; (always on) checks Google Web Risk for malware, social engineering and unwanted software. &lt;strong&gt;Advanced&lt;/strong&gt; (opt-in per instance under &lt;strong&gt;Settings &amp;rarr; Chat&lt;/strong&gt;) adds a &lt;strong&gt;Cloudflare Radar deep scan&lt;/strong&gt; in parallel, which can add up to 30 seconds of latency for URLs Cloudflare has not seen recently. Only &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;http&lt;/span&gt; and &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;https&lt;/span&gt; are accepted, &lt;strong&gt;redirect targets are screened too&lt;/strong&gt; (which closes the open-redirect bypass), and &lt;strong&gt;links in emails processed by email triggers are also checked&lt;/strong&gt;. Blocked requests return no partial content, only the threat category.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/8iZ7543ngHYoQ2I-3fiyJ_advanced-url-safety-checks.png?format=webp" alt="Advanced URL safety checks setting" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If a client's firewall blocks the platform from scanning their sites, there is a published allowlist of separate &lt;strong&gt;non-EU and EU egress IPs&lt;/strong&gt;, last updated on 22 July. Check the admin guide for the current list rather than copying it from old project documentation.&lt;/p&gt;
&lt;h3&gt;Roles, auditability and change control&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Roles:&lt;/strong&gt; Opal Administrator, &lt;strong&gt;Agent Builder&lt;/strong&gt; (new on 24 February, for people who build agents without being admins) and Opal User, plus &lt;strong&gt;custom roles&lt;/strong&gt; in the Opti ID Admin Center.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit:&lt;/strong&gt; Created By, Triggered By and Modified By on agents and skills; credit usage per run in logs; execution-log export with date ranges for specialised and workflow agents; and the Execution Status Taxonomy (July).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change control:&lt;/strong&gt; version history for specialised agents, skills and workflows; watchers on skills and agents; and the Agent Update Warning before editing an agent a workflow depends on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Human in the loop for side effects:&lt;/strong&gt; for example, &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;send_email&lt;/span&gt; shows a confirmation "island" in chat (since December 2025), and Execution Advisor can gate sensitive tool calls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kill switch:&lt;/strong&gt; generative AI can be turned off for an organisation in the Opti ID Admin Center. Be aware that turning it off means contacting Optimizely Support to re-enable it.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Artifacts and canvases&lt;/h2&gt;
&lt;p&gt;Outputs have also grown well beyond chat text:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Canvas&lt;/strong&gt; (December 2025) is the working surface for documents, HTML, React/Next.js, CSV and PowerPoint content, with downloads in the relevant formats (February), an "Edit in canvas" button, a raw Markdown mode (May) and &lt;strong&gt;space-level sharing permissions&lt;/strong&gt; with view, edit and manage access (June).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Artifacts page&lt;/strong&gt; (May) is a single searchable home for everything you have created, with &lt;strong&gt;sharing&lt;/strong&gt; and, since September, &lt;strong&gt;RAG over artifacts&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PowerPoint and Word&lt;/strong&gt; creation and editing (June), when the &lt;span style="font-family: Consolas,Menlo,Monaco,monospace; font-size: 0.92em; color: #0b5d3b;"&gt;code_execution&lt;/span&gt; tool is enabled, now on-brand by default thanks to Brands.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;image editor&lt;/strong&gt; (June) offers element detection and selection, brush transforms, batch edits and smart aspect ratios. (below)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Limitless 1:1 Personalization&lt;/strong&gt; (June) generates landing pages per target account or segment, which is the capability I covered from the keynote last month.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="/link/f6d36291e8c64c828f338e6a63d67219.aspx" /&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. Administration, data residency and credits&lt;/h2&gt;
&lt;h3&gt;Administration essentials&lt;/h3&gt;
&lt;p&gt;The setup order has not changed: &lt;strong&gt;connect your product instances first&lt;/strong&gt; under &lt;strong&gt;Settings &amp;rarr; Product Connections&lt;/strong&gt;. If an instance is not connected, it cannot use the platform's features.&lt;/p&gt;
&lt;p style="font-size: 0.85em; color: #555; margin-top: 2px;"&gt;&lt;img style="max-width: 100%; height: auto;" src="https://cdn.images.archbee.com/BrbvVORKJ5iIeSMZye5kI/JRB_-VY_6VeSpeT0aSP3p_product-connections.png?format=webp" alt="Product Connections in Opal settings" /&gt; &lt;em&gt;Source: Optimizely Agent Platform documentation&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Other settings to know about are the default model provider and inference level, RAG in chat, Advanced URL safety, and the &lt;strong&gt;email domain&lt;/strong&gt; used for outbound email. Be careful with the email domain: it is a single subdomain prefix per instance, and &lt;strong&gt;it cannot be changed once set&lt;/strong&gt;. Users without Opti ID can also be enabled (see "Enable Opal for non-Opti ID users" in the docs).&lt;/p&gt;
&lt;h3&gt;Data residency&lt;/h3&gt;
&lt;p&gt;The platform is hosted in the &lt;strong&gt;US and the EU&lt;/strong&gt;, with the region chosen on your order form. Everyone uses the same URL, and routing to your region happens behind the scenes. Customer inputs and outputs are stored long-term in North America or the EU (Belgium), with no per-country selection. However, &lt;strong&gt;prompt processing goes through global model endpoints&lt;/strong&gt; for availability and performance, so a prompt may be processed in any region, although the LLM does not retain it and the output returns to your region. EU app hosting arrived on 4 June and EU RAG on 24 June. If a client has strict residency requirements, that processing nuance is the one to raise early.&lt;/p&gt;
&lt;h3&gt;Credits and cost&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Credits are &lt;strong&gt;pooled across all eligible Optimizely products&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Since &lt;strong&gt;1 March 2026&lt;/strong&gt;, usage falls into &lt;strong&gt;fixed complexity bands&lt;/strong&gt; for predictability.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Every instance gets 200 complimentary credits per month from 1 October 2025 until 31 December 2026.&lt;/strong&gt; This is worth noting for 2027 budgeting.&lt;/li&gt;
&lt;li&gt;There are &lt;strong&gt;no per-user credit limits&lt;/strong&gt;. You control spend by controlling who has access.&lt;/li&gt;
&lt;li&gt;Usage is visible in the Opti ID &lt;strong&gt;Usage and billing dashboards&lt;/strong&gt;, with monthly notifications, and in the redesigned &lt;strong&gt;Opal Agent Usage&lt;/strong&gt; dashboard (30 June), which shows executions, credits, top agents and users, and weekly active users.&lt;/li&gt;
&lt;/ul&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;Complexity band&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Average credits per invocation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;Chat and basic agents&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;XS&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;S&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;M&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;70&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;L&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;130&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;XL&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;200&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;To give a sense of scale from the published per-agent figures: SEO Metadata Implementation for CMS 13 averages 120&amp;ndash;200 credits, GEO Schema Optimization 40&amp;ndash;60, Content Model Creation 30&amp;ndash;80 and the Conductor AEO Gap Finder 180&amp;ndash;200.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Who pays&lt;/strong&gt; depends on the trigger: for chat and in-product invocations it is the invoking user, but for &lt;strong&gt;schedule, webhook and email triggers it is the user who created the trigger&lt;/strong&gt;. Keep that in mind when you design shared automations. In practice, you probably want a dedicated owner (or, increasingly, a Virtual Teammate) owning scheduled workflows, so usage reporting stays meaningful.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;12. 2026 at a glance&lt;/h2&gt;
&lt;table style="border-collapse: collapse; width: 100%;"&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; background: rgb(232, 241, 236); font-weight: bold; white-space: nowrap;"&gt;Month&lt;/th&gt;
&lt;th style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left; background: #e8f1ec; font-weight: bold;"&gt;Headline releases&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Jan&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Specialised agent versioning; prompt template shortcuts; Graph tools; CMS SaaS content tools; Experimentation and Personalization system tools; PIM chat; tools disabled by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Feb&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Agent Builder role&lt;/strong&gt; and custom roles; Slack Marketplace app; canvas downloads; example shots; Visual Editor, CMP and Analytics tool sets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Mar&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Claude models join (15 March); fixed-band credits (1 March); &lt;strong&gt;Commerce Connect product connection&lt;/strong&gt;; &lt;strong&gt;personal skills&lt;/strong&gt;; user-level auth for connectors; first &lt;strong&gt;remote MCP&lt;/strong&gt; connectors; credit usage in logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Apr&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;Agent JSON import/export; message queuing; nested parameter schemas for custom tools; &lt;strong&gt;CMS 13 Opal Chat NuGet&lt;/strong&gt;; Idea Builder&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;May&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Instructions &amp;rarr; Skills&lt;/strong&gt;; &lt;strong&gt;memory&lt;/strong&gt;; Artifacts page; notifications; chat modes, action cards and chat pills; &lt;strong&gt;RAG for CMP and Graph&lt;/strong&gt;; &lt;strong&gt;CMS 13 content tools&lt;/strong&gt;; more remote MCP servers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Jun&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Quality tab&lt;/strong&gt;; EU hosting and EU RAG; model provider selection; skill import/export; PPTX/DOCX generation; image editor; &lt;strong&gt;Agent Library&lt;/strong&gt;; agent sharing and visibility; Limitless 1:1 Personalization; Teams and Outlook tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Jul&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Agent Builder and Skill Builder in chat&lt;/strong&gt;; &lt;strong&gt;input guardrails&lt;/strong&gt;; Safe URL Browsing; conversation compaction; &lt;strong&gt;nested workflows&lt;/strong&gt;; workflow version history; &lt;strong&gt;bring-your-own remote MCP&lt;/strong&gt;; CMS 13 display template and media tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Aug&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Virtual Teammates&lt;/strong&gt;; &lt;strong&gt;Team Messages&lt;/strong&gt;; &lt;strong&gt;Brands&lt;/strong&gt;; Directory auto-update; Graph synonyms and pinned results; CMS 13 Page Builder and Content Analysis agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="border: 1px solid rgb(200, 211, 205); padding: 6px 10px; vertical-align: top; text-align: left; white-space: nowrap;"&gt;&lt;strong&gt;Sep&lt;/strong&gt;&lt;/td&gt;
&lt;td style="border: 1px solid #c8d3cd; padding: 6px 10px; vertical-align: top; text-align: left;"&gt;&lt;strong&gt;Rename to Agent Platform and Mark&lt;/strong&gt;; Mark-1, Mark-IQ and Mark-Bench announced; &lt;strong&gt;sub-agents&lt;/strong&gt;; &lt;strong&gt;RAG for artifacts&lt;/strong&gt;; workflow &lt;strong&gt;Code step&lt;/strong&gt;, parallel loops and trigger priority; &lt;strong&gt;personal files&lt;/strong&gt;; LinkedIn and OpenAI Ads connectors; Braze and Fullstory MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;For context, at the start of the 2025 release notes Opal was essentially a chat assistant embedded in individual products. The platform pieces, namely specialised agents, workflow agents, the Agent Directory, system tools and custom tools, only arrived on &lt;strong&gt;9 September 2025&lt;/strong&gt;. Everything in the table above has been built in the thirteen months since.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;13. What this means for solution architects&lt;/h2&gt;
&lt;p&gt;Pulling this together, these are the points I would take into any design conversation right now:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Upgrade the foundations first.&lt;/strong&gt; Opti ID (identity for humans &lt;em&gt;and&lt;/em&gt; teammates), Graph (retrieval, search tuning and agent access to content) and product connections are the prerequisites for nearly everything above. This is the practical reason the CMS 13 and Commerce 15 upgrade is a strategic decision, not just a version bump.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treat skills as code.&lt;/strong&gt; With import/export, versioning, watchers and a selection model that only reads &lt;em&gt;When to use&lt;/em&gt;, a skills library deserves the same discipline as any shared configuration: naming conventions, scoped activation criteria with explicit exclusions, review, and promotion between instances.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Decide your integration pattern deliberately.&lt;/strong&gt; For each external system, the choice is now between an existing connector, a vendor's remote MCP server, your own remote MCP server, or a custom tool (on OCP or self-hosted). User-level auth on MCP and connectors gives better attribution; custom tools currently authenticate with Opti ID only.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Put deterministic logic in deterministic places.&lt;/strong&gt; Use workflow Code steps and static parameters for anything that does not need an LLM, and keep specialised agents small and single-purpose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Design for cost visibility.&lt;/strong&gt; Choose the inference level per agent rather than defaulting everything to Pro, expect sub-agent rows in logs, and be deliberate about who owns scheduled and webhook triggers, since they pay.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layer governance before autonomy.&lt;/strong&gt; Input guardrails, Output Evaluation, then Execution Guardrails, and only then let Virtual Teammates act without approval, scoping their Opti ID roles and CMS access rights to exactly what their jobs need.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use the docs carefully.&lt;/strong&gt; They are excellent but moving fast: naming is mid-migration, workflow agents are still flagged as private GA, release notes can be superseded by reference pages (as with the inference mapping), and at least one SDK package ID is inconsistent. When it matters, verify in your own instance.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;Twelve months ago, Opal was a promising chat assistant with a brand-new agent builder. Today the Optimizely Agent Platform has a multi-model runtime with managed sub-agent delegation, a layered context system (skills, brands, memory, files and RAG), an open tool ecosystem built around MCP, low-code workflows with real code execution, autonomous Virtual Teammates with their own identities, and a governance stack to keep it all in check. Mark is the friendly face on top, and Mark's own post-trained models are next.&lt;/p&gt;
&lt;p&gt;For those of us designing solutions on Optimizely, the platform is no longer an add-on to CMS or Commerce. It is becoming the operational layer that sits across them, and the quality of what it can do depends directly on how well the underlying platform is connected through Opti ID, Graph and the product connections.&lt;/p&gt;
&lt;p&gt;If you would like help shaping your agent platform strategy, building custom tools and MCP integrations, or getting onto CMS 13 and Commerce 15 so you can use all of this, get in touch with us at Niteco: &lt;a href="https://niteco.com/contact-us/"&gt;https://niteco.com/contact-us/&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Key references&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Agent Platform documentation: &lt;a href="https://docs.optimizely.com/agent-platform/docs"&gt;https://docs.optimizely.com/agent-platform/docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2026 release notes: &lt;a href="https://docs.optimizely.com/agent-platform/release-notes"&gt;https://docs.optimizely.com/agent-platform/release-notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2025 release notes: &lt;a href="https://docs.optimizely.com/agent-platform/release-notes/2025-agent-platform-release-notes"&gt;https://docs.optimizely.com/agent-platform/release-notes/2025-agent-platform-release-notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Inference level and model mapping: &lt;a href="https://docs.optimizely.com/agent-platform/docs/inference-level"&gt;https://docs.optimizely.com/agent-platform/docs/inference-level&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Sub-agents overview: &lt;a href="https://docs.optimizely.com/agent-platform/docs/sub-agents-overview"&gt;https://docs.optimizely.com/agent-platform/docs/sub-agents-overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Skills overview: &lt;a href="https://docs.optimizely.com/agent-platform/docs/skills-overview"&gt;https://docs.optimizely.com/agent-platform/docs/skills-overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Tools overview: &lt;a href="https://docs.optimizely.com/agent-platform/docs/tools-overview"&gt;https://docs.optimizely.com/agent-platform/docs/tools-overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Create custom tools (SDKs): &lt;a href="https://docs.optimizely.com/agent-platform/docs/create-custom-tools"&gt;https://docs.optimizely.com/agent-platform/docs/create-custom-tools&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Virtual Teammates overview: &lt;a href="https://docs.optimizely.com/agent-platform/docs/virtual-teammates-overview"&gt;https://docs.optimizely.com/agent-platform/docs/virtual-teammates-overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Quality tab overview: &lt;a href="https://docs.optimizely.com/agent-platform/docs/quality-tab-overview"&gt;https://docs.optimizely.com/agent-platform/docs/quality-tab-overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Credits: &lt;a href="https://docs.optimizely.com/agent-platform/docs/agent-platform-credits"&gt;https://docs.optimizely.com/agent-platform/docs/agent-platform-credits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Administrator guide: &lt;a href="https://docs.optimizely.com/agent-platform/docs/get-started-with-agent-platform-for-administrators"&gt;https://docs.optimizely.com/agent-platform/docs/get-started-with-agent-platform-for-administrators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Mark models and Mark-Bench press release (1 September 2026): &lt;a href="https://www.prnewswire.com/news-releases/optimizely-introduces-purpose-built-ai-models-for-marketing-that-deliver-frontier-level-quality-at-a-fraction-of-the-cost-302865074.html"&gt;https://www.prnewswire.com/news-releases/optimizely-introduces-purpose-built-ai-models-for-marketing-that-deliver-frontier-level-quality-at-a-fraction-of-the-cost-302865074.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;My Opticon New York write-up: &lt;a href="/link/fecc835a60ef4678891f74e1fcbf06ea.aspx"&gt;https://world.optimizely.com/blogs/scott-reed/dates/2026/9/opticon-new-york--the-strategy-on-upgrading-to-cms-13-/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</summary></entry><entry><title>Migrating from Search &amp; Navigation (Find) to Optimizely Graph in CMS 13</title><link href="https://www.adnanzameer.com/2026/10/find-to-optimizely-graph-migration-guide-cms-13.html" /><id>https://www.adnanzameer.com/2026/10/find-to-optimizely-graph-migration-guide-cms-13.html</id><published>2026-10-05T08:41:10.0000000Z</published><updated>2026-10-05T09:30:06.0000000Z</updated><author><name>adnan.zameer@episerver.com</name></author><category term="Blog post" /><summary type="html">&lt;!--excerpt: Upgrading to Optimizely CMS 13 forces a migration from Find to Optimizely Graph because Search &amp; Navigation is not supported in CMS 13. This guide covers package replacements, configuration changes, DI setup, and how to rewrite your queries using the new IGraphContentClient and async execution model.--&gt;

If you are planning an upgrade to Optimizely CMS 13, one of the biggest </summary></entry><entry><title>So you've decided to move to .NET 10 (but stay on CMS 12)</title><link href="https://world.optimizely.com/blogs/kennyg/dates/2026/10/so-youve-decided-to-move-to-.net-10-but-stay-on-cms-12" /><id>https://world.optimizely.com/blogs/kennyg/dates/2026/10/so-youve-decided-to-move-to-.net-10-but-stay-on-cms-12</id><published>2026-10-02T16:17:30.0000000Z</published><updated>2026-10-02T16:17:30.0000000Z</updated><author><name>kenny.gutierrez@centurycommunities.com</name></author><category term="Blog post" /><summary type="html">&lt;p&gt;Ok, "decided" is a strong word. Microsoft decided for us. .NET 8 and .NET 9 both reach end of support on &lt;strong&gt;November 10, 2026&lt;/strong&gt;, and if you're on CMS 12 and Commerce 14, the calendar is the project plan whether you like it or not.&lt;/p&gt;
&lt;p&gt;CMS 13 is the shiny option, but it brings Opti ID, Optimizely Graph in place of Search &amp;amp; Navigation, and (for Commerce) a real rewrite. That's a project. Staying on CMS 12 / Commerce 14 and moving the runtime to .NET 10 is the "buy yourself time" option. Optimizely has said both &lt;a href="/link/d15848f947264cc59c3d86b9c7d55c95.aspx"&gt;CMS 12&lt;/a&gt; and &lt;a href="/link/4fc8e86414a24921a28f57af498aadc7.aspx"&gt;Commerce 14&lt;/a&gt; support it.&lt;/p&gt;
&lt;p&gt;We've been working through this for a couple of months on a DXP-hosted CMS 12 + Commerce 14 site. We're not on Production yet, so this isn't a victory lap. It's the list I wish someone had handed me on day one.&lt;/p&gt;
&lt;h2&gt;Isn't this just a TargetFramework change?&lt;/h2&gt;
&lt;p&gt;No. The TFM change is the easy part. The packages decide the rest.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The .NET 10-compatible Commerce 14 requires a newer &lt;strong&gt;EPiServer.Find.Commerce&lt;/strong&gt;, which requires &lt;strong&gt;Search &amp;amp; Navigation (Find) 17&lt;/strong&gt;. The build fails outright until you take it. It's not a warning you can sit on.&lt;/li&gt;
&lt;li&gt;Find 17 changes the index format, which means a &lt;strong&gt;full reindex in every environment&lt;/strong&gt;. More on that below, because it's the biggest item on this list.&lt;/li&gt;
&lt;li&gt;Take the latest patch of everything, not the first version that compiles. We hit a SQL deadlock on bulk publish with Find 17.0.1. It went away after we moved to 17.0.2 together with the latest CMS and Commerce patches. We didn't isolate which one fixed it.&lt;/li&gt;
&lt;li&gt;Check the official &lt;em&gt;System requirements&lt;/em&gt; page against the blog posts. For a while they disagreed. Know which one you're relying on.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why is my homepage a 500 right after deploying?&lt;/h2&gt;
&lt;p&gt;Against an index built by Find 16, Find 17 queries come back with a null content link, and you get an &lt;strong&gt;ArgumentNullException&lt;/strong&gt; deep in &lt;strong&gt;ContentInstanceCache&lt;/strong&gt;. If anything in your header or layout does a Find query, that's every page.&lt;/p&gt;
&lt;p&gt;Things to plan for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rollback is asymmetric.&lt;/strong&gt; Redeploying the old code is easy. But Find 16 code can't read a Find 17-format index either, so a rollback needs a &lt;em&gt;second&lt;/em&gt; full reindex. Plan to fix forward.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The out-of-the-box indexing job may not be your whole index.&lt;/strong&gt; If you push catalog content into Find with your own jobs, list every type you index and make sure each one actually has a job that indexes it. We found types that no job covered at all.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP 200 is not "healthy."&lt;/strong&gt; A half-built index can return &lt;strong&gt;{"total":0}&lt;/strong&gt; in 30 ms and pass every smoke test. Check what the page actually renders (nav item counts, search result counts), not just status codes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Big reindex jobs need to batch.&lt;/strong&gt; One-item-at-a-time indexing of ~100k variants took about 12 hours. Batching brought it to about an hour. (The out-of-memory errors we saw on that job were the memory limit in the next section, not the lack of batching.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;For environments with live traffic, ask about a temporary second index.&lt;/strong&gt; Optimizely told us it can provision a temporary migration index, so you build the new index next to the live one and flip &lt;strong&gt;DefaultIndex&lt;/strong&gt; at cutover. Be clear that you mean a temporary one, not a permanent one. On DXP that flip is a support change request, not something you can do yourself on Preproduction/Production. At the time of writing ours hasn't been provisioned yet, so ask early.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Staging slots share the database.&lt;/strong&gt; If you think of reindexing in a slot, remember the new code can run schema upgrades against the same database the live slot is using. Optimizely's suggestion was to turn off automatic schema updates on the slot (&lt;strong&gt;UpdateDatabaseSchema = false&lt;/strong&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why is it suddenly out of memory?&lt;/h2&gt;
&lt;p&gt;This one cost us the most time, so I'll spend a minute on it.&lt;/p&gt;
&lt;p&gt;Same code, same environment tier. On .NET 8 our Integration site happily grew past 8 GB. On .NET 10 the same site capped at about 6 GB and threw &lt;strong&gt;OutOfMemoryException&lt;/strong&gt; on heavy reports, exports and reindex jobs.&lt;/p&gt;
&lt;p&gt;The reason: since .NET 9, the runtime honors the memory limit of the &lt;em&gt;parent&lt;/em&gt; cgroup the App Service site runs in, and .NET 8 did not (&lt;a href="https://github.com/dotnet/runtime/pull/93611"&gt;dotnet/runtime#93611&lt;/a&gt;, part of &lt;a href="https://github.com/dotnet/runtime/issues/93030"&gt;#93030&lt;/a&gt;). By default the GC heap gets 75% of that limit. So .NET 8 was quietly using memory the newer runtime won't touch. If you're already on .NET 9, you already live with this limit.&lt;/p&gt;
&lt;p&gt;What to do before you deploy:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Measure your current peak Private Bytes on .NET 8&lt;/strong&gt;, per environment, for 30 days. Compare it with what the GC will allow on .NET 10: about 75% of the &lt;em&gt;site's&lt;/em&gt; memory limit, which is lower than the plan's RAM. On an 8 GB P1v3 the site limit was about 7 GB.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Log what the runtime actually thinks it has&lt;/strong&gt; at startup. Don't guess. This one line showed us the real limit:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;var gcInfo = GC.GetGCMemoryInfo();
logger.LogInformation("GC TotalAvailableMemoryBytes={TotalAvailable}", gcInfo.TotalAvailableMemoryBytes);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;HeapHardLimitPercent&lt;/strong&gt; buys some headroom, but it's a stopgap. The rest of the memory is native memory you still need:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ItemGroup&amp;gt;
  &amp;lt;RuntimeHostConfigurationOption Include="System.GC.HeapHardLimitPercent" Value="80" /&amp;gt;
&amp;lt;/ItemGroup&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Know about DATAS&lt;/strong&gt; if you're comparing memory between .NET 8 and .NET 10. Since .NET 9, Server GC turns on dynamic heap sizing (DATAS) by default. We turned it off (&lt;strong&gt;&amp;lt;GarbageCollectionAdaptationMode&amp;gt;0&amp;lt;/GarbageCollectionAdaptationMode&amp;gt;&lt;/strong&gt;) so our numbers compared like for like.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ask Optimizely about sizing early&lt;/strong&gt; if your numbers say you need a bigger tier. Bring the measurements: the startup log line and the runtime issue link.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Wait, did we just upgrade Preproduction?&lt;/h2&gt;
&lt;p&gt;Good news: you don't need to ask DXP to change anything. It picks the base image from the target framework in your package, &lt;strong&gt;per environment&lt;/strong&gt;. So Integration can run .NET 10 while Preproduction and Production stay on .NET 8.&lt;/p&gt;
&lt;p&gt;The trap: &lt;strong&gt;a promotion reuses the source environment's container image, runtime included.&lt;/strong&gt; Promote your .NET 10 Integration build and Preproduction is now on .NET 10. While you're evaluating, deploy the package directly to Integration and keep sending .NET 8 packages downstream.&lt;/p&gt;
&lt;p&gt;To revert an environment, redeploy a .NET 8 package. One catch: Commerce and CMS package updates upgrade the database schema on first boot, and older packages refuse to start against a newer schema. A .NET 8 package is a clean rollback only if it carries the same Optimizely package versions. The .NET 10-compatible Commerce 14 still targets .NET 8, so ship the package updates on .NET 8 first. Then the runtime is the only thing you'd be rolling back.&lt;/p&gt;
&lt;h2&gt;Why does it build on my machine and not in CI?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;System.Linq.Async vs. the BCL.&lt;/strong&gt; .NET 10 added &lt;strong&gt;AsyncEnumerable&lt;/strong&gt; to the framework, and the old package still arrives transitively through the CMS UI packages. Exclude its compile assets in every project that sees it (&lt;a href="https://learn.microsoft.com/dotnet/core/compatibility/core-libraries/10.0/asyncenumerable"&gt;Microsoft's documented fix&lt;/a&gt;):&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;PackageReference Include="System.Linq.Async" Version="6.0.1"&amp;gt;
  &amp;lt;ExcludeAssets&amp;gt;compile&amp;lt;/ExcludeAssets&amp;gt;
&amp;lt;/PackageReference&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Castle.Core must stay below 5.0&lt;/strong&gt; for CMS 12. An add-on whose .NET 10 dependency group pulls in Castle.Core 5.x breaks boot with a &lt;strong&gt;TypeLoadException&lt;/strong&gt;. In test projects, newer Moq versions pull it in too. The add-on author we reported it to fixed it within a day.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The classic NuGet task can disagree with dotnet restore.&lt;/strong&gt; With the &lt;strong&gt;nuget.exe&lt;/strong&gt; version our pipeline pinned, restore failed on an NU1605 downgrade that &lt;strong&gt;dotnet restore&lt;/strong&gt; on the .NET 10 SDK never reported. Use a &lt;strong&gt;nuget.exe&lt;/strong&gt; that matches your SDK, or switch CI to &lt;strong&gt;dotnet restore&lt;/strong&gt;, and run a CI build early.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build tools that target an older runtime&lt;/strong&gt; (code generators, CLI tools invoked from MSBuild) fail on an agent that only has .NET 10. &lt;strong&gt;DOTNET_ROLL_FORWARD=LatestMajor&lt;/strong&gt; in the pipeline fixed it for us.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pin LangVersion&lt;/strong&gt; if your team needs to keep a C# version ceiling. The new SDK will happily let people use features you didn't agree to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Third-party UI packages&lt;/strong&gt; with per-version license keys or hard-coded CDN versions break quietly when the package version moves and the hard-coded number doesn't.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Why does EF Core suddenly throw a syntax error?&lt;/h2&gt;
&lt;p&gt;If you move EF Core up with the runtime (EF Core 8 and 9 end support on the same date as .NET 8), check your large &lt;strong&gt;.Contains()&lt;/strong&gt; calls. EF Core 10 sends a list as separate parameters by default, but past SQL Server's 2,100-parameter limit it falls back to one JSON parameter decoded with &lt;strong&gt;OPENJSON&lt;/strong&gt;. That needs SQL Server compatibility level &lt;strong&gt;130 or higher&lt;/strong&gt;. Our local copy of the CMS database (restored from a DXP export) was at &lt;strong&gt;110&lt;/strong&gt;, and a &lt;strong&gt;.Contains()&lt;/strong&gt; over about 5,000 IDs threw &lt;strong&gt;Incorrect syntax near '$'&lt;/strong&gt;. Small lists worked fine, so a quick test won't catch it.&lt;/p&gt;
&lt;p&gt;Check yours:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT name, compatibility_level FROM sys.databases;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you can't raise the level, tell EF to inline constants for that context:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;options.UseSqlServer(connectionString,
    sql =&amp;gt; sql.UseParameterizedCollectionMode(ParameterTranslationMode.Constant));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This applies to every &lt;strong&gt;.Contains()&lt;/strong&gt; in that context, so expect more distinct query plans in SQL Server's plan cache.&lt;/p&gt;
&lt;h2&gt;Is that a .NET 10 bug or something else?&lt;/h2&gt;
&lt;p&gt;Most of what our first regression pass found wasn't .NET 10 at all. Before you blame the runtime, check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Concurrency bugs that were already there.&lt;/strong&gt; We had &lt;strong&gt;Task.WhenAll&lt;/strong&gt; over several content loads on one request, which shares a database connection context that isn't built for parallel use. It was latent on .NET 8 and showed up on the new stack. Search for parallel &lt;strong&gt;IContentLoader&lt;/strong&gt; calls.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stale front-end bundles in local builds.&lt;/strong&gt; If &lt;strong&gt;dist/&lt;/strong&gt; is gitignored, a local &lt;strong&gt;dotnet publish&lt;/strong&gt; copies whatever bundles happen to be on disk. Renders fine, looks wrong.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lower-environment content drift.&lt;/strong&gt; Integration content and settings drift from Production. Compare with Preproduction before you file a bug: if it also fails on the .NET 8 environment, it's not a regression.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Old release pipelines.&lt;/strong&gt; A pipeline nobody has touched in a while may point at a retired hosted agent image.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How do I test this without breaking things?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Turn off outbound integrations before you test.&lt;/strong&gt; A regression pass that runs jobs and publishes content will send records to every partner, CRM and ticketing endpoint that environment is configured for. Check which of those are production endpoints before you start.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Script it.&lt;/strong&gt; Our first pass was a full page-by-page walk-through and took two days. Most of it found content drift. The rerun is a script of about 90 minutes: boot log, a critical-path test suite, a page/API status sweep, heavy reports and jobs under load, one end-to-end checkout in test mode, an exception diff.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capture before-and-after numbers&lt;/strong&gt; in the same time window: request percentiles, memory, exceptions by type, job durations. If you change the tier and the runtime at the same time, you'll want to know which one helped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do a database copy-down rehearsal&lt;/strong&gt; on Integration. Booting a Production-sized database on the new stack catches things a stale lower environment won't. A copy-down brings Production's scheduled jobs and settings with it, so turn off outbound jobs before the site boots.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;So what did I miss?&lt;/h2&gt;
&lt;p&gt;That's the list so far. We're still working toward the Preproduction and Production cutover, so I expect to add to it. If you've already done this move on DXP, especially the Find 17 cutover on a live site, I'd love to hear how you sequenced it. Let me know in the comments.&lt;/p&gt;</summary></entry><entry><title>From robots.txt to GEO Analytics: A Year On</title><link href="https://www.adnanzameer.com/2026/10/from-robotstxt-to-geo-analytics-a-year-on.html" /><id>https://www.adnanzameer.com/2026/10/from-robotstxt-to-geo-analytics-a-year-on.html</id><published>2026-10-02T08:33:26.0000000Z</published><updated>2026-10-02T09:30:08.0000000Z</updated><author><name>adnan.zameer@episerver.com</name></author><category term="Blog post" /><summary type="html">&lt;!--excerpt: In September 2025, we updated our robots.txt files to explicitly allow AI crawlers like GPTBot and PerplexityBot. A year later, Optimizely CMS 13 and the Agent Visibility dashboard let us measure what those bots actually do with our content. Here is how the transition from robots.txt permission to GEO measurement and optimization works.--&gt;

In September 2025, I wrote about  updating </summary></entry><entry><title>Connect Your AI Agents Directly to Optimizely CMS</title><link href="https://optimizely.blog/2026/10/connect-your-ai-agents-directly-to-optimizely-cms/" /><id>https://optimizely.blog/2026/10/connect-your-ai-agents-directly-to-optimizely-cms/</id><published>2026-10-01T11:20:43.0000000Z</published><updated>2026-10-01T12:30:52.0000000Z</updated><author><name>luc.gosso@epicweb.se</name></author><category term="Blog post" /><summary type="html">Connect any AI agent to Optimizely CMS and Commerce via Epicweb Agent API, reusing CMS-aware tools, instructions, and permissions to automate SEO, translation, and updates.
</summary></entry></feed>