<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:discourse="http://discourse.org/rss/modules/discourse/"><channel><title>Ricardo Cunha on DevBlog</title><link>https://devblog.criticalmanufacturing.com/author/ricardo-cunha/</link><description>Recent content in Ricardo Cunha on DevBlog</description><image><url>https://devblog.criticalmanufacturing.com/uploads/og.webp</url><link>https://devblog.criticalmanufacturing.com/uploads/og.webp</link></image><generator>Hugo -- gohugo.io</generator><language>en-us</language><copyright>A template by [Heksagon](https://www.heksagon.net). Implemented for [Critical Manufacturing](https://www.criticalmanufacturing.com/).</copyright><lastBuildDate>Fri, 03 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://devblog.criticalmanufacturing.com/author/ricardo-cunha/index.xml" rel="self" type="application/rss+xml"/><item><title>Medical Devices Template 1.3.0: What's New</title><link>https://devblog.criticalmanufacturing.com/blog/20260703_meddev_template_1_3_0/</link><pubDate>Fri, 03 Jul 2026 00:00:00 +0000</pubDate><dc:creator>Ricardo Cunha</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20260703_meddev_template_1_3_0/</guid><description>This post walks through the new features and enhancements shipped in Medical Devices Template 1.3.0</description><content:encoded><![CDATA[<hr>
<p>In June 2026, the <strong>Medical Devices Template Team</strong> released version <strong>1.3.0</strong>. This is a minor release, but it brings a meaningful set of enhancements to two of the areas that matter most in a regulated manufacturing environment — electronic Device History Record (eDHR) validation and Continuous Flows. In addition, a new Assemble mode has been introduced and several smaller enhancements were also delivered, addressing recurring requests from implementation projects.</p>
<p>In this post, we&rsquo;ll walk through the new features and enhancements shipped in 1.3.0, with a closer look at the most impactful ones: <strong>eDHR Validation and Sign-Off</strong>, <strong>Automatic Explicit Add</strong>, and a handful of <strong>Continuous Flows</strong> improvements.</p>
<h2 id="summary-of-new-features-and-enhancements">Summary of New Features and Enhancements</h2>
<p>The table below summarizes what&rsquo;s new in Medical Devices Template 1.3.0.</p>
<table>
<thead>
<tr>
<th>Feature</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>eDHR Validation and Sign-Off</strong></td>
<td>Automatically associates Materials to a Validation Protocol and generates/attaches Stimulsoft-based reports at configured Steps in the production flow.</td>
</tr>
<tr>
<td><strong>Automatic Explicit Add</strong></td>
<td>New Manual Assemble mode that pre-selects consumable batches from Consumable Feeds, with or without operator confirmation.</td>
</tr>
<tr>
<td><strong>Continuous Flows — Rework / Temporary Off-Flow rank re-evaluation</strong></td>
<td>Extends the automatic re-evaluation of Material ranks to standard Rework and Temporary Off-Flow scenarios.</td>
</tr>
<tr>
<td><strong>Label Reconciliation with Splits &amp; Merges</strong></td>
<td>Supports label verification and reconciliation across a full Continuous Flow material group, not just individual Materials.</td>
</tr>
<tr>
<td><strong>Skip duplicate Protocol Instance creation on DC Limit Set violation</strong></td>
<td>Prevents multiple Protocol Instances from being opened for the same material group when a Data Collection Limit Set is repeatedly violated.</td>
</tr>
</tbody>
</table>
<p>The release also includes a number of bug fixes, mostly related to Continuous Flows edge cases (tail termination, merge logic, rank consistency on move-next, and rework counters).</p>
<p>You can find the full list of work items in this minor release in the <a href="https://info.criticalmanufacturing.com/releases/industry/medical/version_1_3/version_1_3_0/" target="_blank" rel="noopener">Release Notes</a>.</p>
<h2 id="edhr-validation-and-sign-off">eDHR Validation and Sign-Off</h2>
<p>Anyone that has worked on a Medical Devices implementation project knows that generating and signing off a Material/Device History Record is a core, non-negotiable requirement. Before this feature, either reports would need to be manually generated and attached as evidence in MES or similar automatic behavior would need to be designed and implemented on a per-project basis.</p>
<p>The new <strong>eDHR Validation and Sign-Off</strong> feature turns this into a template configuration-driven capability. In short, it lets you:</p>
<ul>
<li>Associate a Material (or a group of Materials) to a Validation Protocol, automatically, as it reaches a specific Step.</li>
<li>Generate a Stimulsoft-based report to PDF and attach it to that Protocol Instance, also automatically triggered by the flow.</li>
</ul>
<p>
<img src="/blogPosts/posts/20260703_meddev_template_1_3_0/edhrvalidation_meddevmaterialprotocolcontext.png" alt="MedDevMaterialProtocolContext Smart Table" />

</p>
<p>The association and the report generation don&rsquo;t have to happen at the same Step. A very common pattern is to associate Materials with a Protocol right at the start of the flow, and only generate/attach the eDHR report at the very end, once all manufacturing data is available. This is all configured in the <code>MedDevMaterialProtocolContext</code> Smart Table (see screenshot above), where you define, per Step:</p>
<ul>
<li><strong>Operation</strong> — either <code>AssociateMaterial</code> (just links the Material to the Protocol Instance) or <code>AttachDocument</code> (generates the report and attaches it).</li>
<li><strong>GroupingProperty</strong> (or a custom <code>GroupingRule</code>) — how Materials are grouped into the same Protocol Instance: by Name, Production Order, or Manufacturer Lot Number. The latter is particularly relevant for Continuous Flows scenarios, where validation typically happens at lot level rather than per individual Material.</li>
<li><strong>Report</strong> and <strong>Report Parameters</strong> — which Stimulsoft report to render, and any filter parameters, which can even reference placeholder expressions such as <code>{{Material.Name}}</code> or <code>{{Material.ManufacturerLotNumber}}</code> to be resolved dynamically at runtime.</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;MaterialName&#34;</span>: <span style="color:#e6db74">&#34;{{Material.Name}}&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;LotNumber&#34;</span>: <span style="color:#e6db74">&#34;{{Material.ManufacturerLotNumber}}&#34;</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">&#34;ProductName&#34;</span>: <span style="color:#e6db74">&#34;{{Material.Product.Name}}&#34;</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Under the hood, report generation is asynchronous: an <code>Integration Entry</code> of type <code>GenerateAndAttachReport</code> is created, and a DEE Rule takes care of rendering the report and attaching the resulting PDF to the Protocol Instance.</p>
<p>A couple of details worth highlighting:</p>
<ul>
<li>The <code>MaterialSelectionRule</code> column lets you filter which Materials in a group actually trigger report generation — for example, only the <strong>Tail</strong> material in a Continuous Flow lot, using the out-of-the-box <code>MedDevCheckSourceMaterialIsTailMaterial</code> rule, avoiding unnecessary report generations for every Material in the group. Since this is a Rule, you can also implement your own custom logic if needed.</li>
<li>The <code>Override</code> flag controls whether a newly generated report replaces an existing attachment for the same report path, or whether generation is simply skipped if one already exists.</li>
<li>If needed, <code>ValidateReplication</code> can be enabled to ensure the report is only generated once the material&rsquo;s last Service History ID (SHID) has been replicated to the ODS — useful when the report relies on OData/ODS-backed data.</li>
</ul>
<p>
<img src="/blogPosts/posts/20260703_meddev_template_1_3_0/edhrvalidation_sequence.png" alt="eDHR Validation feature diagram" />

</p>
<p>To conclude this topic, the next version will include a <strong>on-demand report regeneration</strong> feature, directly from the Protocol Instance page. If a report needs to be reissued — say, after a data correction — an authorized user will be able to trigger a new generation for any previously attached report, without needing to reprocess the entire flow.</p>
<h2 id="automatic-explicit-add">Automatic Explicit Add</h2>
<p>An operator manually picking source Materials for an manual Assemble operation is repetitive, error-prone, and adds unnecessary time to an operation that, in most cases, follows a predictable FIFO pattern from a set of consumable feeders. While automatic consumption could be used in most cases, for <code>Explicit Add</code> scenarios the operator still needs to manually select the source Materials, which is not ideal.</p>
<p><strong>Automatic Explicit Add</strong> is a new feature that combines the benefits of semi-automatic consumption with the flexibility of manual selection. When a Material sits at a Step with an <code>Explicit Add</code> BOM, the operator can trigger a new <strong>Automatic Explicit Add</strong> wizard instead of the standard Assemble flow. The system calculates the consumption plan in the backend, resolving, for each BOM Product, which Materials from the Resource&rsquo;s Consumable Feeds should be consumed — following FIFO order — until the required quantity is fulfilled. In a real scenario, this means the operator can attach the consumable materials to the feeders during <strong>Resource Setup</strong>; the system will then automatically select the right batches for consumption, without the operator needing to manually pick them.</p>
<p>The wizard exposes a single new flag: <strong>Skip Confirmation</strong>.</p>
<ul>
<li><strong>With Confirmation (OFF)</strong> — the system calculates the plan and presents it in the standard BOM confirmation step, so the operator can review the selected source Materials and quantities before confirming.</li>
<li><strong>Without Confirmation (ON)</strong> — the confirmation step is skipped entirely, and the standard <code>AssembleMaterial</code> service is called directly with the computed plan.</li>
</ul>
<p>
<img src="/blogPosts/posts/20260703_meddev_template_1_3_0/automaticexplicitadd_skipconfirmation.png" alt="Automatic Explicit Add - Skip Confirmation flag" />

