<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Malcolm</title>
    <description>The latest articles on DEV Community by Malcolm (@malcolmmcneely).</description>
    <link>https://dev.to/malcolmmcneely</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3963340%2F8732d342-40e3-4917-a3c5-5fcf049fe925.png</url>
      <title>DEV Community: Malcolm</title>
      <link>https://dev.to/malcolmmcneely</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/malcolmmcneely"/>
    <language>en</language>
    <item>
      <title>Nobody owns your AI</title>
      <dc:creator>Malcolm</dc:creator>
      <pubDate>Tue, 29 Sep 2026 08:14:11 +0000</pubDate>
      <link>https://dev.to/malcolmmcneely/nobody-owns-your-ai-74l</link>
      <guid>https://dev.to/malcolmmcneely/nobody-owns-your-ai-74l</guid>
      <description>&lt;p&gt;Many companies at this point have arrived at the same conclusion: that AI generated code can increase your engineering capabilities by leaps and bounds. How many of your developers are using tools such as Claude, Copilot, and Codex? What was your spend for the past 30 days? What skills did they use last week? Do your developers use the same skills or has each team researched and concocted different ways to do the same thing? These questions have been on my mind for the past few weeks. I think I have an answer: your AI engineering needs an owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  3 stages of AI adoption
&lt;/h2&gt;

&lt;p&gt;I see the industry at 3 distinct stages of adoption. The first two are about how people use AI, but only the third has an owner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Curious
&lt;/h3&gt;

&lt;p&gt;Individuals use AI on their own; everyone has their own prompts, skills, and habits. You might have an informal group who're particularly enthusiastic about AI, but by and large nothing is shared. Their stories and tasks are landing faster, so your organisation is improving little by little. Meanwhile your less excited or more sceptical developers are being left behind, and the gap between these two groups is widening.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adopted
&lt;/h3&gt;

&lt;p&gt;Your teams are now using AI together. They agree on standards and refine the skills in the repo and share habits. This is a great step forward from the last stage; your sceptical developers now have a channel for feedback, and feedback from the sceptic is incredibly valuable as I'll talk about later. As a whole, the team's capabilities improve.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI native
&lt;/h3&gt;

&lt;p&gt;There is a real person or team who owns the AI engineering capability. Skills/Plugins/MCP servers ship through one channel, rules are enforced and costs are measured. They work with other teams to improve AI for everyone: there's no difference between a core library which sits upstream from everything in your company and your AI capability.&lt;/p&gt;

&lt;h2&gt;
  
  
  Non-ownership costs you
&lt;/h2&gt;

&lt;p&gt;Without an owner you pay in two ways. The first is the questions I started with: nobody can say what your agents are doing or what they cost. The second is quality. Both apply to Adopted as much as Curious, because a team can agree standards without any kind of measurement. The quality cost is hard to quantify but the signals should not be ignored. Drops in quality don't show straight away.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/blog/products/ai-machine-learning/announcing-the-2025-dora-report" rel="noopener noreferrer"&gt;DORA, Sep 2025&lt;/a&gt; shows about 90% adoption rates of AI and that throughput is up but stability is down.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.faros.ai/blog/ai-acceleration-whiplash-takeaways" rel="noopener noreferrer"&gt;Faros, Apr 2026&lt;/a&gt; (vendor data) reports that bugs per developer increased by 54% as AI adoption grew, up from 9% in its earlier data.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://arxiv.org/abs/2511.04427" rel="noopener noreferrer"&gt;CMU, Jan 2026&lt;/a&gt; reports that across 806 repositories adopting Cursor, the velocity gains faded after two months. Static analysis warnings increased by 30% and code complexity by 41%.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why is it costing you
&lt;/h3&gt;

&lt;p&gt;In my view the reason is everyone's AI is someone else's side project. Skills go stale fast (&lt;a href="https://claude.com/blog/how-anthropic-enables-self-service-data-analytics-with-claude" rel="noopener noreferrer"&gt;Anthropic, Jun 2026&lt;/a&gt; saw analytical question accuracy decrease by 30% in a single month) and little inconsistencies in the generated code can go ignored.&lt;/p&gt;

