<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://shazwazza.com/rss/xslt"?>
<rss xmlns:a10="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title>Shazwazza</title>
    <link>https://shazwazza.com/</link>
    <description>My blog which is pretty much just all about coding</description>
    <generator>Articulate, blogging built on Umbraco</generator>
    <image>
      <url>/media/0libq25y/frog.png?rmode=max&amp;v=1da0e911f4e6890</url>
      <title>Shazwazza</title>
      <link>https://shazwazza.com/</link>
    </image>
    <item>
      <guid isPermaLink="false">1338</guid>
      <link>https://shazwazza.com/post/automate-all-the-things-codegarden-2026/</link>
      <category>Umbraco</category>
      <title>Automate All the Things - my Codegarden 2026 talk is up</title>
      <description>&lt;p&gt;My Codegarden 2026 talk, &lt;strong&gt;Automate All the Things: Building &amp;amp; Evolving Umbraco Sites with AI Agents&lt;/strong&gt;, is now up on YouTube:&lt;/p&gt;
&lt;p&gt;▶️ &lt;a href="https://www.youtube.com/watch?v=A_EUvX8naHQ"&gt;&lt;strong&gt;Watch it here&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;And everything I ran on stage - the prompts, the rules, the skills, the agents, the whole workflow - is open source here:&lt;/p&gt;
&lt;p&gt;📦 &lt;strong&gt;&lt;a href="**https://github.com/Shazwazza/UmbracoAI**"&gt;github.com/Shazwazza/UmbracoAI&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="so-what-actually-happens-in-it"&gt;So what actually happens in it?&lt;/h2&gt;
&lt;p&gt;The whole talk is a live demo. I point GitHub Copilot CLI at a completely empty Umbraco install - no content types, no templates, no content, nothing but an API user I'd set up beforehand - and it designs, scaffolds, styles, populates, validates and commits an entire blogging website. Nobody opens the back office. Nobody writes a line of code.&lt;/p&gt;
&lt;p&gt;It builds the document types, the templates, the CSS, the navigation, ten blog posts, goes and finds matching images for all of them, adds a tag cloud, generates a sitemap, runs accessibility checks, and commits after every single step.&lt;/p&gt;
&lt;p&gt;The content isn't generic filler, either. The agent is told to write about the presentation it's currently part of, and about what it's learning as it goes. So it writes blog posts about its own experience of being built, live, while a room full of people watches. It knows it's on stage, and it will happily tell you so.&lt;/p&gt;
&lt;h2 id="the-bit-im-most-excited-about"&gt;The bit I'm most excited about&lt;/h2&gt;
&lt;p&gt;The thing driving all of it is &lt;a href="https://github.com/microsoft/conductor"&gt;Conductor&lt;/a&gt; - a YAML-based multi-agent workflow runner. If you've used LangChain or LangGraph, it's a bit like that, except you don't write code, you write YAML. Each step is an agent with strongly typed inputs and outputs, so you can wire the output of one step into the input of another. Steps can run in parallel, they can fan out and fan back in, they can loop, they can each use a different model, and the whole thing can be templated.&lt;/p&gt;
&lt;p&gt;The best part is you don't even have to write the YAML. Installing Conductor installs a skill that writes Conductor workflows, so you can just ask for what you want. That's exactly what I did - I told it I wanted a blog engine with a tag cloud and git commits along the way, and deliberately never told it how to use Umbraco. It figured that out entirely from the &lt;a href="https://github.com/Umbraco/umbraco-mcp"&gt;Umbraco MCP&lt;/a&gt; tool metadata.&lt;/p&gt;
&lt;h2 id="the-funny-bits"&gt;The funny bits&lt;/h2&gt;
&lt;p&gt;Because it's writing about itself as it goes, the titles are a delight. Some actual ones it came up with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Hello Codegarden, an AI agent walks into a CMS&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;The first keystroke is always the hardest&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Publishing nine posts in parallel&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Giving the tag cloud a job&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Building navigation that feels obvious&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;My first accessibility pass, out loud&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;em&gt;The day HMAC-signed images humbled me&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last one got a proper laugh out of the room, because of course everybody has had that problem. And &amp;quot;the first keystroke is always the hardest&amp;quot; opened with something about the particular quiet that lives in the moment you create your very first document type, which is honestly more introspection than I manage before coffee.&lt;/p&gt;
&lt;p&gt;One run decided to put a live counter on the home page showing how long it had been building and how many document types it had created, credited to &amp;quot;one very caffeinated agent&amp;quot;. Nobody asked it to do that.&lt;/p&gt;
&lt;p&gt;The other consistent surprise: if you tell a model to be creative and invent its own visual identity, it will make it neon. Over and over. I never once said the word &amp;quot;neon&amp;quot;. Several runs independently decided the site should be called &lt;strong&gt;Aurora&lt;/strong&gt;, which I also never mentioned. Something out there in training data really wants your blog to glow.&lt;/p&gt;
&lt;h2 id="go-have-a-play"&gt;Go have a play&lt;/h2&gt;
&lt;p&gt;Clone the &lt;a href="https://github.com/Shazwazza/UmbracoAI"&gt;repo&lt;/a&gt;, install Conductor, point it at an empty Umbraco and watch it go. The README covers the setup. If you build something interesting with it, I'd genuinely love to hear about it. 🙂&lt;/p&gt;
</description>
      <pubDate>Wed, 12 Aug 2026 19:36:08 Z</pubDate>
      <a10:updated>2026-08-12T19:36:08Z</a10:updated>
    </item>
    <item>
      <guid isPermaLink="false">1336</guid>
      <link>https://shazwazza.com/post/letting-ai-agents-loose-on-examine/</link>
      <category>Examine</category>
      <title>Letting AI agents loose on Examine</title>
      <description>&lt;p&gt;I've been maintaining &lt;a href="https://github.com/Shazwazza/Examine"&gt;Examine&lt;/a&gt; for a very long time now. It's the search and indexing library that sits underneath every Umbraco site, and like any project that's been around long enough, it has plenty of hot paths that could be faster, a steady trickle of issues that need triaging, and a backlog of &amp;quot;I'll get to that eventually&amp;quot; performance work that, well… I never actually get to.&lt;/p&gt;