</p>
<p>The following list summarizes the backend steps that happen when an operator opens the Automatic Explicit Add wizard:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>1. Resolve Material + processing Resource
</span></span><span style="display:flex;"><span>2. Load current BOM, determine eligible BOM Products
</span></span><span style="display:flex;"><span>3. Calculate required quantity per BOM Product
</span></span><span style="display:flex;"><span>4. Resolve Consumable Feeds from the Resource
</span></span><span style="display:flex;"><span>5. Read queued Materials in the Consumable Feeds
</span></span><span style="display:flex;"><span>6. Apply FIFO consumption planning
</span></span><span style="display:flex;"><span>7. Confirm (or auto-call AssembleMaterial)
</span></span></code></pre></div><p>One consumption detail worth calling out: if the first available Material in a feed doesn&rsquo;t have enough quantity to fulfill a BOM Product&rsquo;s requirement, the system doesn&rsquo;t stop there — it continues consuming subsequent Materials of the same Product, in FIFO order, until the quantity is met (i.e., <strong>Multi-Material Consumption</strong>). And if, at any point, the available quantity across the feed is simply not enough, the operation is blocked entirely — regardless of the confirmation mode — with a clear message indicating which BOM Product(s) are short.</p>
<blockquote>
<p>ℹ️  <strong>Skip Confirmation default value</strong><br/>
The default behavior of the <strong>Skip Confirmation</strong> flag can be set per project through the standard <code>GUIElementDefaultValue</code> table (<code>Context = MedDev.AutomaticExplicitAdd</code>, <code>Element = ShowConfirmation</code>).</p>
</blockquote>
<h2 id="other-enhancements">Other Enhancements</h2>
<p>Alongside the two headline features, 1.3.0 delivers more targeted improvements, mostly around Continuous Flows and Data Collection handling.</p>
<h3 id="rework--temporary-off-flow-in-continuous-flows">Rework / Temporary Off-Flow in Continuous Flows</h3>
<p>Continuous Flows already supported automatic re-evaluation of Material ranks (<code>Head</code>, <code>Middle</code>, <code>Tail</code>) as chunks move through the flow. In 1.3.0, this re-evaluation logic is extended to cover Materials that temporarily leave the main flow through <strong>Rework</strong> or a <strong>Temporary Off-Flow</strong> path.</p>
<p>While a Material is out of the main flow, it&rsquo;s assigned a dedicated <code>InRework</code> rank. When it returns, MES determines its new rank — <code>Head</code>, <code>Middle</code>, or <code>Tail</code> — based on its position relative to the remaining Materials still in the flow, following the same logic already used for regular flow progression. This means Continuous Flow processes that rely on Rework or Off-Flow paths no longer need custom logic to keep material ranks consistent after a return.
The following diagram shows the implemented rank re-evaluation logic for a Continuous Flow lot returning from a Rework path.</p>
<p>
<img src="/blogPosts/posts/20260703_meddev_template_1_3_0/continuousflows_returnrework.png" alt="Continuous Flow Rework rank re-evaluation" />

</p>
<h3 id="label-reconciliation-with-splits--merges">Label Reconciliation with Splits &amp; Merges</h3>
<p>The Label Reconciliation feature — which tracks printed labels through verification and final reconciliation — now works properly with Continuous Flow&rsquo;s Splits &amp; Merges. Previously, verification counts (good, scrapped, first/last label checks) were only tracked at the individual Material level, which didn&rsquo;t map well to a Continuous Flow scenario where a lot is split into multiple parallel chunks.</p>
<p>With this enhancement, printed label counts are recorded and reconciled against the <strong>Main Material</strong> of the Continuous Flow group, regardless of which specific chunk triggered the print or the verification step. When a Protocol Instance is opened for label verification (manually, or automatically due to a violation), all active Materials sharing the same Manufacturer Lot Number are automatically associated with it — keeping the full lot in sync for sign-off purposes.</p>
<h3 id="skip-duplicate-protocol-instance-creation-on-dc-limit-set-violation">Skip Duplicate Protocol Instance Creation on DC Limit Set Violation</h3>
<p>A smaller, but very practical fix for a common pain point: when a Data Collection Limit Set is violated repeatedly for the same Material group, the system used to open a new Protocol Instance on every violation. In regulated environments this quickly becomes noisy — multiple open Protocol Instances for what is essentially the same underlying non-compliance.</p>
<p>1.3.0 introduces the <code>MedDevIgnoreInstanceCreationIfAlreadyAssigned</code> Protocol attribute. When enabled, if the violating Material (or, for Continuous Flows, its group) is already associated with an open instance of the same Protocol, the system suppresses the automatic creation of a new Protocol Instance and simply keeps the violation tied to the existing one.</p>
<h2 id="wrap-up">Wrap Up</h2>
<p>Version 1.3.0 continues the trend we&rsquo;ve seen in recent Medical Devices Template releases: closing the gap between generic MES capabilities and the very specific compliance and traceability needs of medical device manufacturing, particularly around Continuous Flows and eDHR/Report Validation.</p>
<p>Feel free to reach out to the Medical Template Team with questions or feedback on any of these features!</p>
<h2 id="public-documentation">Public Documentation</h2>
<ul>
<li><a href="https://help.criticalmanufacturing.com/11.3/userguide/industrytemplates/medical/features/edhrvalidation/" target="_blank" rel="noopener">Feature Guide — eDHR Validation and Sign-off</a></li>
<li><a href="https://help.criticalmanufacturing.com/11.3/userguide/industrytemplates/medical/features/automaticexplicitadd/" target="_blank" rel="noopener">Feature Guide — Automatic Explicit Add</a></li>
<li><a href="https://help.criticalmanufacturing.com/11.3/userguide/industrytemplates/medical/features/continuousflows/" target="_blank" rel="noopener">Feature Guide — Continuous Flows (updated)</a></li>
<li><a href="https://help.criticalmanufacturing.com/11.3/userguide/industrytemplates/medical/features/labelreconciliation-labelprintingtracking/" target="_blank" rel="noopener">Feature Guide — Label Reconciliation (updated)</a></li>
<li><a href="https://info.criticalmanufacturing.com/releases/industry/medical/version_1_3/" target="_blank" rel="noopener">Information Center — Release Notes + Sample Master Data</a></li>
</ul>
<hr>
<h2 id="author">Author</h2>
<h3 id="hi-my-name-is-ricardo-cunha-">Hi! My name is Ricardo Cunha. ✌️</h3>
<p>I joined Critical Manufacturing back in 2013. I started my journey as a Software Engineer, progressed to Tech Lead, and currently serve as Engineering Lead in the Medical/Life Sciences segment in Solutions Delivery area, as well as Team Lead for the Medical/Life Sciences template team.</p>
<p>You can check me on <a href="https://www.linkedin.com/in/ricardo-cunha-18a22757/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>
<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_ricardo_cunha.jpg"
    alt="Ricardo Cunha"
    title="Engineering Lead"
  />
  <figcaption style="text-align: center">Engineering Lead</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>AI as a Development Partner - Part 2 - Spec Driven Development</title><link>https://devblog.criticalmanufacturing.com/blog/20260428_ai_developer-experience-part2/</link><pubDate>Tue, 28 Apr 2026 00:00:00 +0000</pubDate><dc:creator>Ricardo Cunha</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20260428_ai_developer-experience-part2/</guid><description>Another experiment with AI as a development partner, this time using Spec Driven Development to implement a real-life feature.</description><content:encoded><![CDATA[<hr>