&lt;p&gt;With AI, small problems snowball an order of magnitude faster. Your newly created balls of mud can take a massive engineering effort to untangle, and so can poor skill performance when you cannot measure what your agents are doing.&lt;/p&gt;

&lt;p&gt;The closest study I've found on ownership is &lt;a href="https://services.google.com/fh/files/misc/2025_dora_ai_capabilities_model.pdf" rel="noopener noreferrer"&gt;DORA's AI Capabilities Model, Sep 2025&lt;/a&gt;: AI only improved organisational performance where the internal platform was good, and a clear AI policy made the gains bigger. Someone has to own the platform and the policy, and DORA itself recommends naming long-term owners.&lt;/p&gt;

&lt;p&gt;There's one more thing that mostly goes unmentioned; &lt;a href="https://snyk.io/blog/toxicskills-malicious-ai-agent-skills-clawhub/" rel="noopener noreferrer"&gt;Snyk, Feb 2026&lt;/a&gt; (vendor data) found security flaws in 36.8% of 3,984 public skills from ClawHub and skills.sh.&lt;/p&gt;

&lt;h2&gt;
  
  
  What owning your AI engineering means
&lt;/h2&gt;

&lt;p&gt;An owner answers both costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;p&gt;Measure what your agents are doing, what it costs, and (most importantly) whether it helped. This isn't only about usage metrics; we need to see what errors agents encountered, check that the correct skills fired, and correlate bugs in production with the agent session that produced them so we can improve things.&lt;/p&gt;

&lt;h3&gt;
  
  
  Standardisation
&lt;/h3&gt;

&lt;p&gt;Stale skills and ignored inconsistencies are what happens when nobody enforces anything. LLMs are intrinsically non-deterministic so adding more text to a prompt is not good enough. Rules need to be enforced via tests and hooks. Skills need to be evaluated to ensure they produce a good output, and this needs to be determined for every new version of every model you are using. Skills and loops go through the owner's one channel, and nobody installs one straight from an unverified third party.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling
&lt;/h3&gt;

&lt;p&gt;A loop chains skills into a delivery process: spec to tickets, tickets to code, code to review, with checks between each step. Skills make individuals faster; a loop codifies your ways of working, and that is how you scale across teams.&lt;/p&gt;

&lt;p&gt;Going from individual skills to organisational loops is a big job. Someone has to decide the order the steps run in, write the scripts that check each step's output, keep it fast, evaluate the whole thing against every new model, and offer options so teams can decide how they steer the AI. A developer can write a skill in an afternoon but few build a loop as a side project.&lt;/p&gt;

&lt;p&gt;The pay-off is a loop that is deterministic even though the model inside it is not. The loop needs guardrails to ensure that your specs have been met in full: scripts that check facts. Small problems snowball, so those checks cannot rest on the agent's judgement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I have built
&lt;/h2&gt;

&lt;p&gt;For the past few weeks I have been building this for myself. &lt;a href="https://github.com/MalcolmMcNeely/skillworks" rel="noopener noreferrer"&gt;Skillworks&lt;/a&gt; is a plugin for coding agents with an agentic delivery loop, plus a dashboard called Studio. It is built by its own loop, so every change to Skillworks is made by the thing it is building. That is the fastest way I know to find out what does not work.&lt;/p&gt;

&lt;p&gt;It is one repo on one machine, so treat what follows as what I found when I built it, not results from a team of fifty.&lt;/p&gt;

&lt;p&gt;Studio reads the telemetry events coding agents already send. It lists every skill with how often it activated, what it cost, and which models and repositories it ran in. A Sessions view opens a single run and shows what the agent was given and what it did, step by step, including the errors it hit. Every commit the loop makes carries the id of the session that made it, so a bug in production can be traced back to the agent session that wrote it.&lt;/p&gt;