&lt;p&gt;So a couple of months ago I thought I'd try an experiment - what if I stopped doing all of that myself and instead let a bunch of AI agents do it for me, on a schedule, while I got on with everything else? This post is about how that went. I've shipped a few releases off the back of it and some of the hot-path improvements are genuinely a bit ridiculous.&lt;/p&gt;
&lt;h2 id="so-what-are-these-agentic-workflows"&gt;So what are these &amp;quot;agentic workflows&amp;quot;?&lt;/h2&gt;
&lt;p&gt;The thing I installed is &lt;a href="https://github.com/githubnext/agentics"&gt;GitHub Next's Agentic Workflows&lt;/a&gt; - a set of markdown-defined workflows that run in GitHub Actions and drive an AI agent to do actual maintenance work on your repo. They're not chat bots. They run on a schedule, they have persistent memory, they open real pull requests, and they leave the &amp;quot;should this ship?&amp;quot; decision to me.&lt;/p&gt;
&lt;p&gt;I ended up with a few of them running on Examine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Perf Improver&lt;/strong&gt; - runs daily, hunts for performance bottlenecks, writes benchmarks to actually prove the improvement, and opens a draft PR with the before/after numbers.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficiency Improver&lt;/strong&gt; - its scrappier sibling, focused on the smaller allocation-and-LINQ-state-machine wins that add up over time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Daily Issue Triage&lt;/strong&gt; - goes through untriaged issues, sets types, applies labels, spots duplicates, and leaves a tidy triage report for me.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agentic Maintenance&lt;/strong&gt; - keeps the whole setup ticking along.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each one is just a markdown file in &lt;code&gt;.github/workflows/&lt;/code&gt; with a description, a schedule, some &lt;code&gt;safe-outputs&lt;/code&gt; limits (things like &amp;quot;you may open at most 4 PRs per run, and they must be drafts&amp;quot;) and a big prompt describing how to behave. That's it. The nice part is that the guardrails are declarative, so the agent can't merge its own PRs, it can't touch protected files, and it can only comment so many times per run. All of that is baked in.&lt;/p&gt;
&lt;h2 id="how-it-actually-works-day-to-day"&gt;How it actually works day to day&lt;/h2&gt;
&lt;p&gt;The bit that makes this more than a gimmick is the persistent memory. Every run, the Perf Improver reads its own notes - which build/test/benchmark commands it validated, what's on its optimisation backlog, what it worked on last time, and which suggestions I've already ticked off. Then it does a couple of tasks in a round-robin fashion so it's not endlessly poking at the same corner of the codebase.&lt;/p&gt;
&lt;p&gt;It also keeps a single rolling &amp;quot;Monthly Activity&amp;quot; issue open with a checklist of what needs my attention. So my side of it is pretty simple: the agent opens a draft PR with measured before/after numbers, I read it and run CI, and if I'm happy with it I merge. That's the whole loop. I'm the reviewer, the agent is the workhorse doing the grind I never had time for.&lt;/p&gt;
&lt;h2 id="so-what-did-they-actually-get-done"&gt;So what did they actually get done?&lt;/h2&gt;
&lt;p&gt;Over roughly the last two months, here's what these workflows actually got up to on Examine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;124&lt;/strong&gt; successful Perf Improver runs and &lt;strong&gt;9&lt;/strong&gt; successful Efficiency Improver runs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;20&lt;/strong&gt; successful Daily Issue Triage runs quietly keeping the issue tracker tidy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;24&lt;/strong&gt; performance/efficiency PRs reviewed and merged (13 from Perf Improver, 11 from Efficiency Improver) between late May and the end of June&lt;/li&gt;
&lt;li&gt;And the bit that actually matters - &lt;strong&gt;three releases shipped&lt;/strong&gt; off this work: &lt;strong&gt;v3.8.0&lt;/strong&gt;, &lt;strong&gt;v3.9.0&lt;/strong&gt; and the &lt;strong&gt;v4.0.0-beta.7&lt;/strong&gt; pre-release&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That last point is really the whole thing. This isn't a pile of speculative branches rotting in a fork somewhere - it's code that went through my review, passed CI, and is now sitting in NuGet packages that real Umbraco sites are running.&lt;/p&gt;
&lt;h2 id="show-me-the-numbers"&gt;Show me the numbers&lt;/h2&gt;
&lt;p&gt;Right, this is the part I actually get excited about. One of my favourite things about the Perf Improver is that its prompt tells it to only attempt improvements it can actually measure - establish a baseline first, make the change, then measure again and document both numbers. For the algorithmic hot-path stuff that means benchmarks, so along the way it built out a proper &lt;a href="https://benchmarkdotnet.org/"&gt;BenchmarkDotNet&lt;/a&gt; suite that compares the current source against the published NuGet packages (3.0.1 through 3.3.0), which means I can show you real, reproducible deltas instead of hand waving.&lt;/p&gt;
&lt;h3 id="the-full-text-search-hot-path"&gt;The full-text search hot path&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;ManagedQuery&lt;/code&gt; is &lt;em&gt;the&lt;/em&gt; primary full-text search entry point in Examine - it's what runs on basically every search. The agent noticed it had no dedicated benchmark, wrote one, and then stacked up a series of small, individually measured changes: a volatile factory cache in &lt;code&gt;SearchContext.GetFieldValueType&lt;/code&gt;, an early return in the extract-terms check, and killing off some redundant &lt;code&gt;ConcurrentDictionary&lt;/code&gt; lookups in &lt;code&gt;AddDocument&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here's the current source vs the most recent 3.3.0 release, on a 1,000-document index:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Mean&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Allocated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ManagedQueryAllFields&lt;/td&gt;
&lt;td&gt;3.3.0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;11.42 ms&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1,323 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ManagedQueryAllFields&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;2.17 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;371 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That's about &lt;strong&gt;5.3x faster&lt;/strong&gt; and roughly &lt;strong&gt;3.6x less memory allocated&lt;/strong&gt; on the single most travelled code path in the whole library. On the busiest thing Examine does. I'll happily take that.&lt;/p&gt;
&lt;h3 id="building-queries"&gt;Building queries&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;GroupedAnd&lt;/code&gt; / &lt;code&gt;GroupedOr&lt;/code&gt; / &lt;code&gt;GroupedNot&lt;/code&gt; are the workhorses of the query builder. A little &lt;code&gt;string[]&lt;/code&gt; fast-path (skipping a defensive &lt;code&gt;.ToArray()&lt;/code&gt; copy when the caller already handed it a &lt;code&gt;string[]&lt;/code&gt;) plus some allocation trimming got this:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Mean&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Allocated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CreateQueryOnly&lt;/td&gt;
&lt;td&gt;3.3.0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;3,995 ns&lt;/td&gt;
&lt;td style="text-align: right;"&gt;8.34 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CreateQueryOnly&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;319 ns&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;2.20 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GroupedAndStringArray&lt;/td&gt;
&lt;td&gt;3.3.0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;21,377 ns&lt;/td&gt;
&lt;td style="text-align: right;"&gt;21.10 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GroupedAndStringArray&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;16,659 ns&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;14.34 KB&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The &lt;code&gt;CreateQuery()&lt;/code&gt; baseline dropping from 8.34 KB down to 2.2 KB is a lovely little win, and the grouped clauses are about 25% faster with a third of the allocation shaved off.&lt;/p&gt;
&lt;h3 id="constructing-a-valueset"&gt;Constructing a ValueSet&lt;/h3&gt;
&lt;p&gt;Every single document you index goes through a &lt;code&gt;ValueSet&lt;/code&gt; constructor. The old path allocated an intermediate dictionary and a generator state machine per field, which is exactly the kind of thing you don't notice until you're bulk indexing a big site. The agent got rid of both:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Mean&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Allocated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FromDictionary5Fields&lt;/td&gt;
&lt;td&gt;3.3.0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1,183 ns&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2,200 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FromDictionary5Fields&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;226 ns&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;592 B&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FromDictionary20Fields&lt;/td&gt;
&lt;td&gt;3.3.0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;4,007 ns&lt;/td&gt;
&lt;td style="text-align: right;"&gt;6,544 B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FromDictionary20Fields&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Source&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;654 ns&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;&lt;strong&gt;1,520 B&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That's roughly &lt;strong&gt;5-6x faster&lt;/strong&gt; and about &lt;strong&gt;4x less allocated&lt;/strong&gt; on indexing, and when you're rebuilding the index on a large Umbraco site that adds up fast.&lt;/p&gt;
&lt;p&gt;The really nice touch is that these benchmark result tables now live in &lt;code&gt;&amp;lt;remarks&amp;gt;&lt;/code&gt; doc-comments right next to the benchmark code, so the numbers are versioned in the repo alongside the thing they measure. The agent did that bit too.&lt;/p&gt;
&lt;h2 id="was-it-actually-worth-it"&gt;Was it actually worth it?&lt;/h2&gt;
&lt;p&gt;For me the thing that makes it work is the measurement. Everything comes to me as a small, focused, draft PR with the numbers attached, so I can look at it, sanity check it, run CI, and decide in a couple of minutes. It's not zero effort - I still read every change before it goes anywhere near main - but the ratio is fantastic. I'm getting a steady stream of well-measured, single-purpose performance PRs on a library I care about, on paths I'd genuinely never have found the time to optimise by hand, and I've shipped real releases because of it. For a project I maintain around everything else, that's a pretty great deal.&lt;/p&gt;
&lt;p&gt;If you maintain a repo with a backlog you never get to, especially performance work that needs benchmarks to justify it, I'd recommend giving &lt;a href="https://github.com/githubnext/agentics"&gt;GitHub Next's agentics&lt;/a&gt; a go.&lt;/p&gt;
&lt;p&gt;You can see all of it out in the open on the &lt;a href="https://github.com/Shazwazza/Examine"&gt;Examine repo&lt;/a&gt; - the &lt;code&gt;[perf-improver]&lt;/code&gt; and &lt;code&gt;[efficiency-improver]&lt;/code&gt; PRs, the benchmark suite, and the releases they fed into. And there's a nice bonus here for me too: &lt;a href="https://examinex.online/"&gt;ExamineX&lt;/a&gt;, my managed, cloud-hosted Examine search offering, runs on this exact same internal plumbing. So all this work the agents have been doing to tighten up Examine's query and indexing hot paths feeds straight through into ExamineX - the underlying engine gets faster and leaner, and every ExamineX site gets those wins for free without changing a thing. If you'd rather have your search running as a managed service instead of hosting Lucene indexes on your own servers, that's what ExamineX is there for. 🙂&lt;/p&gt;
</description>
      <pubDate>Fri, 03 Jul 2026 17:45:20 Z</pubDate>
      <a10:updated>2026-07-03T17:45:20Z</a10:updated>
    </item>
  </channel>
</rss>