<p>In the <a href="https://devblog.criticalmanufacturing.com/blog/20260414_ai-developer-experience/" target="_blank" rel="noopener">previous post</a>, we explored how AI can assist in the development process by generating code snippets and providing suggestions. In this second part, we will take it a step further by implementing a real-life feature using Spec Driven Development with the help of AI tooling.</p>
<h2 id="introduction">Introduction</h2>
<p>While the first experiment was promising, it was mainly focused and limited to generating code snippets and unit tests. I wanted to see how AI could assist in a more comprehensive development process, using real-life examples. For this experiment, I chose two new enhancements planned for the <strong>Medical Devices Template</strong> solution. For the first one, which was the simplest, I used the traditional approach of just writing the prompt I thought would be best for the task. For the second one, which was more complex, I used a more structured approach, following the Spec Driven Development methodology, which I will explain in more detail later.</p>
<blockquote>
<p>ℹ️  <strong>Medical Devices Template</strong><br/>
For more information about the Medical Devices Template and its features, please check Critical Manufacturing&rsquo;s Documentation Portal: <a href="https://help.criticalmanufacturing.com/11.2/userguide/industrytemplates/medical/" target="_blank" rel="noopener">User Guide</a> and <a href="https://help.criticalmanufacturing.com/11.2/tutorials/industrytemplates/medical/" target="_blank" rel="noopener">Tutorials</a>.</p>
</blockquote>
<p>Regarding tooling, I used <a href="https://github.com/features/copilot/cli" target="_blank" rel="noopener">GitHub Copilot CLI</a> with an enterprise account. This tool allows you to interact with GitHub Copilot directly from the command line, making it easier to integrate AI assistance into your development workflow. The look and feel and commands are very similar to the ones you can find on Claude Code.</p>
<h2 id="enhancement-1-fixing-a-bug-in-the-installation">Enhancement 1: Fixing a Bug in the Installation</h2>
<p>The first enhancement was to fix a bug in the installation process of the Medical Devices Template. In the current development cycle (not yet released), we are extending the <strong>Continuous Flows</strong> feature to support materials sent to Rework, by supporting a new <code>In Rework</code> rank.
The issue was that the installation process rule was not properly handling scenarios where a previous version of the MedDev Template was already installed. In those cases, the installation process was not creating the <code>In Rework</code> rank, resulting in an inconsistent state of the system.</p>
<p>Since the fix was relatively simple, I decided to use the traditional approach of just writing a prompt that I thought would be best for the task. I provided the AI with the necessary context (including the actual file) about the issue and asked it to generate the code to fix the bug.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/InRework-Code-Prompt.png" alt="In Rework Code Prompt" />

</p>
<blockquote>
<p>ℹ️  <strong>Accessing the framework</strong><br/>
You can see on the prompt I am referring to the location of the CM Framework source code. I wanted to ensure that the AI had access to the relevant code, if required, and could understand the context of the issue.</p>
</blockquote>
<p>The log generated by Copilot CLI is shown below. As you can see, the AI was able to understand the context.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/InRework-Session-Log.png" alt="In Rework Session Log" />

</p>
<p>The AI generated the code to fix the bug, which I then reviewed and tested. The generated code was correct and fixed the issue as expected. This was a good indication that AI can assist in fixing bugs and generating code for simple tasks, even without a more structured and detailed approach.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/InRework-Update-Generated-Code.png" alt="In Rework Generated Code" />

</p>
<h2 id="enhancement-2-refactoring-configuration-within-an-existing-feature">Enhancement 2: Refactoring configuration within an existing feature</h2>
<p>The second enhancement required a deeper refactoring of the Continuous Flows feature. If you check closely the documentation you can see that there are several feature flags that enable Continuous Flows and related functionality, such as re-evaluating ranks automatically or creating future merge actions at a later step in the flow. The goal was to implement an additional level of configuration, allowing the same feature flags to be enabled by context and not globally, if required. This allows greater flexibility and allows the activation of these features only for specific contexts. The following diagram shows the logic we wanted to achieve at the end.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/Cont-Flow-Context-Diagram.svg" alt="Continuous Flow Context Diagram" />

</p>
<p>While functionally the change in itself is not very complex, it requires changes on several layers:</p>
<p>➡️ Renaming existing config entries to indicate they are now global settings</p>
<p>➡️ Migrating values of old configuration entries to the new ones, if applicable</p>
<p>➡️ Creating a new context smart table to store context-specific Continuous Flows settings</p>
<p>➡️ Refactor Business logic code (several DEEs) to implement the new logic described above, while ensuring that the existing logic is not broken and that the new logic is properly integrated.</p>
<p>➡️ Update existing unit tests and create new ones to cover the new logic.</p>
<p>While I could have just written a prompt describing the tasks, I wanted to try a more structured approach, following the <strong><em>Spec Driven Development (SDD)</em></strong> methodology.
SDD is a methodology that focuses on writing detailed specifications for the desired behavior of a feature before implementing it. Teams write executable or reviewable specs that define as much as possible what the software should do. The spec documents are not static documents, but rather living documents that can be executed or reviewed to ensure that the implementation meets the specified behavior.</p>
<p>For this use case, I created a spec document with the following sections:</p>
<ol>
<li><strong>Why and What</strong>: Short descriptions of why and what we are changing from a high-level standpoint.</li>
<li><strong>Constraints</strong>: Any <em>musts</em> and <em>must nots</em> that should be considered during the implementation. For example, if this was a feature written from scratch with external libraries dependencies, one could specify what library must or must not be used.</li>
<li><strong>Tasks</strong>: A detailed list of tasks that should be implemented to achieve the final result. In this case, each task corresponded to one of the layers mentioned above. Initially I had a final task regarding integration tests, but decided to remove it since it is better practice to have another developer implement them. For each task, I provided a detailed description of what should be done, and in some cases, guided the agent to which files should be modified.</li>
<li><strong>Validation</strong>: After the agent finished its tasks, it should perform some validations, mainly that the project builds successfully and that all unit tests pass.</li>
</ol>
<h3 id="initial-spec-implementation">Initial spec implementation</h3>
<p>The following image shows a snippet of the specification file, showing the first part of the document.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/Spec-snippet.png" alt="Spec Document Snippet" />

</p>
<p>The prompt in this case was very simple: just reference the spec document and ask the agent for any clarification that it thinks is necessary.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/Spec-prompt.png" alt="Spec Prompt" />

</p>
<p>Although I expected the agent to finish each task (reasoning and coding) before moving to the next one, it actually spent the first minutes thinking and reasoning about the entire list of tasks, and only then started implementing the first one. While the code was being generated, reviewed and accepted, I noticed a couple of issues:</p>
<ul>
<li>The agent was able to abstract the feature &ldquo;enabling&rdquo; logic to a common method that was then used across the code, which was good. However, in one particular case, it removed the previous feature enable check but it did not replace it with the new common method.</li>
<li>In one case, it did not add the expected <code>using</code> namespace statements in the file. Note that I fixed this manually while the agent was still working on the other tasks, but I believe in the final validation step, the agent would be able to identify that the code does not compile and fix the issue.</li>
</ul>
<h3 id="changing-the-spec">Changing the spec</h3>
<p>The first version of the spec was implemented successfully, however two change requests were now required:</p>
<ol>
<li>After clarifying with the Product Manager, it was required to add the <code>ProductionOrder</code> as key in the new context smart table.</li>
<li>The original spec did not include any details about updating documentation, both technical and functional (user guide).</li>
</ol>
<p>For the first one, the change in the spec is straight forward. Just updating the smart table specification by adding the new <code>ProductionOrder</code> key and asking in the prompt for the necessary code changes to support the new key.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/Spec-Add-PO.png" alt="Spec Adding Production Order" />

</p>
<p>In this particular case, I ran the prompt using <strong><em>Plan</em></strong> mode. The agent was able to generate an execution plan to be reviewed, changed (if necessary) and accepted. In this case, the change was relatively simple, so the plan was straightforward and was accepted without changes.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/Spec-Add-PO-Plan.png" alt="Spec Adding Production Order - Plan" />

</p>
<blockquote>
<p>ℹ️  <strong>Agent vs Plan modes</strong><br/>
It&rsquo;s good to run the prompt in Plan mode for more complex changes. That was not the case here, but the model could return a plan that needs to be reviewed and adjusted before execution, which adds an extra layer of control to the process.</p>
</blockquote>
<p>For the documentation, the spec was changed to add two new tasks, one for technical documentation and another for the user guide. After reviewing, a small change on the user guide was performed, in relation to the order of the sections, but overall the generated documentation was good and only required minor adjustments.</p>
<p>
<img src="/blogPosts/posts/20260428_ai_developer-experience-part2/Spec-Update-Docs.png" alt="Spec - Update documentation" />

</p>
<h2 id="conclusions">Conclusions</h2>
<p>This experiment showed that AI can be a valuable partner in the development process, not only for generating code snippets but also for implementing more comprehensive features. The spec-driven approach provided a structured way to guide the AI through the implementation process. While the AI was able to understand the context and generate code for the tasks, it still required some level of supervision and review to ensure that the implementation was correct and met the specified requirements. Nevertheless, while the SDD-implemented feature would probably take a couple of days to finish in the &ldquo;old days&rdquo;, it was implemented in slightly over half a day, which is a good indication given this was a first experiment.</p>
<p>For future experiments, we could explore ways to improve these spec documents and how they would behave on more complex features or even features developed from scratch. We can also improve our prompts and see how the model behaves, for example being explicit on completely finishing one task before moving to the next one, or asking for a more detailed execution plan before starting the implementation.</p>
<p><strong><em>Always remember to review and understand what the AI generates.</em></strong></p>
<h2 id="resources">Resources</h2>
<p>You can download the final version of the spec document <a href="/blogPosts/posts/20260428_ai_developer-experience-part2/570689-context-continuous-flow.md">here</a>.</p>
<hr>
<h2 id="author">Author</h2>
<h3 id="hi-my-name-is-ricardo-cunha-">Hi! My name is Ricardo Cunha. ✌️</h3>
<p>I joined Critical Manufacturing back in 2013. I started my journey as a Software Engineer, progressed to Tech Lead, and currently serve as Engineering Lead in the Medical/Life Sciences segment in Solutions Delivery area, as well as Team Lead for the Medical/Life Sciences template team.</p>
<p>You can check me on <a href="https://www.linkedin.com/in/ricardo-cunha-18a22757/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>
<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_ricardo_cunha.jpg"
    alt="Ricardo Cunha"
    title="Engineering Lead"
  />
  <figcaption style="text-align: center">Engineering Lead</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>AI as a Development Partner - Skills, Agents, and Context</title><link>https://devblog.criticalmanufacturing.com/blog/20260414_ai-developer-experience/</link><pubDate>Tue, 14 Apr 2026 00:00:00 +0000</pubDate><dc:creator>Ricardo Cunha</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20260414_ai-developer-experience/</guid><description>&amp;ldquo;Another JavaScript framework? God, why did I choose this field?&amp;rdquo;