&lt;p&gt;Building it taught me things a vendor dashboard will not tell you. Telemetry sends the signed-in user's email address on every event by default (measured Sep 2026), so switching it on is a decision for legal before it is one for engineering. And of 27 companies I checked (Sep 2026), none publishes which skills their developers use. Some say they log it; nobody shows it. The data exists and nobody owns it.&lt;/p&gt;

&lt;p&gt;Rules live in the repo as data, and a skill turns them into architecture tests the team owns. Each test is proven red against a deliberate breach before it goes green on real code. I also measured what a rule in the agent's instructions is worth on its own (Sep 2026, 60 runs in four setups of 15): with no rule the agent followed it in 0 of 15 runs, and with the rule in 10 of 15. Better than nothing but nowhere near enforcement.&lt;/p&gt;

&lt;p&gt;The loop takes a spec, splits it into tickets, and builds each ticket test-first. Three reviews follow (standards, spec and architecture), each in a fresh session, so no reviewer marks its own work. A script, not the agent, runs the team's checks and reads the results. At the end, every item in the spec gets a verdict and a script counts them. Anything missing is built in one more round, and anything still missing stops the loop and goes to a person.&lt;/p&gt;

&lt;p&gt;The team owns the steering: its rules, its words, and what "green" means. The plugin owns the machinery.&lt;/p&gt;

&lt;p&gt;I'm still working on it. The aim is for Skillworks to cover observability, standardisation and scaling, so an owner has one place to run them from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing sceptics along
&lt;/h2&gt;

&lt;p&gt;The last job for an owner is bringing every team along, including the sceptics who were left behind at the Curious stage.&lt;/p&gt;

&lt;p&gt;Training and coaching do not reach everyone. You need to show something that works and have the numbers as proof. The loop lets you walk into a team, pick up a ticket and let them be the judge of what is produced; their criticisms will improve your AI for everyone else. Inconsistencies with API design, verbose comments, idempotency concerns, and more are all ways for agents to generate bad, messy code.&lt;/p&gt;

&lt;p&gt;This might also differ for every team and project, whether it is personal taste or something mechanical. They will want ways for the team to decide how to steer the agent but it should sit on top of a solid foundation: the loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Back to those questions
&lt;/h2&gt;

&lt;p&gt;How many of your developers use AI? What did it cost last month? Which skills ran last week, and did they help? Do your teams share them, or has each built its own?&lt;/p&gt;

&lt;p&gt;If nobody in your company can answer those, you are not short of tools. You are short of an owner.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>leadership</category>
      <category>softwareengineering</category>
      <category>productivity</category>
    </item>
    <item>
      <title>AI agentic workflows on large codebases</title>
      <dc:creator>Malcolm</dc:creator>
      <pubDate>Tue, 09 Jun 2026 06:54:49 +0000</pubDate>
      <link>https://dev.to/malcolmmcneely/ai-agentic-workflows-on-large-codebases-3275</link>
      <guid>https://dev.to/malcolmmcneely/ai-agentic-workflows-on-large-codebases-3275</guid>
      <description>&lt;p&gt;The &lt;a href="https://malcolmania.co.uk/writing/edict" rel="noopener noreferrer"&gt;first post&lt;/a&gt; went over some of its capabilities. Over the past week Edict went v1.0, adding cursors for reading projections after command dispatch (to close some eventual-consistency gaps), a new type of projection that holds state inside the Orleans grain directly instead of a table, saga timeouts, schedules, an improved skills package and MCP server that ships with Edict, and more.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/MalcolmMcNeely/Edict" rel="noopener noreferrer"&gt;Edict&lt;/a&gt; has now grown to over 75,000 lines of code and more than 1000 tests, and contains several deep mechanisms that have been fixed, broken, and fixed again. It is well past the point where I can hold all of Edict in my head.&lt;/p&gt;

&lt;p&gt;This post is about working with AI on large codebases, which I expect to be the first problem most software engineers have to solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  The context problem
&lt;/h2&gt;