If you have been working with software for a significant number of years, you probably have heard or even asked yourself the exact same question. But for about two years now, all the talk has been related to Large Language Models (LLM). And if we thought new JavaScript frameworks were popping out like mushrooms, then compared to the latest updates in the AI/LLM world, it was no big deal after all&amp;hellip;</description><content:encoded><![CDATA[<hr>
<p>&ldquo;<em>Another JavaScript framework? God, why did I choose this field?</em>&rdquo;</p>
<p>If you have been working with software for a significant number of years, you probably have heard or even asked yourself the exact same question. But for about two years now, all the talk has been related to Large Language Models (LLM). And if we thought new JavaScript frameworks were <em>popping out like mushrooms</em>, then compared to the latest updates in the AI/LLM world, it was no big deal after all&hellip;</p>
<h2 id="introduction">Introduction</h2>
<p>For decades, the developer experience has been about mastering tools: IDEs, debuggers, compilers, version control. You learned the tool, and the tool did exactly what you told it to. AI and LLMs are changing the contract. The new generation of tools doesn&rsquo;t just execute instructions — they <strong>understand</strong> them.</p>
<p>Although models like Claude, GPT or Gemini bring the ability to reason over natural language, explain complex systems and generate meaningful content, the truth is LLMs have real limitations: these models have been trained up to a certain point in time, meaning they have a fixed knowledge cut-off. They have no knowledge of current events or inherent access to your codebase.
The models themselves have no idea of what the current weather is in Porto or the world&rsquo;s current geopolitical&hellip; <em>situation</em>.</p>
<p>But over the last couple of years, the industry has converged on a set of patterns and protocols that let you <strong>extend</strong> what an LLM knows and can do. It&rsquo;s no longer a smart auto-complete / summarization tool - it&rsquo;s an active collaborator in your workflow.</p>
<h2 id="extending-llms">Extending LLMs</h2>
<p>So what are these patterns and tools that can be used to extend the context provided to LLMs?
The following image provides a summary of the different layers that compose the extensibility ecosystem of an LLM.</p>
<p>
<img src="/blogPosts/posts/20260414_ai-developer-experience/ai-tooling-ecosystem.png" alt="LLM Extensibility Ecosystem" />

</p>
<p>At the <strong>foundation</strong> layer, you have the LLM itself — a transformer model with powerful reasoning but bounded knowledge.</p>
<p>At the <strong>context</strong> layer, you shape what the model knows and how to behave. This includes:</p>
<ul>
<li><strong>Prompt Engineering</strong>: Crafting prompts in a way that guides the LLM to produce the desired output.</li>
<li><strong>Skills</strong>: Pre-built/packaged functionalities that can be invoked by the LLM to perform specific tasks. They are not bounded only by instructions, but can even execute scripts/code.</li>
<li><strong>Retrieval Augmented Generation (RAG)</strong>: A technique that allows LLMs to access external knowledge bases or documents to provide more accurate responses.</li>
</ul>
<p>At the <strong>connectivity</strong> layer, you have tools that enable the LLM to interact with external systems and APIs. Here the buzz has been around <strong>Model Context Protocols (MCP)</strong>, a standardized way for LLMs to access and interact with external data sources. MCPs allow LLMs to fetch real-time data, query databases, or even interact with other APIs, effectively bridging the gap between the model&rsquo;s static knowledge and the dynamic world.</p>
<p>Finally, at the <strong>orchestration</strong> layer, where agents and workflows come into play. <strong>Agents</strong> are autonomous entities that can perform tasks on behalf of the user, while <strong>workflows</strong> allow you to define complex sequences of actions that can be executed by the LLM and its connected tools.</p>
<h2 id="developer-experience-in-critical-manufacturing">Developer Experience in Critical Manufacturing</h2>
<p>Now the question becomes: how can we leverage these tools to enhance the developer experience in Critical Manufacturing?</p>
<p>As a first experience, I have explored three different use cases:</p>
<p>➡️ Boilerplate code generation</p>
<p>➡️ Unit tests generation</p>
<p>➡️ Code Reviews</p>
<h3 id="boilerplate-code-generation">Boilerplate Code Generation</h3>
<p>Whenever a new DEE action or custom service needs to be created, the usual approach is to copy code from existing ones and adapt it (change action/service names, change input &amp; output classes, etc.). Especially for new services, this is boring and slightly time-consuming.
Another challenge is ensuring that the LLM has the necessary <strong>context</strong> to generate code that is consistent with the existing codebase. How can we do that?</p>
<p>For this, a new skill was created that instructs the LLM on what to do when asked to generate boilerplate code. The skill provides the LLM with a template of the code structure for each scenario (DEE action or custom service), along with guidelines on how to fill in the blanks based on the provided specifications. The skill also includes examples of well-structured code snippets to serve as references for the LLM.</p>
<blockquote>
<p>ℹ️  <strong>How to create new skills?</strong><br/>
Skills can be created in your repository, by creating a new folder and a <strong>SKILL.md</strong> file with the instructions. If you&rsquo;re using Visual Studio Code, you can check in the <em>Settings</em> the available <em>Agent Skills Locations</em>, or add new ones.

<img src="/blogPosts/posts/20260414_ai-developer-experience/agent-skills-locations.png" alt="VS Code - Agent Skills Locations" />

</p>
</blockquote>
<p>As mentioned before, the instructions are specified in a markdown file that is passed as context to the model. You can find an example of the instructions provided in the <a href="/blogPosts/posts/20260414_ai-developer-experience/new-business-item-skill.md">Boilerplate Code Skill</a> created for this use case.</p>
<p>Here is the code generated for a potential new DEE action. VS Code Chat was able to understand with just natural language what I wanted to achieve (&quot;<em>create a new DEE Action (&hellip;)</em>&quot;) and automatically used the skill to get the instructions.</p>
<p>
<img src="/blogPosts/posts/20260414_ai-developer-experience/new-dee-action-example.png" alt="New DEE Action - Generated Code" />

</p>
<blockquote>
<p>ℹ️  <strong>Code generation for other layers</strong><br/>
During this experience, a skill focused on GUI components (widgets, wizards, etc.) was created. However, there are already some commands to generate this boilerplate so the advantage of using an LLM for this is not as visible. For GUI components, the commands are usually faster.</p>
</blockquote>
<h3 id="unit-tests-generation">Unit Tests Generation</h3>
<p>Unit tests are a critical part of software development, but they can be time-consuming to write. By leveraging LLMs, we can automate the generation of unit tests based on the existing codebase and the specifications provided by the developer.
In this use case, the approach followed was different. I selected an existing project that had no unit tests and no skill or agent was created.</p>
<p>1️⃣ A set of unit tests were manually created for a sample DEE action, covering the most common scenarios and edge cases.</p>
<p>2️⃣ The generated unit tests were then used as a reference for the LLM to generate additional unit tests for other DEE actions in the same project - this was explicit in the prompt.</p>
<p>There were some observations on the subsequent attempts to generate unit tests with the LLM:</p>
<ul>
<li>The LLM was able to generate unit tests that were consistent with the style and structure of the manually created ones and it was able to generate tests covering approximately 100% of DEE code.</li>
<li>Without being explicit in the prompt, the LLM typically generated different unit test methods for each test combination instead of trying to reuse and parameterize them (i.e, <em>xUnit&rsquo;s Facts and Theories</em>).</li>
<li>When generating tests for a new DEE that had different Input &ldquo;types&rdquo; (i.e., an orchestration Input/Output class), the LLM struggled to understand how to generate the unit tests. Eventually, I cancelled the execution.</li>
</ul>
<p>The last point touches on an important aspect of this whole experience: <strong>the LLM&rsquo;s ability to understand the context of the entire codebase</strong>. In this case, the LLM had no prior knowledge of the CM internal framework&rsquo;s patterns and structures, which made it difficult for it to generate accurate unit tests for new scenarios.
Nevertheless, in most cases the LLM was able to assist in the generation of unit tests. In a particular scenario, with a DEE whose structure was similar to a previous one, it took less than 5 minutes to generate, review and commit the code to the repository.</p>
<blockquote>
<p>⚠️  <strong>A note on code generation</strong><br/>
Always review and adapt the generated code, if necessary! Keep <em>vibe coding</em> for personal projects and prototypes, not for enterprise-grade production code. The ones that need to maintain the codebase in just a couple of years will really appreciate it&hellip;</p>
</blockquote>
<h3 id="code-reviews">Code Reviews</h3>
<p>I was quite happy with the previous experiences but I wanted to go even further. What about code reviews? Can LLMs be used to assist in code reviews and provide feedback on code quality, potential bugs, or adherence to coding standards?</p>
<p>For this use case, instead of a skill, an agent was created that could analyze code changes from a particular feature branch. I focused the code review on C#/.NET projects in the repository. The agent&rsquo;s workflow in itself was simple:</p>
<ul>
<li>Calculate the diff of the feature branch with the main/development branch.</li>
<li>For each changed file, review the code for naming conventions, potential bugs, performance improvements, etc.</li>
<li>Provide a summary of the review, by categorizing each finding with a severity level (e.g., 🔴 CRITICAL, 🟠 HIGH, 🟡MEDIUM, etc. )</li>
</ul>
<p>While C#/.NET best practices are quite well-known and documented, the challenge was to provide the LLM with enough context to understand the specific coding standards and patterns used in the CM codebase. For this, the instructions for the agent also included examples of typical issues found in code reviews: loading large object graphs into memory (the <em>levelsToLoad</em> issue 😖), loading inside loops, etc.</p>
<p>For a first approach, I was quite happy with the results. I tested with a real feature branch and the agent was able to identify a critical bug in the logic, which would lead to the feature being completely broken.</p>
<p>However, a couple of observations were made:</p>
<ol>
<li>The generated report, although very complete, was too verbose and not easy to read.</li>
<li>The agent definition .md file already had more than 500 lines.</li>
</ol>
<p>The first point is something that can always be improved by providing better instructions to the agent on how to summarize the findings and how to present them in a more concise way.  For the second one, I decided to test a multiple sub-agent approach: one agent acted as the orchestrator, while multiple child agents were responsible for reviewing specific aspects of the code.</p>
<p>
<img src="/blogPosts/posts/20260414_ai-developer-experience/multi-sub-agents.png" alt="Multiple Sub-Agents Approach" />

</p>
<p>Using this approach, we can select the orchestrator agent in the context window and just ask it to review the code. The orchestrator will then delegate the review to the different sub-agents and aggregate the results in a final report.</p>
<p>Nevertheless, there were some challenges with this approach, especially regarding the conflicts of some findings. The runtime sub-agent, responsible for looking into possible null reference exceptions, might point to potential issues which are in conflict with some assumptions made by the CM framework sub-agent. For example, the <code>ServiceProvider</code> must always be an Input in the DEE Actions. The runtime sub-agent might point to potential null reference exceptions related to the <code>ServiceProvider</code>, while the CM framework sub-agent might not consider it as an issue since it&rsquo;s a known pattern in the codebase. This led to some confusion, and a new instruction was added to the orchestrator agent to prioritize the findings of the CM framework sub-agent.</p>
<p>Here is an example of the generated report for a particular feature branch:</p>
<p>
<img src="/blogPosts/posts/20260414_ai-developer-experience/code-review-sample.png" alt="Code Review Report" />

</p>
<p>The <em>Critical</em> finding was actually part of the change request, so the fact that the model does not have the full context makes it appear as a critical issue when it is not. Be on the look out for false positives. 👀</p>
<h2 id="conclusion">Conclusion</h2>
<p>The goal of this experiment was to play around with the different tools and patterns available to extend LLMs and see how they can be applied to enhance the developer experience in Critical Manufacturing. The results showed some potential benefits in terms of productivity and code quality, but also highlighted some challenges that would need to be addressed. In addition, another question is how to integrate these tools into the existing development workflow. Can code reviews be fully automated and part of the pull request pipelines? Can unit test generation be triggered by specific commits or branches? These are questions that we will need to explore in the future.</p>
<h2 id="resources">Resources</h2>
<p>You can download the (<em>current</em>) final version of the skills and agents used in this blog post <a href="/blogPosts/posts/20260414_ai-developer-experience/.github.zip">here</a>.</p>
<hr>
<h2 id="author">Author</h2>
<h3 id="hi-my-name-is-ricardo-cunha-">Hi! My name is Ricardo Cunha. ✌️</h3>
<p>I joined Critical Manufacturing back in 2013. I started my journey as a Software Engineer, progressed to Tech Lead, and currently serve as Engineering Lead in the Medical/Life Sciences segment in Solutions Delivery area, as well as Team Lead for the Medical/Life Sciences template team.</p>
<p>You can check me on <a href="https://www.linkedin.com/in/ricardo-cunha-18a22757/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>
<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_ricardo_cunha.jpg"
    alt="Ricardo Cunha"
    title="Engineering Lead"
  />
  <figcaption style="text-align: center">Engineering Lead</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>A Closer Look at Integration Entries, Part II: Low Code</title><link>https://devblog.criticalmanufacturing.com/blog/20250619_low_code_integration/</link><pubDate>Thu, 19 Jun 2025 00:00:00 +0000</pubDate><dc:creator>Ricardo Cunha</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20250619_low_code_integration/</guid><description>Take advantage of low code, drag-and-drop workflows to design and implement seamless system integrations</description><content:encoded><![CDATA[<hr>
<br/>
<h2 id="introduction-to-low-code-integrations">Introduction to Low Code integrations</h2>
<p>In the <a href="/blog/20250429_integration_entries/">original post</a> from this series, we looked at the core of our built-in enterprise integration framework, with special focus on the definition of Integration Entries and the high-level explanation on how the integration pipeline operates.
The post&rsquo;s content was based on perhaps the most frequent system integration example we observe in CM MES implementations: using the <code>GenericIntegrationHandler</code> integration package and handle the integration logic using DEE Actions, each responsible for handling specific message types.
Since DEE actions are, after all, a collection of C#/.NET code using the CM MES internal framework, they fall in the category of traditional software development. On specific use cases, traditional development might bring some drawbacks:</p>
<p>➡️ <strong>Time</strong>: Traditional development focuses on manual coding, which is a time-consuming task - even in the age of AI-based code generation tools. Furthermore, time-consuming tasks do not end with just manual coding, because other important phases of software development are closely impacted: code reviews, deployment actions (e.g., new package deployment when an integration is changed or a bug is fixed), etc.</p>
<p>➡️ <strong>Technical Knowledge/Skills</strong>: It goes without saying that traditional development requires specific expertise in software engineering. In practice, this expertise might result in the need to understand multiple programming languages and respective frameworks and/or libraries. As a consequence, not all parties of an implementation project will be able to actively contribute to these integrations, either by creating new ones from scratch or changing/fixing others.</p>
<p>In certain integration use cases, a low code integration platform could be a viable solution. <strong>Low code</strong> development can be described as an alternative approach on building software: an approach where little code is required. Instead, the business process is built using visual tools, such as drag-and-drop, pre-built widgets with simple manual coding, only when required. In essence, the user builds a workflow, connecting the different widgets and deciding how the information flows until reaching the end of the execution.</p>
<p>Low code platforms can bring the following advantages when building new integrations:</p>
<p>🚀 <strong>Implementation Speed</strong>: By offloading implementation logic to pre-built components, the user&rsquo;s main task becomes building a visual workflow, connecting and configuring the required components so that the business value is reached at the end of the workflow&rsquo;s execution.</p>
<p>📚 <strong>Skills</strong>: Since the implementation details are part of the pre-built components, the user would only need to develop code, under certain conditions (e.g., if there is no provided component that fulfills a particular task). For specific simple scenarios, a workflow could be implemented without the need for extra coding. Consequently, users with different backgrounds and roles could contribute for certain interfaces.</p>
<p>🧪 <strong>Prototyping/PoC</strong>: Considering the two points above, it&rsquo;s easier to setup a prototype or a proof of concept for new integrations without the need to step into the traditional software development and deployment cycle.</p>
<p>🔁 <strong>Reusability</strong>: The components that are available to build the low code workflows are reusable, thus avoiding frequent programming issues such as not following the DRY (<em>Don&rsquo;t Repeat Yourself</em>) principle or creating technical debt.</p>
<blockquote>
<p>⚠️  <strong>Choose the right tool for the job</strong> <br/>
The goal of this post is to describe a different approach for implementing integrations in CM MES, not to determine that <i>&ldquo;A&rdquo; is better than &ldquo;B&rdquo;</i> or vice-versa. In fact, low code solutions can also create new challenges in other areas such as change management and governance, especially in highly regulated segments, such as medical devices. Traditional or low code development are not mutually exclusive and can be used in conjuntion.</p>
</blockquote>
<p>In the CM MES ecosystem, there are more examples of low code solutions which you might already be aware of. Connect IoT uses low code workflows to easily setup and build integration scenarios. More recently, a new feature called <a href="/blog/20250407_business_workflow_feature/">Business Workflows</a> was implemented and made available, where users can build simple workflows to react to certain events and execute CM MES operations according to their functional requirements.</p>
<p>In the next section, an example will be provided on how to configure, execute and monitor low code workflows in the context of enterprise integrations. At the end we&rsquo;ll take a closer look at the different technical components in place and how the solution is designed from a high-level standpoint.</p>
<h2 id="demo-importing-a-production-order">Demo: Importing a Production Order</h2>
<p>For this demonstration, I wanted to show an example that would be simple to understand, but at the same time would resonate to those actively working in implementation projects with ERP integrations. Taking this into account, in this example we will simulate the creation of a new production order (in this case represented by a <code>Material</code> instance), by receiving this JSON payload:</p>
<p>
<img src="/blogPosts/posts/20250619_low_code_integration/integration_payload.png" alt="Import Production Order Payload" />

</p>
<table>
<thead>
<tr>
<th style="text-align:left"><strong>Field</strong></th>
<th style="text-align:left"><strong>Description/Mapping</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left"><strong>PlantCode</strong></td>
<td style="text-align:left">A code representing the plant. In this example, the received value must match a custom attribute in the <code>Facility</code> instance.</td>
</tr>
<tr>
<td style="text-align:left"><strong>LotNumber</strong></td>
<td style="text-align:left">The new material&rsquo;s name.</td>
</tr>
<tr>
<td style="text-align:left"><strong>PartNumber</strong></td>
<td style="text-align:left">The <code>Product</code> name. A <code>Product</code> with the same name must exist in CM MES.</td>
</tr>
<tr>
<td style="text-align:left"><strong>DueDate</strong></td>
<td style="text-align:left">Assigned to the Material&rsquo;s <code>DueDate</code> property.</td>
</tr>
<tr>
<td style="text-align:left"><strong>Quantity</strong></td>
<td style="text-align:left">Assigned to the Material&rsquo;s <code>PrimaryQuantity</code> property.</td>
</tr>
<tr>
<td style="text-align:left"><strong>UnitOfMeasure</strong></td>
<td style="text-align:left">Assigned to the Material&rsquo;s <code>PrimaryUnits</code> property.</td>
</tr>
<tr>
<td style="text-align:left"><strong>Priority</strong></td>
<td style="text-align:left">Assigned to the Material&rsquo;s <code>Priority</code> property.</td>
</tr>
</tbody>
</table>
<h3 id="configuration">Configuration</h3>
<p>First, we need to register our event/payload as an <code>IoTEventDefinition</code>. Create a new instance for scope <code>Enterprise Integration</code>, and enter the properties mentioned above.</p>
<p>
<img src="/blogPosts/posts/20250619_low_code_integration/iot_event_definition.png" alt="Event Definition" />

</p>
<blockquote>
<p>💡 At the event definition creation wizard, instead of entering each property individually, you have the option to Import a JSON file or template. The wizard will parse the content and generate the properties automatically. Nevertheless, ensure that the properties are created with the expected data type.</p>
</blockquote>
<p>Now that the event is registered, an <code>AutomationController</code> must be created. Create a new one for scope <code>Enterprise Integration</code> and choose the <code>IoTEventDefinition</code> created above. In this controller, we will define the workflow that will be triggered when the integration entry is set to be processed.</p>
<p>
<img src="/blogPosts/posts/20250619_low_code_integration/automation_controller_workflow.png" alt="Automation Controller Workflow" />

</p>
<p>In the workflow above, we have setup the following tasks:</p>
<ol>
<li><strong>Job Start</strong>
<ul>
<li>Initial task that just outputs the list of properties defined in the <code>IoTEventDefinition</code>.</li>
</ul>
</li>
<li><strong>Load Facility</strong>
<ul>
<li>The task receives as input the PlantCode from the event definition.</li>
<li>As a code task, it is running a query to retrieve the Facility whose custom attribute matches the <em>PlantCode</em>, and returning it as the output.</li>
</ul>
</li>
<li><strong>Load Product</strong>
<ul>
<li>Similar to <em>Load Facility</em>, but just loads a Product whose name matches the input <em>PartNumber</em>.</li>
</ul>
</li>
<li><strong>Create Material Object</strong>
<ul>
<li>With the original event properties and the result of the previous tasks, this task builds a <code>Material</code> object and returns it as the output.</li>
</ul>
</li>
</ol>
<p>
<img src="/blogPosts/posts/20250619_low_code_integration/create_material_object_task.png" alt="Create Material Object Code Task" />

</p>
<ol start="5">
<li><strong>Call CreateObject</strong>
<ul>
<li>This task will call the <code>CreateObject</code> API, to create the input <code>Material</code> object.</li>
</ul>
</li>
</ol>
<p>After the <code>AutomationController</code> is approved and set effective, the only thing missing in the configuration should be more familiar. In the <code>IntegrationHandlerResolution</code> smart table, the <code>IoTEventDefinition</code> created above should be defined in the respective column instead of a DEE Action.</p>
<h3 id="execution">Execution</h3>
<p>After the configuration is in place, we can test the execution by creating a new <code>IntegrationEntry</code> with the respective <code>MessageType</code> and the expected JSON payload. For integration entries processed using this mechanism, additional information can be reviewed, since an <code>AutomationJob</code> is created. Execution metadata such as the duration, creation time and completed date are available in the entry&rsquo;s details page.</p>
<p>
<img src="/blogPosts/posts/20250619_low_code_integration/integration_entry_processed.png" alt="Integration Entry Processed" />

</p>
<h2 id="technical-overview">Technical Overview</h2>
<p>We&rsquo;ve seen from a functional standpoint what needs to be configured to set up enterprise integration workflows using event definitions. But how does this technically work?
The following diagram explains from a high-level perspective the relationship between the different components in this processing pipeline.</p>
<p>
<img src="/blogPosts/posts/20250619_low_code_integration/low_code_tech_diagram.png" alt="Low Code Tech Diagram" />

</p>
<p>The initial part of the process is similar to what we have seen before; it&rsquo;s not relevant if an integration entry message type is configured to be processed by the traditional implementation route (e.g., DEE Action) or by low code workflows (IoT Event Definition).
The <code>IntegrationEntry</code> is created either by external applications (inbound) or by CM MES itself (outbound). The previously described integration orchestrator will pull the respective integration entry to be processed and, if the configuration in the <code>IntegrationHandlerResolution</code> smart table determines that the handler type is <code>GenericIntegrationHandler</code> and the <code>IoTEventDefinition</code> column is not empty, then it will trigger the pipeline to process that message.</p>
<p>The integration handler will send a message to the Apache Kafka broker, containing not only the original message&rsquo;s payload but additional metadata, including the <code>EventTime</code>, <code>IPAddress</code> and other event properties.
In the Kafka cluster, there will be one topic for each registered <code>IoTEventDefinition</code>. For example, during the demo described above, a new topic was created in the environment&rsquo;s Kafka cluster with the format <code>&lt;EnvironmentName&gt;_enterpriseintegration_customimportorder_raw</code>, being <em>CustomImportOrder</em> the name of the <code>IoTEventDefinition</code>.
One could even monitor and read the messages written to the respective topic. For example, in a CM MES environment where Kafka is hosted as a pod inside our deployment stack, we can open a terminal inside the running pod and use the <code>kafka-console-consumer</code> CLI tool to read the incoming messages.</p>
<p>
<img src="/blogPosts/posts/20250619_low_code_integration/kafka_consumer.png" alt="Kafka Consumer" />

</p>
<p>In this case the CM MES <code>Host</code> component is the producer, while the <code>Connect IoT Manager</code> will be the consumer of the messages.</p>
<blockquote>
<p>⚠️  <strong>Connect IoT Manager</strong> <br/>
By default, a Connect IoT Manager deployment will be created and started when the application is installed. This manager will also contain a default Automation Controller Worker, that has been configured to handle these integration cases.</p>
</blockquote>
<p>After the <code>Connect IoT Manager</code> consumes the message, it will use the Host&rsquo;s API to create an <code>AutomationJob</code>. The <code>Host</code> will create the object in the database but also send another message to the <code>RabbitMQ</code> instance, which again will be consumed by the <code>Connect IoT Manager</code>&rsquo;s internal controller. This will trigger the execution of the job, instantiating the respective <code>AutomationController</code>. Depending on the tasks in the workflow, the IoT process will call CM MES APIs in the <code>Host</code> component, finalizing the execution by completing the <code>AutomationJob</code>.</p>
<blockquote>
<p>⚠️  <strong>Execute Service Call tasks</strong> <br/>
The workflow execution is not an atomic operation: in a scenario of multiple service call tasks, these are not executed under the same transaction. In the example above, only the <code>CreateObject</code> service call changes state in CM MES, but in the case of multiple state-changing service calls, one is required to handle potential inconsistencies when one service call succeeds but the following in the workflow does not. Consider those potential issues when designing your workflows.</p>
</blockquote>
<h2 id="summary">Summary</h2>
<p>In this post, we took a look at an alternative, relatively new way to integrate with external systems, focused on visual widgets/components that can be orchestrated via drag-and-drop operations to build a comprehensive integration workflow, with minimal additional coding. As previously stated, this is an alternative way that can be used together with the traditional development approach, since they are not mutually exclusive. For simple integration scenarios, low code integration can be a valid alternative and even provide some advantages, such as the speed of implementation.</p>
<p>To conclude this post, it is worth mentioning that there are already plans to provide, in advance, integration scenarios for common use cases, such as importing a production order from SAP.</p>
<p>For follow-up questions or other topics that you&rsquo;d like to be in this blog regarding integrations, please feel free to reach me!</p>
<hr>
<h2 id="author">Author</h2>
<h3 id="hi-my-name-is-ricardo-cunha-">Hi! My name is Ricardo Cunha. ✌️</h3>
<p>I joined Critical Manufacturing back in 2013. I started my journey as a Software Engineer, progressed to Tech Lead, and currently serve as a Technical Architect in the Solutions Delivery area, providing technical guidance for CM MES deployments.</p>
<p>You can check me on <a href="https://www.linkedin.com/in/ricardo-cunha-18a22757/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>
<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_ricardo_cunha.jpg"
    alt="Ricardo Cunha"
    title="Technical Architect"
  />
  <figcaption style="text-align: center">Technical Architect</figcaption>
</figure>

</p>
]]></content:encoded></item><item><title>A Closer Look at Integration Entries</title><link>https://devblog.criticalmanufacturing.com/blog/20250429_integration_entries/</link><pubDate>Tue, 29 Apr 2025 00:00:00 +0000</pubDate><dc:creator>Ricardo Cunha</dc:creator><guid>https://devblog.criticalmanufacturing.com/blog/20250429_integration_entries/</guid><description>Explore the intricacies of integration entries in CM MES, covering use cases, the integration engine pipeline, and advanced configurations for seamless system integration.</description><content:encoded><![CDATA[<br/>
💡 This post is based on the latest CM MES version **v11.1**, although the discussed concepts should also be valid for previous versions.
<br/>
<hr>
<br/>
<p>In today&rsquo;s dynamic technological landscape, the need to integrate diverse software systems using appropriate communication protocols is increasingly common for delivering comprehensive solutions to complex requirements. While traditional client-server integration has largely relied on the synchronous <em>Request-Response</em> architecture (<strong>Synchronous</strong> APIs), non-blocking, <em>fire and forget</em> <strong>Asynchronous</strong> APIs have gained significant traction in recent years, driven by the growing adoption of microservices architecture and its associated server-to-server communication demands.</p>
<p>
<img src="/blogPosts/posts/20250429_integration_entries/api_sync_async.png" alt="Sync vs Async API" />

</p>
<p>In essence, a Synchronous model requires the caller to wait for the request to be fully processed before receiving a response. Conversely, in an Asynchronous model, the request is stored externally (e.g., database, message queue) for background processing, and the API immediately acknowledges receipt to the caller. In scenarios where immediate process results are not critical, this API architecture offers several key advantages:</p>
<p>➡️ <strong>Long-running tasks</strong>: For operations expected to take more than a few seconds (e.g., image/video processing), it&rsquo;s a best practice to provide immediate feedback to the caller while processing the request in the background.</p>
<p>➡️ <strong>Scalability</strong>: By offloading processing to background workers, the API can handle a higher volume of incoming requests without being blocked by the processing time of each operation.</p>
<p>➡️ <strong>Service decoupling</strong>: Request-response models can create tighter dependencies between services. Asynchronous models reduce this coupling by allowing services to communicate indirectly. For instance, Service A can send a message to a storage mechanism, and a background worker can then relay this message to Service B. If Service B is temporarily unavailable, Service A&rsquo;s functionality remains unaffected.</p>
<p>A frequent requirement in Critical Manufacturing MES project deployments is the ability to integrate with external 3rd party systems such as ERP, PLM, or Asset Management applications. To address this, CM MES provides an internal framework and integration engine designed to handle scenarios where asynchronous communication is required or architecturally preferred. The entire process of message storage and processing is managed within the CM MES ecosystem.</p>
<p>At the core of this mechanism lies the concept of an <strong>Integration Entry</strong>. An Integration Entry can be conceptually compared to a message in an external message queue system like RabbitMQ. It comprises a set of metadata properties and a body containing the message payload, which can be in virtually any format (e.g., JSON, XML, etc.).</p>
<p>In the next sections, we will take a closer look on how this mechanism works, focusing on three key aspects:</p>
<ul>
<li>The setup of the process pipeline, from the initial Host execution to the processing of a message.</li>
<li>The process by which integration entries are retrieved from the database for processing (pulling).</li>
<li>Important configurations that can be applied to the integration engine or the Integration Entry itself.</li>
</ul>
<p>But first, let&rsquo;s have a quick recap of the basic functionality and configuration of the Integration Engine.</p>
<h2 id="integration-engine-recap">Integration Engine Recap</h2>
<p>To aid our understanding, let&rsquo;s refer to the following diagram from the <a href="https://help.criticalmanufacturing.com/userguide/administration/system-integrations/" target="_blank" rel="noopener">Documentation Portal</a>:</p>
<p>
<img src="/blogPosts/posts/20250429_integration_entries/integration_entries_overview.png" alt="Integration Engine Overview" />