&lt;p&gt;Years ago I was talking to a PhD candidate whose area of research was Natural Language Processing (NLP). He explained to me that one of the most difficult NLP problems was context. If a colleague says they need to pop out to pick their kids up from school, a scene can form in your head: one with a school, the layout of the road, people waiting, walking, driving, the environs. You may never have seen the school your colleague mentioned, but you can form a rich scene from your accumulated experience and use it to drive the rest of the conversation with a shared understanding.&lt;/p&gt;

&lt;p&gt;LLMs ingeniously dodge this entire issue by making it your problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just a word-probability machine
&lt;/h2&gt;

&lt;p&gt;Strip away the chat window and a Large Language Model (LLM) is doing one thing: predicting the next token. Give it a run of text and it returns a probability distribution over what comes next, samples one, appends it, and repeats.&lt;/p&gt;

&lt;p&gt;Companies like OpenAI and Anthropic then beat it into shape using techniques like supervised fine-tuning and reinforcement learning, which tune those probabilities in meaningful ways.&lt;/p&gt;

&lt;p&gt;That is why Claude is always telling me "Good framing" or "You've spotted...". It even called me "Bold" on one occasion. The probabilities have been shaped to reward &lt;em&gt;me&lt;/em&gt;; in other words the predictor is being steered towards outputs deemed preferable to people. This includes a proclivity to always complete a given task, making assumptions to do so, which can be dangerous when writing code.&lt;/p&gt;

&lt;p&gt;What matters here is knowing where the gap is between you and the LLM, and how &lt;a href="https://arxiv.org/html/2605.08837v1" rel="noopener noreferrer"&gt;they are not the same thing&lt;/a&gt;. What it builds is statistical. When your colleague gave you, the human, the input of the school run, you assembled a scene out of a life of standing at school gates. When an LLM is given the same input as text, it assembles associations from text about school runs. Models lean on word association and underproduce exactly the emotional and physical detail a person supplies for free.&lt;/p&gt;

&lt;p&gt;That is why it lays context at your feet: it cannot fetch yours, so you have to hand it over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Side note: affectations and non-determinism
&lt;/h3&gt;

&lt;p&gt;Because the interface is natural language, it is tempting to talk to an LLM like a person. I have found that effective, but two things are worth knowing.&lt;/p&gt;

&lt;p&gt;First, non-determinism: the same prompt can give you different answers on different runs. It is a probability engine, not a lookup. For a codebase that means you cannot assume yesterday's good result repeats exactly.&lt;/p&gt;

&lt;p&gt;Second, and more useful day to day: the model is acutely sensitive to how you frame things. "My CTO recommended this technology" or "I'm sceptical this will work" can produce entirely different outcomes, because the model tends to mirror the stance you hand it. The trap is telegraphing the answer you want. I stay factual and state goals plainly, not because emotion is forbidden, but because it stops me biasing the reply. It is part of why &lt;a href="https://developers.redhat.com/articles/2025/10/22/how-spec-driven-development-improves-ai-coding-quality#how_to_get_started_with_spec_coding" rel="noopener noreferrer"&gt;spec-driven development&lt;/a&gt; works so well: a spec is intent stated without a thumb on the scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The purpose of an agentic workflow is context
&lt;/h2&gt;

&lt;p&gt;The workflow I use is largely based on &lt;a href="https://github.com/mattpocock/skills" rel="noopener noreferrer"&gt;Matt Pocock's skills&lt;/a&gt; with a few tweaks. There is a lot of great stuff in there, but the absolute minimum I use is four commands, run in order.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;/grill-with-docs&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This is where the context is established. It does four things.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reads and maintains a &lt;code&gt;CONTEXT.md&lt;/code&gt;&lt;/strong&gt; (or a &lt;code&gt;CONTEXT-MAP.md&lt;/code&gt; plus project-specific &lt;code&gt;CONTEXT.md&lt;/code&gt;s for larger solutions), which holds your domain and its relationships. If you look at &lt;a href="https://github.com/MalcolmMcNeely/Edict/blob/main/CONTEXT.md" rel="noopener noreferrer"&gt;Edict's &lt;code&gt;CONTEXT.md&lt;/code&gt;&lt;/a&gt; and are familiar with CQRS / event-driven systems, none of it should surprise you. But this is how I hand my context to every Claude session.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reads and maintains Architectural Decision Records (ADRs)&lt;/strong&gt; for non-obvious, hard-to-reverse decisions. This is how we address bad assumptions made by the LLM. &lt;a href="https://github.com/MalcolmMcNeely/Edict/blob/main/docs/adr/0051-eventid-assigned-once-at-enqueue.md" rel="noopener noreferrer"&gt;ADR 0051&lt;/a&gt;, for example, details how event IDs are stamped once at enqueue time and persist through the idempotency, claim-check, and outbox mechanisms. Changing event IDs would cause havoc for Edict's telemetry and break several mechanisms, including the idempotency layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Walks Edict's surfaces&lt;/strong&gt; (my addition). Across 45 projects, Edict has:

&lt;ul&gt;
&lt;li&gt;Source generation&lt;/li&gt;
&lt;li&gt;Roslyn analysers&lt;/li&gt;
&lt;li&gt;Benchmarks for performance regression&lt;/li&gt;
&lt;li&gt;A skills package and MCP tooling for other developers' LLM sessions&lt;/li&gt;
&lt;li&gt;An in-memory testing library, &lt;code&gt;Edict.Testing&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;A gold-standard sample app demonstrating every feature of Edict, including the use of &lt;code&gt;Edict.Testing&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At some point it became a nightmare to keep everything aligned, and I found that adding this as another step kept all my ducks in a row.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Walks every branch of the decision tree.&lt;/strong&gt; This is the essential guard against the LLM making bad guesses and assumptions. A mistake made here snowballs into every later step, as well as into future work.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;/to-prd&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Takes the context you established with &lt;code&gt;/grill-with-docs&lt;/code&gt;, including every decision you made, analyses it along several axes (schema changes, architectural changes), and works out the implementation details while respecting your domain language and any relevant ADRs.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;/to-issues&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Takes the resulting Product Requirements Document (PRD) and turns it into vertical slices ready for implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;/tdd&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Takes a vertical slice and implements it using a red-green-refactor method. &lt;a href="https://www.youtube.com/watch?v=hYZdIwFIy-c" rel="noopener noreferrer"&gt;Matt has an excellent video&lt;/a&gt; on why this works so well with LLMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;At first it might be tempting to think "I need a better prompt", and that works for small projects. It does not scale to enterprise systems, or even a mere 75,000 lines of code.&lt;/p&gt;