</p>
<p><strong>Integration Entries</strong> are stored directly within the CM MES database, eliminating the need for external message queues or databases for this mechanism. An <code>IntegrationEntry</code> is an entity type that includes several properties, notably:</p>
<ul>
<li><strong>Name</strong>: A unique identifier for the integration entry - can virtually be anything, such as a GUID or a consistent naming convention for easy tracking.</li>
<li><strong>MessageType</strong>: Identifies the type of message and is typically used to determine how the integration entry will be processed.</li>
<li><strong>SourceSystem</strong> and <strong>TargetSystem</strong>: Indicate the originating and destination systems of the message, referencing the <code>IntegrationSystem</code> lookup table. These can also influence the processing logic.</li>
<li><strong>SystemState</strong>: Reflects the current processing status of the Integration Entry (more details below).</li>
<li><strong>IsRetriable</strong>: A flag indicating whether the message should be automatically retried in case of an error.</li>
</ul>
<p>The actual message payload is stored in a separate but tightly linked entity type, <code>IntegrationMessage</code>, which contains a reference to its corresponding <code>IntegrationEntry</code>.</p>
<p>Integration entries can be created through various means, depending on whether the message is intended for processing by CM MES (<strong>Inbound</strong>) or for sending from CM MES (<strong>Outbound</strong>):</p>
<ul>
<li>📨 <strong>Inbound</strong>: These messages are usually created using the standard <code>CreateObject</code> API or custom APIs that perform initial processing before creating the integration entry.</li>
<li><strong>Outbound</strong> 📨: These messages are created and stored following specific operations within CM MES, typically via DEE Actions or custom services.</li>
</ul>
<p>The <strong>Integration Engine</strong> is a background worker framework responsible for processing these entries. We will explore its key components later, but for now, it&rsquo;s crucial to understand that the engine processes entries based on their <code>SystemState</code>. The following table outlines the main system states of an Integration Entry:</p>
<table>
<thead>
<tr>
<th style="text-align:left">State</th>
<th style="text-align:left">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left"><strong>Received</strong></td>
<td style="text-align:left">The initial state upon creation of the Integration Entry, indicating it&rsquo;s ready for processing by the engine.</td>
</tr>
<tr>
<td style="text-align:left"><strong>Processed</strong></td>
<td style="text-align:left">Set after the entry has been successfully processed by the integration logic.</td>
</tr>
<tr>
<td style="text-align:left"><strong>Failed</strong></td>
<td style="text-align:left">Indicates that an error occurred during the processing of the entry.</td>
</tr>
</tbody>
</table>
<blockquote>
<p>ℹ️  <strong>Reprocess Integration Entries</strong><br/>
Failed entries can be reprocessed either manually via the System Integration UI Page or automatically using an error handling DEE Action. However, these actions only sets the entry&rsquo;s <strong>IsRetriable</strong> flag to <strong>True</strong> and does not revert the entry&rsquo;s state to <em>Received</em>. A <em>CmfTimer</em> named <strong>RetryIntegrationEntry</strong>, running frequently (default every 5 minutes), checks for entries with the <em>IsRetriable</em> flag set. If the maximum retry limit hasn&rsquo;t been reached, it changes the state back to <em>Received</em>.</p>
</blockquote>
<p>Once the integration engine retrieves the next integration entry (or a batch of entries) for processing, it determines the appropriate <strong>Integration Package</strong> responsible for handling it. An Integration Package is a software library containing an integration handler as its entry point. These packages and handlers are configured in the generic table <code>IntegrationHandler</code>. By default, two integration packages/handlers are registered upon CM MES environment installation:</p>
<ul>
<li><strong>SapIntegrationHandler</strong>: For direct connection and integration with an SAP instance.</li>
<li><strong>GenericIntegrationHandler</strong>: A versatile handler suitable for simpler integration scenarios.</li>
</ul>
<p>The mapping between Integration Entries and their corresponding handlers is defined in the smart table <code>IntegrationHandlerResolution</code>. This table allows you to specify which integration package handler will process an entry based on its key properties (<em>SourceSystem</em>, <em>TargetSystem</em>, <em>MessageType</em>).</p>
<p>
<img src="/blogPosts/posts/20250429_integration_entries/integration_handler_resolution.png" alt="Integration Handler Resolution Table" />