&lt;p&gt;If you accept what an LLM is, a powerful predictor with no access to the scene in your head, the problem shifts: how do you carry your context across every session, for an entire codebase? &lt;code&gt;CONTEXT.md&lt;/code&gt; gives it the domain. ADRs give it the decisions it would otherwise guess at. Walking the surfaces and the decision tree catches the bad assumptions before they snowball. PRD, issues, and TDD turn that shared understanding into code, in slices small enough to review (if that's your thing).&lt;/p&gt;

&lt;p&gt;The vocabulary differs, but &lt;a href="https://github.blog/ai-and-ml/github-copilot/how-to-build-reliable-ai-workflows-with-agentic-primitives-and-context-engineering/" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, &lt;a href="https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents" rel="noopener noreferrer"&gt;Anthropic&lt;/a&gt;, &lt;a href="https://martinfowler.com/articles/exploring-gen-ai/context-engineering-coding-agents.html" rel="noopener noreferrer"&gt;Thoughtworks&lt;/a&gt;, and many more are all converging on this concept of Context Engineering: spending human effort up-front to establish durable context and constraints so LLMs can take the wheel without drifting into dangerous territory.&lt;/p&gt;

&lt;p&gt;Edict is past the point where I can hold it in my head. The workflow is how I hold it instead. The model never understands the codebase the way I do, and it does not need to, as long as I keep laying the context at its feet.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>dotnet</category>
      <category>csharp</category>
    </item>
    <item>
      <title>Edict: a CQRS framework for .NET, built in two weeks with Claude</title>
      <dc:creator>Malcolm</dc:creator>
      <pubDate>Tue, 02 Jun 2026 11:12:38 +0000</pubDate>
      <link>https://dev.to/malcolmmcneely/edict-a-cqrs-framework-for-net-built-in-two-weeks-with-claude-3i09</link>
      <guid>https://dev.to/malcolmmcneely/edict-a-cqrs-framework-for-net-built-in-two-weeks-with-claude-3i09</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Most of my career was making things the same."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A staff engineer I worked with said that to me once. He was talking about a different framework at the time, but it was exactly the kind of problem &lt;strong&gt;Edict&lt;/strong&gt; solves. That conversation stuck. It was a big driver in building this.&lt;/p&gt;

&lt;p&gt;Edict is a CQRS framework for .NET on top of Microsoft Orleans. It absorbs the plumbing every event-driven team rewrites by hand: idempotency keys for at-least-once redeliveries, an outbox for atomic state and events, trace propagation across stream hops, a queryable dead-letter projection.&lt;/p&gt;

&lt;p&gt;I built it in two weeks. Claude wrote almost every line of code; I drove the design, reviewed every change, and corrected course when needed.&lt;/p&gt;

&lt;p&gt;This post is the short tour. What it does, how it was built, whether it's worth your time.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this was built
&lt;/h2&gt;

&lt;p&gt;Two weeks of focused sessions, almost entirely agentic. Claude wrote the code; I drove the design and reviewed every change.&lt;/p&gt;

&lt;p&gt;The workflow was loosely modelled on &lt;a href="https://github.com/mattpocock/skills" rel="noopener noreferrer"&gt;Matt Pocock's skills&lt;/a&gt;: each feature began as a PRD on the issue tracker, got broken into vertical slices, and landed via red-green-refactor TDD. Domain language lives in &lt;code&gt;CONTEXT.md&lt;/code&gt;; load-bearing decisions live in ADRs. Whenever I caught myself making the same correction twice, I codified it as a project skill or analyzer.&lt;/p&gt;

&lt;p&gt;This is not "look what AI can do alone." Claude is a powerful implementation surface but it needed a human with strong architectural opinions and a clear domain language to be useful. This is what agentic development looks like when the human has done the design work.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/dotnet/orleans/" rel="noopener noreferrer"&gt;Microsoft Orleans&lt;/a&gt; is great. It gives you a programming model where every entity in your system has exactly one in-memory home, on one node, on one thread at a time. The whole class of &lt;em&gt;"two pods, same order, race condition"&lt;/em&gt; problems just disappears.&lt;/p&gt;

&lt;p&gt;But Orleans is a runtime, not an opinion. The moment you want CQRS, event-driven flows, sagas, projections, or an outbox, you start writing the same plumbing every team writes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Idempotency keys for at-least-once redeliveries&lt;/li&gt;
&lt;li&gt;Trace propagation across async stream hops&lt;/li&gt;
&lt;li&gt;Atomic commit of state &lt;em&gt;and&lt;/em&gt; the events you raised&lt;/li&gt;
&lt;li&gt;A dead-letter table for the poison message that just took out your aggregate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of it is hard. All of it is repetitive. And most teams get at least one of them subtly wrong.&lt;/p&gt;

&lt;p&gt;Edict's bet is that this is a framework's job, not yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it feels like
&lt;/h2&gt;

&lt;p&gt;A command handler is one method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderCommandHandler&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EdictCommandHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EdictCommandResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PlaceOrderCommand&lt;/span&gt; &lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Status&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OrderStatus&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;Raise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;OrderPlacedEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FromResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EdictCommandResult&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;EdictCommandResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Accepted&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A validator that gates that handler is one constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderPlaceCommandValidator&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EdictCommandValidator&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;PlaceOrderCommand&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;OrderPlaceCommandValidator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;
        &lt;span class="nf"&gt;RuleFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CustomerReference&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;NotEmpty&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;WithErrorCode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"customer_reference_required"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It runs in the same activation turn as the handler, before &lt;code&gt;HandleAsync&lt;/code&gt;. A failure short-circuits to &lt;code&gt;EdictCommandResult.Rejected&lt;/code&gt; with &lt;code&gt;customer_reference_required&lt;/code&gt; as the rejection code; the handler never sees an invalid command and no state mutation occurs.&lt;/p&gt;

&lt;p&gt;An event handler is also one method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;partial&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OrderEmailHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IEmailSender&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EdictEventHandler&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt; &lt;span class="nf"&gt;HandleAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderPlacedEvent&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendConfirmation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OrderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both sides of an event-driven flow. No Orleans interfaces. No stream wiring. No serialization attributes. No idempotency code. Source generators connect &lt;code&gt;HandleAsync&lt;/code&gt; to the right stream based on its parameter type, and the base class deduplicates redeliveries by &lt;code&gt;EventId&lt;/code&gt; before ever calling you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole vocabulary
&lt;/h2&gt;

&lt;p&gt;There are six things you write. That's it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;What you write&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Command handler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The aggregate's invariant. Receives a command, mutates state, raises events.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Event handler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A side effect. Receives an event, does something (send email, call API).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Saga&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A long-running coordinator. Reacts to events, sends commands.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Projection builder&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A read model. Receives events, writes a queryable row.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sender&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How callers reach into the system to issue a command.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stream&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A topic identity. Where events flow.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Everything else is the framework's problem: routing, serialization, the outbox, retries, dead-lettering, tracing, the parts of Orleans you'd rather not type.&lt;/p&gt;

&lt;h2&gt;
  
  
  The flow end to end
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzu985z2uqu570fes23hw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzu985z2uqu570fes23hw.png" alt="Edict Flow Diagram" width="800" height="317"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fro701ux511mtjm9ovvmv.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fro701ux511mtjm9ovvmv.gif" alt="Demo Application" width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One OpenTelemetry trace covers the whole graph. If any handler throws, the failure lands in a dead-letter projection you can query. The aggregate keeps accepting commands.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favhewv1g2fqb5xs04t9q.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Favhewv1g2fqb5xs04t9q.gif" alt="Live Metrics Demo" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick your substrate
&lt;/h2&gt;

&lt;p&gt;The same handler code runs on either of two reference pairings, both passing the same conformance battery:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Substrate&lt;/th&gt;
&lt;th&gt;Streaming&lt;/th&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Azure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Azure Queue Storage&lt;/td&gt;
&lt;td&gt;Azure Table Storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Kafka + Postgres&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apache Kafka&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Adding a third is a matter of implementing the substrate seam. The framework itself doesn't care which queue or store sits underneath.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing without containers
&lt;/h2&gt;

&lt;p&gt;Edict ships an in-memory test app so you can exercise an entire command → event → saga → projection flow in-process. No Orleans cluster, no Azurite, no Docker. Three lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;var&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;EdictTestApp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StartAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;WithConsumer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;OrderCommandHandler&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Assembly&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SendAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;PlaceOrderCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"REF-001"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Drain&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;Verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetSagaProgress&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;OrderPaymentSaga&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;OrderPaymentProgress&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;orderId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Chaos is on by default.&lt;/strong&gt; Duplicate redeliveries and bounded reorder are simulated deterministically, so every test you write exercises the at-least-once guarantees production has to tolerate. No setup required.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-assisted development, built in
&lt;/h2&gt;

&lt;p&gt;Edict's whole philosophy is that the framework should absorb the things every team rewrites by hand, so feature devs can focus on feature code. The MCP server and Claude Code skill bundle that ship with Edict apply that same principle to AI tooling: consumers should be able to use Claude productively against Edict without first writing scaffolding to teach the agent what Edict is.&lt;/p&gt;

&lt;p&gt;Ask Claude &lt;em&gt;"where does &lt;code&gt;PlaceOrderCommand&lt;/code&gt; get routed?"&lt;/em&gt; and it calls &lt;code&gt;edict_describe_silo_wiring&lt;/code&gt; instead of guessing from grep results. Ask it to add a saga and it calls &lt;code&gt;edict_list_route_keys&lt;/code&gt; to see which &lt;code&gt;RouteKey&lt;/code&gt; Guids are already taken, so it generates a fresh one instead of colliding. Ask it why a dead-letter behaves a particular way and it calls &lt;code&gt;edict_lookup_adr&lt;/code&gt; and returns the source decision.&lt;/p&gt;

&lt;p&gt;The two pieces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;edict-mcp&lt;/code&gt;&lt;/strong&gt; is an MCP server that exposes six tools the agent can call against your live solution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;edict-skills&lt;/code&gt;&lt;/strong&gt; is a Claude Code skill bundle that knows &lt;em&gt;when&lt;/em&gt; to call each tool&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together they mean the agent works from your actual code, not from a guess about what an event-driven framework probably looks like:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Skill (when it fires)&lt;/th&gt;
&lt;th&gt;What the agent stops guessing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;edict-authoring&lt;/strong&gt; (adding a handler, saga, or projection)&lt;/td&gt;
&lt;td&gt;Which &lt;code&gt;RouteKey&lt;/code&gt; Guids are taken, which handlers already exist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;edict-silo-wiring&lt;/strong&gt; (touching any &lt;code&gt;AddEdict*&lt;/code&gt; call)&lt;/td&gt;
&lt;td&gt;Which substrate is wired in &lt;code&gt;Program.cs&lt;/code&gt;, which extensions are missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;edict-contracts&lt;/strong&gt; (attribute or wire-format questions)&lt;/td&gt;
&lt;td&gt;What a &lt;code&gt;Stream&lt;/code&gt; is, why &lt;code&gt;[Union]&lt;/code&gt; is banned (with the source ADR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;edict-diagnostics&lt;/strong&gt; (debugging dead-letter, outbox, or trace issues)&lt;/td&gt;
&lt;td&gt;Why the framework behaves the way it does, with the decision record attached&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I think this is going to matter more over the next year, not less. The frameworks that work well with AI tools are the ones that tell the agent the truth about themselves. That's a lot easier to do for the framework author than for every consumer team to invent on their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  When it fits
&lt;/h2&gt;

&lt;p&gt;Edict is probably worth a look if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're already on Orleans, or you've been weighing it up&lt;/li&gt;
&lt;li&gt;You want CQRS and event-driven flows without handwriting the plumbing&lt;/li&gt;
&lt;li&gt;You like writing C# and want one programming model across every entity in the system&lt;/li&gt;
&lt;li&gt;You're curious what agentic development can produce when the human in the loop knows the design space cold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's probably &lt;strong&gt;not&lt;/strong&gt; for you if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A REST API over EF Core is enough. You genuinely don't need this.&lt;/li&gt;
&lt;li&gt;You can't move to .NET 10&lt;/li&gt;
&lt;li&gt;You need a battle-tested production framework today. Edict is portfolio quality. The design is solid and the test coverage is real, but no one is running it at a Fortune 500 yet, including me.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'd rather you know that up front than find out later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/MalcolmMcNeely/Edict" rel="noopener noreferrer"&gt;https://github.com/MalcolmMcNeely/Edict&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Getting started:&lt;/strong&gt; &lt;a href="https://github.com/MalcolmMcNeely/Edict/blob/main/docs/usage/getting-started.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/usage/getting-started.md&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Sample app:&lt;/strong&gt; clone the repo, run the Aspire AppHost, and watch commands flow through to a Blazor dashboard in real time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reasoning:&lt;/strong&gt; every non-obvious design decision lives in &lt;a href="https://github.com/MalcolmMcNeely/Edict/tree/main/docs/adr" rel="noopener noreferrer"&gt;&lt;code&gt;docs/adr/&lt;/code&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you spot something missing, surprising, or wrong, a GitHub issue is the fastest way to reach me. I'm also &lt;a href="https://www.linkedin.com/in/malcolm-mcneely-programmer" rel="noopener noreferrer"&gt;on LinkedIn&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thanks for reading.&lt;/p&gt;

</description>
      <category>dotnet</category>
      <category>ai</category>
      <category>cqrs</category>
      <category>csharp</category>
    </item>
  </channel>
</rss>