</p>
<blockquote>
<p>ℹ️  <strong>Integration Packages</strong> <br/>
In most situations, the <strong>GenericIntegrationHandler</strong> proves sufficient. This package requires you to specify a DEE Action in the <strong>ActionName</strong> column, which will be executed to process the entry. You can also specify an <strong>IotEventDefinition</strong>, although low-code logic falls outside the scope of this post. <br>
Custom integration packages can be beneficial when you need to listen for new messages from external systems like message queues and automatically create the corresponding Integration Entries. This advanced topic is also beyond the scope of this discussion.</p>
</blockquote>
<p>Let&rsquo;s now delve into the Integration Engine in more detail. How does this background worker process initiate? What are its constituent components, and how does information flow within it?</p>
<h2 id="the-integration-engine-pipeline-explained">The Integration Engine Pipeline Explained</h2>
<p>The Integration Engine pipeline is an integral part of the CM MES Host component. Upon Host startup, the <code>StartIntegrationHandler</code> triggers the background creation of this pipeline. You can verify this process by observing the <code>Starting Integration Manager...</code> entry in the host&rsquo;s logs during startup.</p>
<p>To better visualize the pipeline&rsquo;s structure and information flow between internal components, the following sequence diagram provides a high-level overview:</p>
<p>
<img src="/blogPosts/posts/20250429_integration_entries/integration_seq_diagram.png" alt="Integration Engine Sequence Diagram" />

</p>
<blockquote>
<p>⚠️  This diagram is intended to illustrate the key components and the overall process flow at a higher level, rather than detailing every single C# class or method involved.</p>
</blockquote>
<p>Let&rsquo;s examine these steps in more detail.</p>
<h3 id="starting-the-background-orchestrator">Starting the Background Orchestrator</h3>
<p>As previously mentioned, the <code>StartIntegrationHandler</code> initiates the <code>Integration Scheduler</code>. The Scheduler&rsquo;s main responsibilities are:</p>
<ol>
<li>It calls the <code>Integration Manager</code> to load information about the Integration Packages (e.g., <em>GenericIntegrationPackage</em>) from the database and caches it within an <code>Integration Package Registry</code>.</li>
<li>It creates a new thread (to avoid blocking the Host&rsquo;s main operations) that executes the <code>Orchestrator</code> process.</li>
</ol>
<h3 id="pulling-integration-entries">Pulling Integration Entries</h3>
<p>Once the orchestration logic begins, it enters a loop where its first task is to retrieve eligible integration entries from the database for processing. This retrieval logic resides in the stored procedure <code>[CoreDataModel].[P_PullNextIntegrationEntry]</code>. This procedure queries the <code>T_IntegrationEntry</code> table and returns one or more integration entries that are ready to be processed.</p>
<p>Analyzing this stored procedure reveals the involvement of another table, <code>T_IntegrationEntryLock</code>, alongside <code>T_IntegrationEntry</code>. This auxiliary table works in conjunction with the main entity table to track the current &ldquo;processing&rdquo; status of Integration Entries. When an Integration Entry is created, a record is added not only to the main table but also to this &ldquo;lock&rdquo; auxiliary table.</p>
<p>Since Integration Entries are stored in their own table within the SQL Server ONLINE database, it&rsquo;s crucial to ensure that in environments with multiple running Hosts (e.g., Production), the same entries are not processed by different hosts concurrently. This is why the query employs SQL Hints such as <code>UPDLOCK</code> and <code>READPAST</code>. The <code>UPDLOCK</code> hint applies an update lock to the selected records, while <code>READPAST</code> instructs the query to skip records that already have an update lock. Consequently, if one Host is processing a specific entry, another Host will &ldquo;read past&rdquo; it and select the next available entry (or entries).</p>
<p>Furthermore, within the same query, you might observe fields like <code>BatchId</code> and <code>ParentIntegrationEntryId</code>.</p>
<p>Integration entries sharing the same <code>BatchId</code> are retrieved together by this procedure and processed sequentially by the orchestrator. This explains why the procedure might return more than one entry at a time.</p>
<p>
<img src="/blogPosts/posts/20250429_integration_entries/integration_batch_id.png" alt="Pull Integration Entries - Batch Id example" />

</p>
<p>The <code>ParentIntegrationEntryId</code> field establishes a dependency chain between entries. If Entry B has Entry A as its parent, Entry A must be processed before Entry B becomes eligible for processing.</p>
<blockquote>
<p>⚠️  <strong>Parent integration entry might not have been successfully processed</strong> <br/>
It&rsquo;s important to note that the system doesn&rsquo;t inherently prevent the processing of child entries if the parent entry has failed after multiple retry attempts. Implementing a &ldquo;circuit breaker&rdquo; or similar custom logic might be necessary in your projects to handle such dependencies and prevent unintended processing.</p>
</blockquote>
<p>
<img src="/blogPosts/posts/20250429_integration_entries/integration_parent_id.png" alt="Pull Integration Entries - Parent Id example" />

</p>
<h3 id="dispatch-and-process-integration-entries">Dispatch and Process Integration Entries</h3>
<p>After the pull stored procedure returns the integration entries, they are dispatched and processed. During the dispatch phase, for each entry, the system determines the responsible Integration Package based on the configuration in the <code>IntegrationHandlerResolution</code> table. Following this selection, the entry and its routing information are passed to the &ldquo;Process Integration Entry&rdquo; orchestration method, which executes the actual processing logic.</p>
<p>If the entry is processed successfully, its system state is updated to <code>Processed</code>, and the process concludes for that entry. If an error occurs during processing, the entry&rsquo;s state is set to <code>Failed</code>. Additionally, if a custom error handling action is configured in the <code>IntegrationHandlerResolution</code> smart table, this action will be executed (e.g., to automatically set the entry to be retried after examining the error message).</p>
<h2 id="global-configurations-and-wrap-up">Global Configurations and Wrap Up</h2>
<p>To conclude this post, I&rsquo;d like to highlight a couple of global configurations that can be adjusted if needed.</p>
<p>When the orchestrator doesn&rsquo;t find any integration entries to process at a particular time, it waits for a specific duration before attempting to read new entries from the database again. This duration is known as the <strong>Polling interval</strong> and can be configured in the Config entry <code>/Cmf/System/Configuration/Integration/PollingInterval/</code>. The default value for this configuration is <code>60000</code>, which translates to 60 seconds.</p>
<p>Another global configuration is the <strong>Number of parallel requests</strong> (Config entry <code>/Cmf/System/Configuration/Integration/NumberOfParallelRequests/</code>). This setting determines the maximum number of integration entry processing requests that <u>each MES Host instance</u> can handle concurrently. Therefore, the total number of parallel requests your system can handle is the product of this configuration value and the number of active MES Host instance in your environment. When an MES Host reaches its configured limit, it will stop pulling new integration entries until a processing slot becomes available. The following diagram illustrates an example with two hosts, both using the default value for the number of parallel requests.</p>
<p>
<img src="/blogPosts/posts/20250429_integration_entries/integration_number_parallel_requests.png" alt="Number Parallel Requests" />

</p>
<blockquote>
<p>ℹ️  <strong>One Batch, One Slot</strong> <br/>
A single batch containing multiple integration entries will only consume one processing slot at a time, as illustrated by the &ldquo;B &amp; C&rdquo; example above.</p>
</blockquote>
<blockquote>
<p>⚠️  <strong>Changing default values</strong> <br/>
In most scenarios, the default configuration values are adequate. However, you can modify these values to better suit your specific needs. It&rsquo;s crucial to exercise caution when adjusting the number of parallel requests to avoid negatively impacting the system&rsquo;s normal operation. If possible, it&rsquo;s highly recommended to conduct thorough load and stress tests in a non-production environment before deploying any changes to these configurations in production.</p>
</blockquote>
<p>That&rsquo;s all for now! I hope this deep dive into the system integration engine has provided valuable insights. If you&rsquo;re interested in learning more about related topics such as low-code integration, custom integration packages, or anything else, please feel free to reach out!</p>
<hr>
<h2 id="author">Author</h2>
<h3 id="hi-my-name-is-ricardo-cunha-">Hi! My name is Ricardo Cunha. ✌️</h3>
<p>I joined Critical Manufacturing back in 2013. I started my journey as a Software Engineer, progressed to Tech Lead, and currently serve as a Technical Architect in the Solutions Delivery area, providing technical guidance for CM MES deployments.</p>
<p>You can check me on <a href="https://www.linkedin.com/in/ricardo-cunha-18a22757/" target="_blank" rel="noopener">LinkedIn</a></p>
<p>
<figure class="text-center">
  <img
    src="/blogPosts/contributors/blogger_ricardo_cunha.jpg"
    alt="Ricardo Cunha"
    title="Technical Architect"
  />
  <figcaption style="text-align: center">Technical Architect</figcaption>
</figure>

</p>
]]></content:encoded></item></channel></rss>