<?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: tia-forge</title>
    <description>The latest articles on DEV Community by tia-forge (@tiaforge).</description>
    <link>https://dev.to/tiaforge</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%2F4179613%2Fddd904c9-5210-4f7a-be21-51476a4ccf54.png</url>
      <title>DEV Community: tia-forge</title>
      <link>https://dev.to/tiaforge</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tiaforge"/>
    <language>en</language>
    <item>
      <title>Two Routes to Connect AI to TIA Portal: 6 Tools, and Only 4 Can Write LAD</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Thu, 05 Nov 2026 16:17:39 +0000</pubDate>
      <link>https://dev.to/tiaforge/two-routes-to-connect-ai-to-tia-portal-6-tools-and-only-4-can-write-lad-534h</link>
      <guid>https://dev.to/tiaforge/two-routes-to-connect-ai-to-tia-portal-6-tools-and-only-4-can-write-lad-534h</guid>
      <description>&lt;p&gt;Putting an LLM next to a Siemens TIA Portal project in 2026 means picking one of two routes. I picked six tools that have public pages you can actually check: &lt;strong&gt;four can write LAD, only three can run a test&lt;/strong&gt; — and counting LadDoc next to TiaLink is what gets LAD to four.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How this was checked.&lt;/strong&gt; Five of the six entries come from vendor pages read on 2026-10-02 and 10-25; the TiaLink row comes from its own product docs and hands-on use. Every cell a vendor does not state explicitly is marked "not stated" — that is not the same as "missing", and only someone who has installed the thing can tell the difference.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Two routes: whose model is it
&lt;/h2&gt;

&lt;p&gt;Everything on this list falls on one line.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route A — a purpose-built agent.&lt;/strong&gt; The model is fixed, TIA knowledge is baked in, it works out of the box. You describe the job, review the result, sign off. You never pick a model and never touch MCP.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Route B — your own agent plus a tool.&lt;/strong&gt; You choose the brain: Claude Code, Codex, Cursor, whatever. The tool layer only supplies hands — read blocks, edit blocks, compile, run a simulation. The tool can be an MCP server, an HTTP API, or a command-line program; the wiring changes, the route doesn't.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Who supplies the model&lt;/th&gt;
&lt;th&gt;What you have to do&lt;/th&gt;
&lt;th&gt;The cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A · purpose-built agent&lt;/td&gt;
&lt;td&gt;The vendor&lt;/td&gt;
&lt;td&gt;Describe, review, sign&lt;/td&gt;
&lt;td&gt;Model is not swappable; offline and intranet use is the vendor's call; switching clients means starting over&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B · your agent + a tool&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Install the tool, configure the client, write prompts, define acceptance&lt;/td&gt;
&lt;td&gt;Free choice, but you own both the tool quality and the model quality&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;A sells an outcome; B sells a capability.&lt;/strong&gt; That single sentence decides everything downstream: do you want an assistant that hands back finished work, or a set of tools you can wire into your own pipeline?&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Six tools, split by route
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Form&lt;/th&gt;
&lt;th&gt;Versions&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Siemens Industrial Copilot&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;In-product add-in&lt;/td&gt;
&lt;td&gt;Recent TIA Portal&lt;/td&gt;
&lt;td&gt;Vendor, fixed, cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Siemens Eigen Engineering Agent&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;In-product add-in&lt;/td&gt;
&lt;td&gt;V19 U5+ / V20 U4+ / V21&lt;/td&gt;
&lt;td&gt;Vendor, fixed, cloud only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;AnyAutomation Studio&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Desktop IDE&lt;/td&gt;
&lt;td&gt;V15–V21&lt;/td&gt;
&lt;td&gt;Your own key, fully local possible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Coilmind&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Desktop client&lt;/td&gt;
&lt;td&gt;V17–V21&lt;/td&gt;
&lt;td&gt;Included, own key optional, local supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;T-IA Connect&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;REST + MCP&lt;/td&gt;
&lt;td&gt;V17–V21&lt;/td&gt;
&lt;td&gt;Your own key or a local model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;TiaLink&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;HTTP MCP&lt;/td&gt;
&lt;td&gt;V19–V21&lt;/td&gt;
&lt;td&gt;Any MCP client, local or national models&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four A, two B. The split is not accidental: A sells convenience, so it bundles model and knowledge; B sells connectivity, so it hands the model back to you.&lt;/p&gt;

&lt;p&gt;What is not in the table, and why: the open-source side has plenty of projects, but they stop at primitives plus generation and leave verification to you; and pasting code into a browser chatbot is not a route at all — it cannot reach the project.&lt;/p&gt;

&lt;h3&gt;
  
  
  A1: vendor agents — deepest, least free
&lt;/h3&gt;

&lt;p&gt;Eigen is the April 2026 generation: an add-in that talks to the project directly, emits SCL and LAD, generates test and validation logic, and also covers HMI scripting, drives and axes, PROFINET topology, and bulk hardware import from a spreadsheet. It coexists with the 2023 chat-style Industrial Copilot rather than replacing it — one answers a question inside TIA Portal, the other finishes the job.&lt;/p&gt;

&lt;p&gt;Its boundaries are all in Siemens' own documentation: fixed model, cloud only, S7-1200 and S7-1500 only, no older TIA versions, not approved for fail-safe programming, and no documented source-control integration. Those are not defects; they are the product definition — it guarantees &lt;em&gt;behaviour Siemens has certified&lt;/em&gt;, not &lt;em&gt;whatever model you want&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Locking the model has a rationale: in industrial software the hard part is not getting an LLM to write code, it is making the result &lt;strong&gt;accountable&lt;/strong&gt; — who verified it, on which version, against which test cases. Fixed model plus a certified behaviour envelope is the shortest path to a closed responsibility chain. The price is that you cannot swap in a model you fine-tuned, and cannot wire generation into your own pipeline.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deepest integration and least freedom, in the same product.&lt;/strong&gt; If your work is standard TIA Portal, it is the cheapest path; if you need your own agent, your own model, or your own CI, it was never designed to leave you a door.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frfktgh0kcfgufyfo2oqq.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frfktgh0kcfgufyfo2oqq.jpg" alt="Siemens Eigen: one engineer, two screens — the project, and the conversation" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Siemens' own launch visual for Eigen (April 2026). The form factor of this generation: a plug-in plus cloud, with the engineer still at the desk.&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A2: third-party purpose-built clients
&lt;/h3&gt;

&lt;p&gt;This group is not tied to a vendor. It ships its own model and its own TIA knowledge, and sells "install it and work".&lt;/p&gt;

&lt;p&gt;AnyAutomation combines an engineering IDE with an agent: versions all the way back to V15, local models (Ollama, LM Studio) as first-class options, SCL unit tests on PLCSIM Advanced, and a diff you approve before anything is imported. Coilmind covers every block type except F-blocks, documents its simulation testing down to PLCSIM Advanced V3–V6, and also lets you swap in your own key.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw2vbkn9u18drirx93zs7.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw2vbkn9u18drirx93zs7.jpg" alt="AnyAutomation Studio: project tree on the left, SCL in the middle, the agent on the right" width="799" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;AnyAutomation Studio. The line on the right says what this category sells: 12 of 12 test cases passed on PLCSIM Advanced — the verification result is on screen, not in the marketing copy.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The smart part of this group is that it separates the two choices: the model can be yours, the client and the engineering surface stay theirs. You swap the brain, not the hands.&lt;/p&gt;

&lt;p&gt;A category-wide trait: these tools are thick by construction — context, generation and verification arrive bundled, and you have no thinner option. Thin-versus-thick only becomes a real choice on route B.&lt;/p&gt;

&lt;h3&gt;
  
  
  B1: three ways to wire a tool, not one
&lt;/h3&gt;

&lt;p&gt;"Does it support MCP?" answers nothing. MCP is only one of the wirings.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Wiring&lt;/th&gt;
&lt;th&gt;Who works out what tools exist and what arguments they take&lt;/th&gt;
&lt;th&gt;What comes back&lt;/th&gt;
&lt;th&gt;Fits&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;MCP (local process)&lt;/td&gt;
&lt;td&gt;The protocol describes itself; the client reads the tool list and schemas&lt;/td&gt;
&lt;td&gt;Structured results&lt;/td&gt;
&lt;td&gt;One person, one machine, one client&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP (HTTP endpoint)&lt;/td&gt;
&lt;td&gt;Same, but the service lives independently of the client&lt;/td&gt;
&lt;td&gt;Structured results&lt;/td&gt;
&lt;td&gt;Several clients, scripts, CI sharing one service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP API / CLI&lt;/td&gt;
&lt;td&gt;You, or the agent reading documentation&lt;/td&gt;
&lt;td&gt;Usually text or a JSON string&lt;/td&gt;
&lt;td&gt;Gluing onto existing systems, throwaway scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The difference is &lt;strong&gt;who is responsible for explaining the tool list and its parameters&lt;/strong&gt;: with MCP the model reads it, with HTTP and CLI somebody has to write it down, otherwise the model guesses arguments — which is where hallucinations usually start. CLI has an underrated advantage: no service to install, no client config to edit, any agent with a shell can use it. The price is that arguments have to be assembled correctly every single time, and there is no state to carry between calls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verification splits into two levels, and they get conflated constantly.&lt;/strong&gt; &lt;em&gt;Unit verification&lt;/em&gt; checks what one block or one stretch of logic does for a given set of inputs — fast, no full hardware configuration, a hundred scenarios in a row. &lt;em&gt;Whole-machine verification&lt;/em&gt; checks whether the entire project comes up on a virtual CPU: how OBs are scheduled, how the I/O image updates, how blocks call each other, how the HMI behaves. The first is cheap, the second is real. They are not alternatives; they are two rungs of the same ladder.&lt;/p&gt;

&lt;p&gt;How the two B-route tools split across those rungs:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Unit verification&lt;/th&gt;
&lt;th&gt;Whole-machine verification&lt;/th&gt;
&lt;th&gt;Where context comes from&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T-IA Connect&lt;/td&gt;
&lt;td&gt;Test harness (running on PLCSIM)&lt;/td&gt;
&lt;td&gt;Not stated&lt;/td&gt;
&lt;td&gt;Project reports and dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TiaLink&lt;/td&gt;
&lt;td&gt;Its own engine, no TIA Portal required&lt;/td&gt;
&lt;td&gt;PLCSIM&lt;/td&gt;
&lt;td&gt;Built-in project knowledge graph&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Same task, different rung and different data source — which decides what the tool can answer for you.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  B2: the two thick ones
&lt;/h3&gt;

&lt;p&gt;T-IA Connect is the broadest in surface area: blocks and snapshots, project reports, a test harness, version management, several hundred tools exposed to an agent, V17 through V21, both REST and MCP. Its approach is to build the engineering platform first, then let an agent connect to it.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkeccs2sflzsutckhgr8c.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkeccs2sflzsutckhgr8c.jpg" alt="T-IA Connect: 316+ API endpoints, 393+ MCP tools, V17–V21, 100% on-premise" width="799" height="527"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;T-IA Connect's first screen. "100% on-premise" is a deliberate first-screen claim — that is the first dividing line between this class and cloud offerings.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;TiaLink takes the other path: primitives, generation and whole-machine verification in one endpoint on the same machine — bulk import/export, generate SCL and compile, bulk HMI screen generation, start PLCSIM and run a test, then read the project back. Project-level context lives in the gateway as a knowledge graph; LAD generation is handled by another endpoint in the same product line, on purpose. Section B3 explains why.&lt;/p&gt;

&lt;p&gt;Wiring matters a second time here: a local-process gateway lives inside the client process, so closing the client kills the connection and switching clients means reinstalling; an HTTP endpoint runs on the same machine as TIA Portal and can serve Claude Code, Codex, your own scripts and a CI job at once, without fighting each other; a CLI is the lightest, but stateless — every call reconnects to the project.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A gateway is a hand. The capability layer is an eye. With only a hand you have to trust what the AI says; with an eye you can look.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  B3: why three endpoints instead of one program
&lt;/h3&gt;

&lt;p&gt;Compressing each vendor into one table row creates a false impression: that "thick" means "everything stuffed into one binary".&lt;/p&gt;

&lt;p&gt;Split, it is clearer. Producing a LAD block that TIA Portal will import, and running that logic to see if it behaves, are two different jobs: the first is &lt;strong&gt;deterministic generation&lt;/strong&gt; (the same description always produces the same XML, identifiers and wiring assigned automatically, no output at all if validation fails), the second is &lt;strong&gt;deterministic execution&lt;/strong&gt; (same program, same inputs, same result, every time). Written as one program, both get worse.&lt;/p&gt;

&lt;p&gt;So the line is three endpoints, not one program:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Endpoint&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;When you reach for it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Engineering operations (TiaLink, with the project knowledge graph inside)&lt;/td&gt;
&lt;td&gt;Read the project, create and edit blocks, bulk import/export, compile, run PLCSIM, read back after writing; the graph answers "who calls whom, what breaks if I change this"&lt;/td&gt;
&lt;td&gt;TIA Portal is installed and you are changing a real project&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LAD generation (LadDoc Cloud)&lt;/td&gt;
&lt;td&gt;JSON ladder description → validation → SimaticML block, importable as-is&lt;/td&gt;
&lt;td&gt;A description has to become an importable block&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit verification (cloud simulation)&lt;/td&gt;
&lt;td&gt;Executes one block or one stretch of logic scan by scan — scripted cases, exhaustive matrices, seeded fuzzing, invariants&lt;/td&gt;
&lt;td&gt;Behaviour check right after a change; no TIA Portal on the machine; batch regression&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All three are MCP, all three are callable by the same agent, and each works alone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The graph deserves its own paragraph.&lt;/strong&gt; In most tools "context" means the project tree plus a block list — the AI knows which blocks exist, not how they interlock. The project knowledge graph parses an exported project into a queryable model: who calls whom, which network reads or writes which tag, how a signal flows from a physical input to its consumers, which call sites an interface change reaches (transitive, not one hop), dead code, and the classic hazard of several places writing the same signal, plus fan-in, fan-out and call depth. Ask in natural language; the answer carries exact locations.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvx2acvlm5zmp1qyaq5m.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpvx2acvlm5zmp1qyaq5m.png" alt="Impact subgraph: changing FB::Auto_mode reaches its call site and instance DB directly, then walks up through the network, the OB and the block file" width="799" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One impact graph from the knowledge graph. The question is "what does changing this block touch"; the answer is a transitive closure — call sites, instance DB, the enclosing network and OB, all the way to the block file.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is a read-only analysis layer; edits happen on the engineering side. &lt;strong&gt;Analysis and execution being separate is itself a safety boundary&lt;/strong&gt;: the thing that can tell you what a change would do has no permission to make it. It has limits, too — it works from an exported snapshot rather than a live controller, covers LAD, SCL and FBD (older languages are recognised but not analysed in depth), and excludes hardware configuration and HMI content.&lt;/p&gt;

&lt;p&gt;One pleasant side effect of the split: &lt;strong&gt;people without TIA Portal can see results first.&lt;/strong&gt; An unfinished block goes to the cloud engine, comes back passing, and only then travels to the machine that has TIA Portal. The reverse holds as well: when an import fails, go back to the generator and read the validation report instead of poking at TIA Portal.&lt;/p&gt;

&lt;p&gt;The cloud engine verifies the &lt;strong&gt;unit&lt;/strong&gt;, not the machine: it executes one ladder rung or one SCL program scan by scan — contacts, coils, timers, counters, edges, block calls, jumps — with scenarios that can be scripted cases, exhaustive matrices, seeded fuzzing, or invariants, returning PASS/FAIL in one call. It does not replace PLCSIM: PLCSIM checks whether the whole project comes up on a virtual CPU (OB scheduling, I/O image, inter-block calls, HMI), and that step still needs TIA Portal.&lt;/p&gt;

&lt;p&gt;Once separated, the value is obvious: unit verification is cheap enough to run after every block edit; whole-machine verification is expensive enough that it only happens at milestones. &lt;strong&gt;Without the cheap rung, every AI edit waits until you have time to open TIA Portal.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What each route struggles with
&lt;/h3&gt;

&lt;p&gt;A's problem in China's plant networks is connectivity: the model lives in a vendor cloud, and the internal network either has no route or needs an approval. That is compliance, not capability, and the bigger the plant the harder it gets. B's problem is scaffolding: a thin wrapper gives you a hand and nothing else — context and acceptance criteria are yours to build. The same route can go smoothly or end in "the AI writes worse code than I do"; the difference is usually not the tool but whether you have an acceptance process (section 6).&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Of six, four can write LAD and three can run a test
&lt;/h2&gt;

&lt;p&gt;The two numbers are not ratings; they are structural. &lt;strong&gt;LAD requires a structured description&lt;/strong&gt; — element identifiers, wiring, branches, ordering — so one wrong identifier kills the whole import. Plenty of tools write SCL; only four write LAD, and LadDoc makes five. &lt;strong&gt;Testing requires an interface to a simulator&lt;/strong&gt;, downloading the block, feeding inputs and reading results; three tools have that whole chain (AnyAutomation, T-IA Connect, Coilmind), and TiaLink makes four.&lt;/p&gt;

&lt;p&gt;Siemens' Eigen says it "generates test and validation logic"; whether that executes on a simulator disagrees across two public sources, so it is not counted.&lt;/p&gt;

&lt;p&gt;All three of those run PLCSIM Advanced, which means whole-machine verification and a Windows box with TIA Portal installed. &lt;strong&gt;Exactly one tool here can do unit verification of a single block without TIA Portal: TiaLink.&lt;/strong&gt; Why that rung deserves its own column is in B3.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generating is the entry ticket; verifying behaviour is what counts as integration.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. "Wiring AI up" and "integrating AI" are two different things
&lt;/h2&gt;

&lt;p&gt;There is a near-miss worth naming: OPC UA or Modbus can also "connect AI to TIA Portal" — but that path only exposes runtime data: tag values, registers, alarm bits. &lt;strong&gt;An AI that sees tag values still cannot see program blocks, cross-references, or compile errors&lt;/strong&gt;, so it cannot change a program.&lt;/p&gt;

&lt;p&gt;The test is simple: if the AI can see blocks and trigger a compile, that is integration; if it only sees values, that is data collection. You want both eventually, but never confuse them — one of them cannot fix a bug in an FB.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Building your own tool: five traps
&lt;/h2&gt;

&lt;p&gt;Whether you install an existing tool or write your own, these five arrive within the first two days. None of them depends on your language or framework; they come from how TIA Portal is built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One: a version upgrade splits the assemblies.&lt;/strong&gt; The single engineering assembly of earlier versions was split — as of V21 — into base, PLC, HMI and safety assemblies in a new subfolder. A PLC-only tool must reference both base and PLC; namespaces are unchanged, but reference lists and runtime resolution paths are not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two: Copy Local has to be off.&lt;/strong&gt; Copying the assemblies next to your binary fights the copy TIA Portal already has loaded — at best a load failure, at worst strange behaviour. Reference them, do not copy them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Three: resolve assemblies by name at runtime.&lt;/strong&gt; Since you are not copying, the program has to register a resolver before it touches any engineering type, looking assemblies up by simple name in the TIA Portal install directory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Four: build once per major version.&lt;/strong&gt; The binaries are not compatible across versions; one build, one version.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Five: default to a preview mode and return structured results.&lt;/strong&gt; Writes should simulate first and only commit when explicitly allowed; and what comes back to the model should be structured data, not log text — logs invite the model to guess what happened.&lt;/p&gt;

&lt;p&gt;Each of these costs half a day to two days. Together they are the real price of "build it yourself": not the install, but &lt;strong&gt;redoing it every time a version ships&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Acceptance: zero compile errors is only layer one
&lt;/h2&gt;

&lt;p&gt;The most misleading sentence after an AI writes code is "it compiles". Five layers, each proving something different:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Proves&lt;/th&gt;
&lt;th&gt;Cannot prove&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Structural validation (pre-import)&lt;/td&gt;
&lt;td&gt;Syntax is valid, names do not collide&lt;/td&gt;
&lt;td&gt;Whether the logic is right&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compile&lt;/td&gt;
&lt;td&gt;Types, references, addresses, instruction usage&lt;/td&gt;
&lt;td&gt;Behaviour across scan cycles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit verification (one block / one stretch)&lt;/td&gt;
&lt;td&gt;Behaviour for given inputs, edges, counting, interlocks&lt;/td&gt;
&lt;td&gt;How blocks interlock, how OBs schedule, how the I/O image updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Whole-machine verification (PLCSIM)&lt;/td&gt;
&lt;td&gt;The project comes up on a virtual CPU; scheduling and I/O image hold&lt;/td&gt;
&lt;td&gt;Real I/O and field disturbances&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read-back comparison&lt;/td&gt;
&lt;td&gt;What is in the project equals what you think you wrote&lt;/td&gt;
&lt;td&gt;Whether the requirement itself is right&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The middle layers are the ones people skip, and they are the valuable ones.&lt;/p&gt;

&lt;p&gt;A real example: during a refactor an impulse timer became an on-delay timer. Compile: 0 errors, 0 warnings. The code looked entirely normal. Under unit verification the motor simply would not turn — the difference was one instruction name, and the compiler cannot object because both are type-correct. After reverting, the same program passed 5,735 scans.&lt;/p&gt;

&lt;p&gt;That is why "it compiles" is not a delivery criterion. It catches wrong types and wrong addresses; it cannot catch wrong semantics — and in control engineering the errors that hurt are almost always semantic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Zero compile errors proves it looks like a program. Unit verification proves it means what you think. Whole-machine verification proves it still works once installed.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Choose by the work in front of you
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Your situation&lt;/th&gt;
&lt;th&gt;Route&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard TIA Portal workflow, want vendor backing&lt;/td&gt;
&lt;td&gt;A1 (Eigen)&lt;/td&gt;
&lt;td&gt;Deepest integration, vendor support when it breaks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want out-of-the-box, but your own model&lt;/td&gt;
&lt;td&gt;A2 (AnyAutomation / Coilmind)&lt;/td&gt;
&lt;td&gt;The client is theirs, the model can be yours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need Git, CI, test reports&lt;/td&gt;
&lt;td&gt;B (T-IA Connect)&lt;/td&gt;
&lt;td&gt;Version management and a test harness are part of the product&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already have an agent, just need hands&lt;/td&gt;
&lt;td&gt;Build your own (section 5)&lt;/td&gt;
&lt;td&gt;Light, hackable, no network needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Need to know "will this change break another block"&lt;/td&gt;
&lt;td&gt;The thick B side (with a knowledge graph)&lt;/td&gt;
&lt;td&gt;A thin wrapper has no call graph or data flow to query&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;On-site network cannot reach a cloud&lt;/td&gt;
&lt;td&gt;A2 with local models, or the thick B side&lt;/td&gt;
&lt;td&gt;Model runs on the machine, the project never leaves it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Projects on V17 / V18&lt;/td&gt;
&lt;td&gt;A2 (goes back to V15) or the B side&lt;/td&gt;
&lt;td&gt;The vendor agents do not cover older versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Want to verify logic before TIA Portal is installed&lt;/td&gt;
&lt;td&gt;Unit verification (cloud)&lt;/td&gt;
&lt;td&gt;A single block can return PASS/FAIL without an engineering box&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  8. Three boundaries
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Fail-safe programs are off limits.&lt;/strong&gt; Siemens' own documentation excludes F-programs; third-party tools should not go further. Responsibility for F-program validation sits with people and the certification process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versions are hard-bound.&lt;/strong&gt; A tool supports the versions it says it supports. Older projects either get their logic verified in simulation first, or need a route with a wider version range.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write protection belongs in the process, not in a warning.&lt;/strong&gt; Preview before write, read back after write, snapshot before bulk changes — those three do more than any "please be careful" notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Four questions that come up
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does TIA Portal need anything extra enabled?&lt;/strong&gt; Yes. Openness — the interface that can read and write the project — is not on by default. It must be selected during installation, and the Windows account running the tool has to be in the &lt;code&gt;Siemens TIA Openness&lt;/code&gt; group. Without both, nothing connects, and the error will not tell you why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does it need internet access?&lt;/strong&gt; Depends which part. Engineering operations do not: the model can run locally and the gateway sits on the same machine as TIA Portal, so the project never leaves it. LAD generation and cloud simulation are online services — useful when there is no TIA Portal yet, or when a batch needs regression. Vendor agents are cloud inference, so connectivity is a hard requirement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once a gateway is installed, can the AI change programs?&lt;/strong&gt; It can — but do not start there. Let it read only for a week: read blocks, read cross-references, read compile errors, answer questions. When its understanding survives your spot checks, enable writing, and make preview and read-back part of the process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can AI-written ladder logic be downloaded to a real machine?&lt;/strong&gt; Not directly. Even after all five layers, on-site work is only the first small step of the last one: simulate, dry-run, single machine, then commissioning. That order has nothing to do with whether AI wrote the code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;AI tools for TIA Portal compared: who writes LAD, who tests, who runs offline&lt;/em&gt;, svghmi.pro, checked 2026-10-02&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Best AI for PLC Programming 2026: 9 Tools Compared&lt;/em&gt;, anyautomation.ch, 2026-10-25&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Building a Siemens TIA Portal MCP Server on Openness V21: A Hands-On Guide&lt;/em&gt;, controlbyte.tech, updated 2026-10-29&lt;/li&gt;
&lt;li&gt;Siemens Eigen Engineering Agent launch and product pages (version range, cloud-only constraint, fail-safe boundary)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vendor pricing and licensing change fast, so this piece lists billing models rather than amounts. "Not stated" means the vendor's public pages do not say; it does not mean the capability is absent.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this comparison adds
&lt;/h2&gt;

&lt;p&gt;The two routes are not "new tool versus old tool"; they are two ways of selling. A sells an outcome, B sells a capability — and inside B, thickness varies by an order of magnitude. The open-source projects stop at primitives or generation: light, fast, offline-friendly, but with no cross-reference data and no verifier, &lt;strong&gt;so they can change things without being able to tell you what the change breaks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The thicker approach is to split the work into three callable endpoints: engineering operations (with the project knowledge graph inside), LAD generation, and unit verification. Each has its own determinism — the same description always produces the same XML, the same project parses to the same graph, the same program with the same inputs always gives the same result — so each can be accepted on its own and still be chained by one agent.&lt;/p&gt;

&lt;p&gt;Context is where the spread is widest: exactly one of the six offers a program-level graph instead of a project tree and a block list. And that graph is read-only, with edits happening on the engineering side: &lt;strong&gt;the thing that can tell you what a change would do has no permission to make it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Selection advice, in reverse order of how people usually ask: first ask whether your model can stay on your machine, then whether you can see and verify what the AI changed, and only then how good its generated code looks. The first two decide whether you can deploy at all; the last one only decides how pleasant it is.&lt;/p&gt;

&lt;p&gt;Three entry points: &lt;a href="https://tia.hubwiz.com/tia-portal?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-ai-tia" rel="noopener noreferrer"&gt;TiaLink&lt;/a&gt; (engineering operations, V19 / V20 / V21, same machine as TIA Portal); &lt;a href="https://tia.hubwiz.com/lad/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-ai-tia" rel="noopener noreferrer"&gt;LadDoc Cloud&lt;/a&gt; (LAD generation, produces importable blocks); &lt;a href="https://tia.hubwiz.com/sim/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=connect-ai-tia" rel="noopener noreferrer"&gt;cloud simulation&lt;/a&gt; (run ladder and SCL scan by scan without TIA Portal). Cross-references and call relationships are queried inside the engineering endpoint.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>plc</category>
      <category>automation</category>
      <category>programming</category>
    </item>
    <item>
      <title>AI + PLC Programming: The Missing Layer Is Project Context, Not Model Quality</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Wed, 04 Nov 2026 13:41:17 +0000</pubDate>
      <link>https://dev.to/tiaforge/ai-plc-programming-the-missing-layer-is-project-context-not-model-quality-j1m</link>
      <guid>https://dev.to/tiaforge/ai-plc-programming-the-missing-layer-is-project-context-not-model-quality-j1m</guid>
      <description>&lt;p&gt;Ask an AI agent &lt;em&gt;"who reads and who writes &lt;code&gt;additional_variables.tag3&lt;/code&gt;?"&lt;/em&gt; in a real TIA Portal project, and you can tell within one reply whether it is guessing.&lt;/p&gt;

&lt;p&gt;A grounded answer is four rows: two contacts reading it at lines &lt;strong&gt;152&lt;/strong&gt; and &lt;strong&gt;222&lt;/strong&gt;, two coils writing it at lines &lt;strong&gt;217&lt;/strong&gt; and &lt;strong&gt;262&lt;/strong&gt;, all inside &lt;code&gt;Auto_mode&lt;/code&gt;, network 1. An ungrounded answer is a paragraph that &lt;em&gt;sounds&lt;/em&gt; right.&lt;/p&gt;

&lt;p&gt;That gap — four rows versus a paragraph — is not a model-quality problem. Current models write SCL fine. It is a &lt;strong&gt;project-context&lt;/strong&gt; problem: a PLC project is a graph, and we keep handing the model a stack of files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three ways agents get project context today, and what each one loses
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Answers well&lt;/th&gt;
&lt;th&gt;Silently loses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Paste the export into the prompt&lt;/td&gt;
&lt;td&gt;"What does this network do?"&lt;/td&gt;
&lt;td&gt;Anything over the window; all cross-block coupling once you truncate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Global search / grep&lt;/td&gt;
&lt;td&gt;"Where does this string appear?"&lt;/td&gt;
&lt;td&gt;Direction (read vs. write, call vs. instance), and the &lt;em&gt;absence&lt;/em&gt; of an expected relation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Let the agent browse and remember&lt;/td&gt;
&lt;td&gt;Short sessions, one block at a time&lt;/td&gt;
&lt;td&gt;Freshness — it may be reasoning over what it read 40 turns ago, and it will not say so&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Chunking is the worst of the three, because the relationships you most need are exactly the ones that cross the chunk boundary. In a ladder program that boundary is everywhere: a coil in network N3 writes a tag that a contact in network N7 reads, and the tag itself is defined in a tag table that is a third file.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a project graph actually holds
&lt;/h2&gt;

&lt;p&gt;The graph we used has four kinds of content, and you need all four:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;What's in it&lt;/th&gt;
&lt;th&gt;What it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Nodes&lt;/td&gt;
&lt;td&gt;Blocks, networks, interface members, tags, LAD parts (contacts/coils/boxes), SCL statements&lt;/td&gt;
&lt;td&gt;"What is in this project?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edges&lt;/td&gt;
&lt;td&gt;Call, instantiate, type, read, write, wire&lt;/td&gt;
&lt;td&gt;"Who calls whom, who writes this tag?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Provenance&lt;/td&gt;
&lt;td&gt;A label on &lt;em&gt;every&lt;/em&gt; edge: parsed from a call / parsed from an operand / structural containment / tag-table membership / wire&lt;/td&gt;
&lt;td&gt;"Is this claim evidence, or an inference?"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unresolved references&lt;/td&gt;
&lt;td&gt;Names that are referenced but defined nowhere in the project; deliberately &lt;strong&gt;not&lt;/strong&gt; forced into an edge&lt;/td&gt;
&lt;td&gt;"What is missing?" — the most valuable list before a handover or a migration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That third row is the part people skip. In a graph built from a real project, "&lt;code&gt;tag3&lt;/code&gt; is written by a coil in &lt;code&gt;Auto_mode&lt;/code&gt;" and "&lt;code&gt;tag3&lt;/code&gt; is written by a coil in &lt;code&gt;Auto_mode&lt;/code&gt;, &lt;em&gt;per an operand parse&lt;/em&gt;, line 217" are different claims with different trust levels. If your agent cannot separate them, you are back to prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The run, with numbers
&lt;/h2&gt;

&lt;p&gt;Target: &lt;code&gt;PLC_SCADA_HMI&lt;/code&gt;, an MIT-licensed open-source project (&lt;code&gt;rassulz/multirobot_painting_control_system&lt;/code&gt;), opened in TIA Portal V19 on an &lt;strong&gt;S7-1500 / ET200MP station, CPU 1513F-1 PN&lt;/strong&gt; (&lt;code&gt;6ES7 513-1FL02-0AB0&lt;/code&gt;, firmware V2.9). Read-only the whole way: no imports, no block edits, no save, project dirty flag unchanged.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Measured&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Build time, from scratch&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1.2 s&lt;/strong&gt; (one sentence to the agent; no further project access after that)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nodes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;227&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edges&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;517&lt;/strong&gt; — containment 220, calls 2, instantiation 2, reads 87, writes 52, wires 154&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unresolved references&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;8&lt;/strong&gt; (7 "pending", 1 "external")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Project size&lt;/td&gt;
&lt;td&gt;OB1 (4 networks), 2 FBs, 2 instance DBs, 2 tag tables (51 tags), 0 UDTs, 0 duplicate names&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;1.2 seconds is not the interesting number. The interesting number is &lt;strong&gt;517 edges against 227 nodes&lt;/strong&gt; — the relationship layer is twice the size of the object layer. That ratio is the reason a context window is the wrong container: you don't have a document of 227 things, you have a network of 517 claims about them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three checks before you believe any answer
&lt;/h2&gt;

&lt;p&gt;Any graph-derived answer is only as good as its freshness, and the failure here is silent. Before asking your first real question, get three facts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Which project is the graph attached to?&lt;/strong&gt; (should be the one you're analyzing — here, &lt;code&gt;PLC_SCADA_HMI&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When was it built?&lt;/strong&gt; If you edited a block after the build, the graph is stale and &lt;em&gt;nothing will tell you&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Was it built from the currently open project, or is it a leftover graph from last week?&lt;/strong&gt; A stale graph looks completely healthy. It just describes a project that no longer exists.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If any of the three is unclear: rebuild, then ask. Two related traps: an empty graph gets rejected outright (the previous graph is kept, so you may be looking at yesterday's), and a graph query that hits its result cap says so — "there's more" — which means &lt;em&gt;you have not seen the project&lt;/em&gt;, you have seen a page of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  One probe at a time
&lt;/h2&gt;

&lt;p&gt;The single most repeated mistake in this kind of analysis is batching six questions into one prompt, getting a long answer, and not noticing that section four was truncated. Six probes, six turns, each verifiable on its own:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Probe&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;What a good answer looks like&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Inventory&lt;/td&gt;
&lt;td&gt;What's in this project: blocks (type/number/language/network count), instance DBs, tag tables, UDTs, CPU and order number? List duplicates separately, write 0 where empty&lt;/td&gt;
&lt;td&gt;A table that reconciles against the TIA project tree, with explicit zeros&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Find&lt;/td&gt;
&lt;td&gt;Find &lt;code&gt;Auto_mode&lt;/code&gt; (or &lt;code&gt;CV-101&lt;/code&gt;, or &lt;code&gt;Conveyor&lt;/code&gt;), widening exact → prefix → comment word → contains; tell me which tier matched and whether results were truncated&lt;/td&gt;
&lt;td&gt;The match tier named, plus "there is more" if there is&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Dissect&lt;/td&gt;
&lt;td&gt;Open &lt;code&gt;OB1::Main&lt;/code&gt; once: interface, each network, what it calls, read/write list, unresolved refs inside the block; say which part was truncated&lt;/td&gt;
&lt;td&gt;One block, one page, complete&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Relations&lt;/td&gt;
&lt;td&gt;Who reads and who writes &lt;code&gt;additional_variables.tag3&lt;/code&gt;? Reads and writes separately, each with block + network + line + provenance&lt;/td&gt;
&lt;td&gt;Rows, not sentences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Impact&lt;/td&gt;
&lt;td&gt;If I change or delete &lt;code&gt;FB::Auto_mode&lt;/code&gt;, what is affected? By depth, and through which relation?&lt;/td&gt;
&lt;td&gt;Depth-numbered hits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Gaps&lt;/td&gt;
&lt;td&gt;List every unresolved reference with file and line, mark pending/external, classify truly-missing / external-library / to-be-added; do not guess&lt;/td&gt;
&lt;td&gt;A table you can act on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Probe 2 is worth its own paragraph. On this project, &lt;code&gt;Auto_mode&lt;/code&gt; matched four things — the FB, a call site &lt;code&gt;Main::N1::I1&lt;/code&gt;, its file, and its network — and the answer came back with "there is more". Without the "was it truncated" question you would have concluded "four references" and been wrong. &lt;strong&gt;Ask about truncation every single time.&lt;/strong&gt; It costs one sentence and it is the difference between an inventory and a sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  The payoff: four rows you can take to a review
&lt;/h2&gt;

&lt;p&gt;Probe 4, verbatim result for &lt;code&gt;additional_variables.tag3&lt;/code&gt;. First the shape, then the table — because the shape is the part prose loses:&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk00rrg7p4hbgdeo3ne77.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fk00rrg7p4hbgdeo3ne77.png" alt="Relationship subgraph: call site Main::N1::I1 calls FB::Auto_mode, and tag3 is read by two contacts and written by two coils" width="799" height="513"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Sample subgraph — the full graph is 227 objects / 517 relations. Green = call, blue = read, orange = write, grey dashed = containment and LAD wiring, purple dash-dot = tag-table membership. Figure labels are Chinese; every identifier in it is the one used in the text, and every arrow can be traced back to a line number.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Relation&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;th&gt;Provenance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;member of tag table&lt;/td&gt;
&lt;td&gt;&lt;code&gt;additional_variables&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;tag-table containment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;read&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Auto_mode::N1::L13&lt;/code&gt; (contact)&lt;/td&gt;
&lt;td&gt;152&lt;/td&gt;
&lt;td&gt;operand parse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;write&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Auto_mode::N1::L26&lt;/code&gt; (coil)&lt;/td&gt;
&lt;td&gt;217&lt;/td&gt;
&lt;td&gt;operand parse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;read&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Auto_mode::N1::L27&lt;/code&gt; (contact)&lt;/td&gt;
&lt;td&gt;222&lt;/td&gt;
&lt;td&gt;operand parse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;write&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Auto_mode::N1::L35&lt;/code&gt; (coil)&lt;/td&gt;
&lt;td&gt;262&lt;/td&gt;
&lt;td&gt;operand parse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things to notice. First, &lt;strong&gt;reads and writes are separate columns of truth&lt;/strong&gt;: 2 reads + 2 writes means a change to this tag touches four ladder positions, all between lines 152 and 262 — that is the entire software blast radius of the change, stated as a list of places. "This tag is used in the auto mode block" is not that; it cannot be checked and it cannot be assigned.&lt;/p&gt;

&lt;p&gt;Second, &lt;strong&gt;the line numbers make the claim auditable&lt;/strong&gt;. Not "trust me, the graph says so" — open the XML, go to line 217, look at the coil. If an audit finds the graph wrong at line 217, you now know something about the graph for every subsequent answer. This is the whole reason to prefer a graph over a summary: it gives reviewers a way to be lazy &lt;em&gt;safely&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact analysis: the default edges answer a different question
&lt;/h2&gt;

&lt;p&gt;For &lt;code&gt;FB::Auto_mode&lt;/code&gt;, the default relation set (call / instantiate / read / write) gives:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Depth&lt;/th&gt;
&lt;th&gt;Through&lt;/th&gt;
&lt;th&gt;Hit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;call&lt;/td&gt;
&lt;td&gt;call site &lt;code&gt;Main::N1::I1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;instantiation&lt;/td&gt;
&lt;td&gt;instance DB &lt;code&gt;InstanceDB::Block_1_DB&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which reads as one honest sentence: &lt;em&gt;Auto_mode is called from network 1 of the main program and its instance DB is Block_1_DB; change the interface and both need a look.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But "which block does it live inside?" is &lt;strong&gt;not&lt;/strong&gt; in that answer, because containment is not part of the default set. Add containment and walk up — the two layers are drawn on the same figure below: the green/violet edges are what the default relation set can answer, and ①–④ are the containment chain that only appears once you add it.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6a6gn8ryqa2umi9r6kx3.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6a6gn8ryqa2umi9r6kx3.png" alt="Impact subgraph: call site and instance DB reach FB::Auto_mode directly, while containment walks up through Auto_mode.xml, network Main::N1, OB1::Main, Main.xml" width="799" height="473"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Arrows are drawn in the normal containment direction (outer source file → block → network → inner file). Impact analysis walks them **backwards&lt;/em&gt;&lt;em&gt;, which is why the node nearest &lt;code&gt;FB::Auto_mode&lt;/code&gt; is depth 1, not depth 4.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Above, in table form:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Depth&lt;/th&gt;
&lt;th&gt;Through&lt;/th&gt;
&lt;th&gt;Hit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;containment&lt;/td&gt;
&lt;td&gt;its source file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;containment&lt;/td&gt;
&lt;td&gt;the network it sits in (&lt;code&gt;Main::N1&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;containment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;OB1::Main&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;containment&lt;/td&gt;
&lt;td&gt;the main program's source file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are two different questions, and conflating them is the fastest way to a wrong edit plan: the first says &lt;strong&gt;who is affected&lt;/strong&gt; (callers, instance DBs, tag readers), the second says &lt;strong&gt;where it is nested&lt;/strong&gt; (file → network → block → file). Keep them as two probes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most useful output is the list of things that don't exist
&lt;/h2&gt;

&lt;p&gt;Eight references in this project point at objects the project does not define. The graph does not invent edges for them; it puts them in a table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;th&gt;Reference (written)&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pending&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IEC_Timer_0_DB_3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blocks/Auto_mode.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;332&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pending&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IEC_Timer_0_DB_4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blocks/Auto_mode.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;337&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pending&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IEC_Timer_0_DB_1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blocks/Auto_mode.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;483&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pending&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IEC_Timer_0_DB_2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blocks/Auto_mode.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;488&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;external&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IEC_Timer_0_DB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blocks/Auto_mode.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;493&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pending&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IEC_Timer_0_DB_3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blocks/Manu_mode.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;232&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pending&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IEC_Timer_0_DB_4&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blocks/Manu_mode.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;237&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pending&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IEC_Timer_0_DB_2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;blocks/Manu_mode.xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;323&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Cross-check: all eight are timer coils writing an instance — and both of these FBs have &lt;strong&gt;empty interfaces&lt;/strong&gt;, and no global DB with those names exists. So this is not a parser miss. The blocks reference timer instance DB names that were never created in this project. A search finds the name; only a project-wide graph can tell you the name resolves to nothing.&lt;/p&gt;

&lt;p&gt;Then the compile: &lt;code&gt;No block was compiled. All blocks are up-to-date.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Read that sentence carefully before you put it in a report. It does not mean "the program is fine." It means "nothing needed compiling." A green compile is evidence about the compiler's queue, not about the program — and it is exactly how a dangling timer instance survives to commissioning.&lt;/p&gt;

&lt;p&gt;The last step of the probe is the one only a human can do: classify each row as &lt;strong&gt;truly missing&lt;/strong&gt; (add the object), &lt;strong&gt;external library&lt;/strong&gt; (this project cannot define it; import something), or &lt;strong&gt;to be added later&lt;/strong&gt;. The agent assembles the evidence; the judgment stays with the engineer who owns the machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this cannot do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It reports &lt;strong&gt;what is&lt;/strong&gt;, never &lt;strong&gt;what should be&lt;/strong&gt;. An empty FB interface and two empty OB networks are facts; whether that is intentional design or a half-finished migration is a conversation with whoever built it.&lt;/li&gt;
&lt;li&gt;It knows nothing about &lt;strong&gt;wiring, channels, or hardware&lt;/strong&gt;. A tag addressed to &lt;code&gt;%I29.4&lt;/code&gt; is a string in a table until someone checks the terminal strip.&lt;/li&gt;
&lt;li&gt;It does not replace a &lt;strong&gt;compile, a simulation, or a safety review.&lt;/strong&gt; Those prove different things, and one never substitutes for another.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unresolved ≠ broken code.&lt;/strong&gt; "Pending" often just means the object never entered this copy of the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Do it on your own project in 30 minutes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pick one project you inherited, not one you wrote. The point is to be surprised.&lt;/li&gt;
&lt;li&gt;Build the graph, then immediately confirm the three checks (project name, build time, current-project origin). Write the build time into your notes — you will need it to know when the graph went stale.&lt;/li&gt;
&lt;li&gt;Inventory probe. Reconcile against the TIA project tree by hand, once. If they disagree, stop; the graph is not trustworthy yet.&lt;/li&gt;
&lt;li&gt;Pick &lt;strong&gt;one&lt;/strong&gt; tag that matters — an output enable, an interlock, a handshake bit — and ask probe 4. Read the four (or forty) rows.&lt;/li&gt;
&lt;li&gt;Sample two or three line numbers back into the XML or the TIA editor. Verify. This is the step everyone skips and the only one that calibrates trust.&lt;/li&gt;
&lt;li&gt;Ask probe 5 twice: default relations, then with containment.&lt;/li&gt;
&lt;li&gt;Ask probe 6, then classify every row yourself. Put the classification in the report, not in your head.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expect the surprises to come from steps 4 and 6. Impact analysis on a block you "know" is where the containment layers show you the file you forgot, and the tag probes routinely find a writer in a block nobody wanted to touch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our take: project context is a data structure, not a longer prompt
&lt;/h2&gt;

&lt;p&gt;The industry conversation about AI and PLC programming is mostly about generation — better SCL, better ladder, better conversions. That is the part that got solved first, because it only needs the spec.&lt;/p&gt;

&lt;p&gt;The part that did not get solved is context, and prompt engineering cannot fix it, for a structural reason: &lt;strong&gt;a graph does not fit in a sequence.&lt;/strong&gt; You can always paste more text, but pasting more text makes the coupling you need &lt;em&gt;less&lt;/em&gt; visible, not more, because the relationships are spread across the very boundaries you are introducing.&lt;/p&gt;

&lt;p&gt;Three things are worth taking from this even if you never build a graph:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ask for provenance on every claim.&lt;/strong&gt; "Parsed from an operand, line 217" versus "the block seems to use this tag" — one is evidence, one is a sentence. AI-assisted engineering fails quietly when the two are formatted the same way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proofread for truncation, not for tone.&lt;/strong&gt; Long outputs from agents are persuasive at the paragraph level and unreliable at the inventory level. The question "did you see all of it?" should be muscle memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat absence as a finding.&lt;/strong&gt; The eight dangling timer references are worth more than the 87 read edges, because a compile will never tell you about them and a human search will only confirm the name appears somewhere. Missing relations are only visible against a complete model of the project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The lesson this came from is &lt;a href="https://tia.hubwiz.com/learn-lad/ai/project-knowledge-graph/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=plc-project-knowledge-graph" rel="noopener noreferrer"&gt;Lesson 6 of PLC AI in Practice&lt;/a&gt; — a free, text-based course (no video) that walks the same project through six probes with copy-paste prompts, ~30 minutes. The tool behind it is &lt;a href="https://tia.hubwiz.com/tia-link?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=plc-project-knowledge-graph" rel="noopener noreferrer"&gt;TiaLink&lt;/a&gt;, an MCP gateway that connects TIA Portal to an agent and runs locally, so project data stays in the plant.&lt;/p&gt;

&lt;p&gt;If you'd rather start from the human problem than the tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.hubwiz.com/blog/taking-over-legacy-plc-program-4-steps/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=plc-project-knowledge-graph" rel="noopener noreferrer"&gt;Taking over a legacy PLC program: a 4-step forensics routine&lt;/a&gt; (in Chinese) — what to do in the first 48 hours, before any analysis;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.hubwiz.com/blog/ai-plc-programming-7-lessons-verification/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=plc-project-knowledge-graph" rel="noopener noreferrer"&gt;AI PLC programming: 7 hands-on lessons, each with a verifiable gate&lt;/a&gt; (in Chinese) — how this lesson fits the series;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.hubwiz.com/blog/tia-portal-openness-mcp-write-guards/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=plc-project-knowledge-graph" rel="noopener noreferrer"&gt;Write protection in TIA Openness MCP tools&lt;/a&gt; (in Chinese) — why "read-only" has to be designed, not promised.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Disclosure: every measured number above — 227 nodes, 517 edges (220/2/2/87/52/154), 8 unresolved references, 1.2 s build, lines 152–262, the `IEC_Timer_0_DB&lt;/em&gt;` rows — comes from the course's own published run on 2026-10-27 (TIA Portal V19, TiaLink 1.4.0.0). I did not re-run the measurement independently for this post. Prompt examples were written in Chinese in the lesson and are translated here.*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>plc</category>
      <category>automation</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why AI Writes SCL But Rarely Ladder Logic: Text Is Cheap, Diagrams Need an IR</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Wed, 04 Nov 2026 08:45:22 +0000</pubDate>
      <link>https://dev.to/tiaforge/why-ai-writes-scl-but-rarely-ladder-logic-text-is-cheap-diagrams-need-an-ir-3ijo</link>
      <guid>https://dev.to/tiaforge/why-ai-writes-scl-but-rarely-ladder-logic-text-is-cheap-diagrams-need-an-ir-3ijo</guid>
      <description>&lt;p&gt;Ask an AI assistant for a PLC program that runs a conveyor — start button, stop button, e-stop, overload, contactor feedback, and a fault that latches unless you reset it.&lt;/p&gt;

&lt;p&gt;You get SCL. Or ST. It looks plausible: a &lt;code&gt;TON&lt;/code&gt;, an &lt;code&gt;SR&lt;/code&gt;, a few &lt;code&gt;IF&lt;/code&gt;s, sensible variable names.&lt;/p&gt;

&lt;p&gt;Now ask for the same thing as a ladder diagram. You get an ASCII sketch, or a sentence telling you to put &lt;code&gt;Start&lt;/code&gt; in parallel with &lt;code&gt;Run&lt;/code&gt; and then in series with the NC contact of &lt;code&gt;Stop&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is not laziness. Text languages and graphical languages sit at different costs for a token-predicting model, and ladder logic is on the expensive side of that gap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gap is three layers, not a matter of drawing skill
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;SCL / ST&lt;/th&gt;
&lt;th&gt;Ladder (LD)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model output&lt;/td&gt;
&lt;td&gt;The token sequence &lt;strong&gt;is&lt;/strong&gt; the source file&lt;/td&gt;
&lt;td&gt;Must be a graph: networks, series/parallel, branches, joins, element UIds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Intermediate representation&lt;/td&gt;
&lt;td&gt;None needed&lt;/td&gt;
&lt;td&gt;Required. Something has to record the topology before anything can be generated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generation&lt;/td&gt;
&lt;td&gt;Write to disk, compile, fix a line, retry&lt;/td&gt;
&lt;td&gt;A deterministic generator assigns UIds and wires and emits strict SimaticML XML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gate&lt;/td&gt;
&lt;td&gt;Compile errors are still fixable&lt;/td&gt;
&lt;td&gt;One validation issue and no XML is produced at all&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A model emits tokens. SCL is tokens. The path from "model output" to "compilable source" is essentially zero-length, and when it fails you get a line number.&lt;/p&gt;

&lt;p&gt;Ladder logic isn't text. What a rung actually encodes is topology: which contacts are in parallel, where the branch rejoins, which network a timer belongs to, which element feeds which. Natural language can't carry that, so an AI that "writes ladder" has to first write down the diagram as data.&lt;/p&gt;

&lt;p&gt;That's the whole trick, and it's why so few tools do it: &lt;strong&gt;you don't ask the model to draw the diagram, you ask it to emit the diagram as a data structure, and a deterministic program does the drawing.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Somebody has to define a ladder IR and write a generator that turns it into SimaticML. SCL reuses a compiler that already exists. Ladder doesn't have that path for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the pipeline looks like when it exists
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IR&lt;/strong&gt; — the ladder document is JSON: networks, elements, operands, connections.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation gate&lt;/strong&gt; — before generation, the document is checked layer by layer: document shape, block interface, networks and parallel branches, operands, block calls. One issue and no XML is emitted, so you never hand TIA Portal a block that cannot be imported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic generation&lt;/strong&gt; — the same document always produces byte-identical SimaticML; UIds and wire connections are assigned by the generator, output targeting V19 &lt;code&gt;&amp;lt;SW.Blocks.FC|FB|OB&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Into the project&lt;/strong&gt; — &lt;a href="https://tia.hubwiz.com/tia-link?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai-lad-generation" rel="noopener noreferrer"&gt;TiaLink&lt;/a&gt; imports the XML into the TIA Portal project you have open, compiles the whole software, and reads back objects and call relationships. Same-named objects are not overwritten by default, and the project is not saved automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last two points matter more than they look: "generated" and "imported" are not the same as "the application compiles". The validation-and-generation half is &lt;a href="https://tia.hubwiz.com/lad?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai-lad-generation" rel="noopener noreferrer"&gt;LadDoc Cloud&lt;/a&gt;; the import-and-compile half is &lt;a href="https://tia.hubwiz.com/tia-link?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai-lad-generation" rel="noopener noreferrer"&gt;TiaLink&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A real run: one message in, four objects out, whole-software compile 0 errors / 0 warnings
&lt;/h2&gt;

&lt;p&gt;Environment: TIA Portal V19, S7-1500 (CPU 1516-3 PN/DP), gateway on the same machine, empty project.&lt;/p&gt;

&lt;p&gt;One message, and no follow-up message after it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We have a conveyor that has to run on a PLC. On the field side there is a start button, a stop button, an e-stop, an overload relay, a contactor feedback signal, and a reset button; on the output side the motor contactor plus two fault indicators.&lt;/p&gt;

&lt;p&gt;Press start: the motor runs and stays running. Press stop, trip the e-stop, or trip the overload: the motor stops.&lt;/p&gt;

&lt;p&gt;If the motor is running and no contactor feedback arrives within 2 seconds, raise a feedback fault, stop the motor, and latch it. Once the protection has recovered, the reset button clears the fault.&lt;/p&gt;

&lt;p&gt;The main program has to call it every scan cycle. The e-stop and the safety circuit are handled by hardware; the program only does interlocking and annunciation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Nothing was specified about block decomposition, block numbers, DB versus tags, or how the instance DB should be created. That was the agent's call; the operator's job is to state the requirement and verify the result.&lt;/p&gt;

&lt;p&gt;What it delivered:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Object&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Number&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DB_Conveyor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;global DB&lt;/td&gt;
&lt;td&gt;DB10&lt;/td&gt;
&lt;td&gt;Field signals: 6 inputs + 3 outputs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConveyorControl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;FB&lt;/td&gt;
&lt;td&gt;FB1000&lt;/td&gt;
&lt;td&gt;Control logic, 4 networks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ConveyorControl_DB&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;instance DB&lt;/td&gt;
&lt;td&gt;DB1&lt;/td&gt;
&lt;td&gt;The FB instance: timer and latch state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;OB&lt;/td&gt;
&lt;td&gt;OB1&lt;/td&gt;
&lt;td&gt;Calls the FB every scan cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Miss any one of the four and it isn't an application: without the OB call the logic never runs, without the instance DB the call doesn't even compile.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network 1 — run seal-in.&lt;/strong&gt; &lt;code&gt;Start&lt;/code&gt; in parallel with &lt;code&gt;Run&lt;/code&gt; for self-holding, then in series with four NC contacts (&lt;code&gt;Stop&lt;/code&gt;, &lt;code&gt;EStop&lt;/code&gt;, &lt;code&gt;Overload&lt;/code&gt;, &lt;code&gt;Fault&lt;/code&gt;), driving the &lt;code&gt;Run&lt;/code&gt; coil.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F98m1a06l3yfp0qgalf4q.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F98m1a06l3yfp0qgalf4q.png" alt="Network 1: start and seal-in, with Stop/EStop/Overload/Fault as NC contacts in the rung" width="797" height="154"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network 2 — feedback timer.&lt;/strong&gt; While &lt;code&gt;Run&lt;/code&gt; is true, start a &lt;code&gt;TON&lt;/code&gt; with &lt;code&gt;PT&lt;/code&gt; bound to a constant &lt;code&gt;DELAY&lt;/code&gt; (2 s).&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj7xx3hqkkhpw054dehpj.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fj7xx3hqkkhpw054dehpj.png" alt="Network 2: a TON started by Run, 2 second preset" width="800" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network 3 — feedback fault.&lt;/strong&gt; &lt;code&gt;RunTimer.Q&lt;/code&gt; reached while &lt;code&gt;RunFeedback&lt;/code&gt; is still FALSE sets &lt;code&gt;RunFeedbackFault&lt;/code&gt;.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fptvsjytw865w2lzfpy11.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fptvsjytw865w2lzfpy11.png" alt="Network 3: timer done and feedback still missing sets the fault bit" width="800" height="91"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network 4 — fault latch.&lt;/strong&gt; &lt;code&gt;Overload&lt;/code&gt;, &lt;code&gt;RunFeedbackFault&lt;/code&gt; and &lt;code&gt;Fault&lt;/code&gt; in parallel, self-holding, cleared through the NC contact of &lt;code&gt;Reset&lt;/code&gt;.&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczjmnhis4khlt0tjp3qe.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fczjmnhis4khlt0tjp3qe.png" alt="Network 4: overload, feedback fault and the fault bit latch in parallel, Reset clears" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The timer is a multi-instance inside the FB (&lt;code&gt;RunTimer&lt;/code&gt; declared in Static), so its state lives in the instance DB. That's part of "the application runs", not decoration.&lt;/p&gt;

&lt;p&gt;The first whole-software compile failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compiling: Main (OB1)  Network 1: Missing instance DB.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The OB called the FB, but the FB's instance DB did not exist yet. After creating &lt;code&gt;ConveyorControl_DB&lt;/code&gt;, re-importing and recompiling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Compiling: Main (OB1): Block was successfully compiled.
DB_Conveyor (DB10): Block was successfully compiled.
ConveyorControl (FB1000): Block was successfully compiled.
ConveyorControl_DB (DB1): Block was successfully compiled.
Compiling finished (errors: 0; warnings: 0)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the difference between "a block" and "an application": a legal block doesn't mean the application runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two more failures, from the same class of block
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A counter without a data type.&lt;/strong&gt; TIA's import reported &lt;code&gt;CTU 缺少 value_type&lt;/code&gt; ("missing value_type"). Fix: have the generator declare the counter's data type, regenerate, re-import.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A temp variable with an initial value.&lt;/strong&gt; Import reported &lt;code&gt;Scratch: The Openness import failed: 该值无法更改。&lt;/code&gt; ("this value cannot be changed"). Temp variables can't carry initial values; remove it and regenerate.&lt;/p&gt;

&lt;p&gt;All three failures point at the same division of labour:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent's self-check owns semantics; TIA's import and compile own TIA's rules, and those are stricter.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;"Validation passed" does not mean "TIA accepts it". Paste the &lt;em&gt;raw error text&lt;/em&gt; back to the agent, let it fix against TIA's rules and regenerate. Don't hand-edit the generated file, and don't argue with the validator. Three rounds max; if it still fails, re-read the requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  Boundaries worth stating before anyone copies this
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compile 0/0 does not mean the behaviour is correct.&lt;/strong&gt; Compiling only proves the block fits a CPU. A &lt;code&gt;TON&lt;/code&gt; written where a &lt;code&gt;TP&lt;/code&gt; belonged also compiles 0 errors / 0 warnings and no motor ever starts. Verify behaviour separately (simulation, or a real machine).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Outputs need mapping to your hardware.&lt;/strong&gt; In the reference application many signals live in &lt;code&gt;%M&lt;/code&gt; bits; map them to &lt;code&gt;%Q&lt;/code&gt; before wiring anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safety does not go to an AI.&lt;/strong&gt; E-stop, safety gates, hardware interlocking belong to functional safety. The program only interlocks and annunciates; the final implementation must be reviewed by a person.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;There are environment prerequisites.&lt;/strong&gt; The gateway runs on the same machine as TIA Portal and currently targets V19. Without TIA Portal installed you can read, but not import.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reproducing it
&lt;/h2&gt;

&lt;p&gt;Two MCP servers, configured once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"tialink"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://localhost:8080/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer &amp;lt;your TiaLink API token&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lad"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://tia.hubwiz.com/lad/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer &amp;lt;your LadDoc API key&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;http://localhost:8080/mcp&lt;/code&gt; is the on-machine gateway — &lt;a href="https://tia.hubwiz.com/tia-link?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai-lad-generation" rel="noopener noreferrer"&gt;TiaLink&lt;/a&gt;, use whatever address your install listens on; &lt;code&gt;https://tia.hubwiz.com/lad/mcp&lt;/code&gt; is the hosted ladder service, &lt;a href="https://tia.hubwiz.com/lad?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai-lad-generation" rel="noopener noreferrer"&gt;LadDoc Cloud&lt;/a&gt;. Any Streamable HTTP MCP client works — Claude Code, Cursor, VS Code.&lt;/p&gt;

&lt;p&gt;Billing: handshake requests and &lt;code&gt;lad_version&lt;/code&gt; are free; each validate/generate call consumes one credit. New accounts get a free allowance, then credits are bought in packs — no subscription.&lt;/p&gt;

&lt;p&gt;Keep the keys out of repositories, screenshots and tickets.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to tell whether "AI generates ladder logic" is real
&lt;/h2&gt;

&lt;p&gt;Don't ask whether it can generate ladder. Ask for three numbers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Are all the objects there?&lt;/strong&gt; Global DB / FB / instance DB / OB — all four, or just a block?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does the main program call it every cycle?&lt;/strong&gt; Or was a lone FB handed over with no OB call?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What did the &lt;em&gt;whole software&lt;/em&gt; compile report?&lt;/strong&gt; Not "the block compiled" — the whole software, plus a read-back of the object list and call relationships that matches what the agent claims.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If it can produce those three, "it generates ladder" is a fact. If it can only produce a diagram that looks like ladder, it's a picture.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: LadDoc Cloud and TiaLink are our products. The error messages, the compile output and the four networks above come from a single real run (TIA Portal V19, S7-1500); one run does not generalise to every project.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Earlier in this series: &lt;a href="https://dev.to/tiaforge/siemens-plc-ladder-logic-examples-60-exercises-you-can-import-into-tia-portal-4kjg"&gt;60 ladder exercises you can import into TIA Portal&lt;/a&gt;, &lt;a href="https://dev.to/tiaforge/113-siemens-ladder-instructions-one-page-each-and-the-sections-that-actually-save-you-time-2pog"&gt;113 ladder instructions, one page each&lt;/a&gt;, &lt;a href="https://dev.to/tiaforge/three-kinds-of-lad-to-scl-tooling-and-why-only-one-belongs-in-a-real-project-6l0"&gt;three kinds of LAD→SCL tooling&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The full walkthrough — signal table, workflow, network-by-network commentary, verification checklist — is lesson 3 of &lt;a href="https://tia.hubwiz.com/learn-lad/ai/lad-zero-errors/?utm_source=devto&amp;amp;utm_medium=article&amp;amp;utm_campaign=ai-lad-generation" rel="noopener noreferrer"&gt;PLC AI in Practice&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>plc</category>
      <category>ai</category>
      <category>automation</category>
      <category>tiaportal</category>
    </item>
    <item>
      <title>Your Product Is Copyable. Your Distribution Isn't: A 24-Channel Map for Solo Builders</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Mon, 02 Nov 2026 19:23:21 +0000</pubDate>
      <link>https://dev.to/tiaforge/your-product-is-copyable-your-distribution-isnt-a-24-channel-map-for-solo-builders-34li</link>
      <guid>https://dev.to/tiaforge/your-product-is-copyable-your-distribution-isnt-a-24-channel-map-for-solo-builders-34li</guid>
      <description>&lt;p&gt;&lt;strong&gt;The uncomfortable fact first.&lt;/strong&gt; A competitor with an agent can now rebuild most of your feature set faster than you shipped it. What they can't rebuild is the list of people who chose to follow you, opted into your emails, or bookmarked the page that answers their question.&lt;/p&gt;

&lt;p&gt;That's the whole argument for treating distribution as the product. Below is the map I use: &lt;strong&gt;24 channels, four funnel stages, four ways to acquire any one of them&lt;/strong&gt; — plus the part most of these lists skip, which is that a solo builder can only run about ten of the 24.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attribution.&lt;/strong&gt; This is a rework of Shann³'s X longread &lt;a href="https://x.com/shannholmberg/status/2117202336451895796" rel="noopener noreferrer"&gt;&lt;em&gt;How to Build Distribution When Anyone Can Clone Your Product&lt;/em&gt;&lt;/a&gt; (2026-11-02). The 24-channel matrix and the acquisition due-diligence questions are his; the solo-operator regrouping, the three tests, and the boundaries are mine. Case numbers below are his, quoted from primary posts; I did not re-audit them.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What counts as a channel
&lt;/h2&gt;

&lt;p&gt;A channel is &lt;strong&gt;a repeatable route to people who could buy from you&lt;/strong&gt;: a ranking page, a newsletter, a creator partnership, a listing in an app store.&lt;/p&gt;

&lt;p&gt;"Repeatable" is the load-bearing word. One viral post is not a channel. A page that brings search traffic every month is.&lt;/p&gt;

&lt;p&gt;Three things make a channel hard to copy with code:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;people who chose to follow you, and would notice if you stopped posting&lt;/li&gt;
&lt;li&gt;subscribers who actively opted in (and whose consent you can prove)&lt;/li&gt;
&lt;li&gt;pages that rank &lt;em&gt;and&lt;/em&gt; get cited by AI assistants&lt;/li&gt;
&lt;li&gt;partners who stake their reputation on you&lt;/li&gt;
&lt;li&gt;customers who answer "who else does this?" with your name&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that lives in a repo. A competitor can fork your code; they can't fork three thousand people choosing not to unsubscribe.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The map: 24 channels × 4 funnel stages
&lt;/h2&gt;

&lt;p&gt;The channels are grouped by where the customer is, not by which platform is hot this quarter — the first is structure, the second expires.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Channel&lt;/th&gt;
&lt;th&gt;Funnel stage&lt;/th&gt;
&lt;th&gt;How to get it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Founder &amp;amp; team audiences&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;build, partner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;YouTube channels&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;build, pay, acquire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Newsletters &amp;amp; email lists&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;build, pay, acquire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Creators &amp;amp; industry experts&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;partner, pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Press, podcasts, publications&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;build, partner, pay, acquire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Events, webinars, workshops&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;build, partner, pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Free tools, templates, open source&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;build, acquire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Social, video &amp;amp; offline ads&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Direct outreach &amp;amp; sales&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Education &amp;amp; certification&lt;/td&gt;
&lt;td&gt;Before they know they need you&lt;/td&gt;
&lt;td&gt;build, partner, pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Your website &amp;amp; search visibility&lt;/td&gt;
&lt;td&gt;While they compare options&lt;/td&gt;
&lt;td&gt;build, acquire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Review &amp;amp; comparison sites&lt;/td&gt;
&lt;td&gt;While they compare options&lt;/td&gt;
&lt;td&gt;build, pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Marketplaces &amp;amp; app stores&lt;/td&gt;
&lt;td&gt;While they compare options&lt;/td&gt;
&lt;td&gt;build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Launch &amp;amp; discovery platforms&lt;/td&gt;
&lt;td&gt;While they compare options&lt;/td&gt;
&lt;td&gt;build, pay, acquire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Search &amp;amp; AI assistant ads&lt;/td&gt;
&lt;td&gt;While they compare options&lt;/td&gt;
&lt;td&gt;pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Integrations &amp;amp; technology partners&lt;/td&gt;
&lt;td&gt;Through tools and people they trust&lt;/td&gt;
&lt;td&gt;build, partner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;AI agent surfaces (MCP, connectors)&lt;/td&gt;
&lt;td&gt;Through tools and people they trust&lt;/td&gt;
&lt;td&gt;build, partner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Agencies, consultants, resellers&lt;/td&gt;
&lt;td&gt;Through tools and people they trust&lt;/td&gt;
&lt;td&gt;partner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Affiliates &amp;amp; referral partners&lt;/td&gt;
&lt;td&gt;Through tools and people they trust&lt;/td&gt;
&lt;td&gt;partner&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Communities&lt;/td&gt;
&lt;td&gt;Through tools and people they trust&lt;/td&gt;
&lt;td&gt;build, partner, acquire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Businesses with an existing customer base&lt;/td&gt;
&lt;td&gt;Through tools and people they trust&lt;/td&gt;
&lt;td&gt;acquire&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;Bundles, pre-installs, default placement&lt;/td&gt;
&lt;td&gt;Through tools and people they trust&lt;/td&gt;
&lt;td&gt;partner, pay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;Distribution built into the product&lt;/td&gt;
&lt;td&gt;After they buy&lt;/td&gt;
&lt;td&gt;build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Customer referrals &amp;amp; word of mouth&lt;/td&gt;
&lt;td&gt;After they buy&lt;/td&gt;
&lt;td&gt;build&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use it as a checklist, not a to-do list. The useful question is never "which channel should I do" but "which of the four stages do I have zero presence in".&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Four ways to get any channel
&lt;/h2&gt;

&lt;p&gt;Same channel, four acquisition modes, and each one leaves you with something different.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Time to results&lt;/th&gt;
&lt;th&gt;What you keep&lt;/th&gt;
&lt;th&gt;Failure mode&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Build&lt;/strong&gt; it&lt;/td&gt;
&lt;td&gt;Slowest&lt;/td&gt;
&lt;td&gt;Audience, content, data — all yours&lt;/td&gt;
&lt;td&gt;The person who makes it leaves, the channel dies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Partner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fast-ish&lt;/td&gt;
&lt;td&gt;One-off reach, no asset&lt;/td&gt;
&lt;td&gt;You mistake your partner's audience for yours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pay&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;td&gt;Nothing&lt;/td&gt;
&lt;td&gt;Counting CAC, ignoring retention&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Acquire&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Immediate&lt;/td&gt;
&lt;td&gt;Existing audience and content&lt;/td&gt;
&lt;td&gt;The audience leaves with the previous owner&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two examples from the source that make the modes concrete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Build + audience → acquisition.&lt;/strong&gt; Maor Shlomo built Base44 alone and documented it in public on LinkedIn and X; Wix bought it for \$80M in cash roughly six months after launch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay → placement.&lt;/strong&gt; Airtel handed 12 months of Perplexity Pro to 360 million customers in July 2025; Perplexity also ships pre-installed on Galaxy S26.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The acquisition column is where people lose money, so the source's due-diligence list is worth quoting in full. Before you treat a purchase as a channel, check:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Do the readers, viewers or members actually match your buyer?&lt;/li&gt;
&lt;li&gt;What leaves with the seller? An audience that came for one writer can follow that writer out.&lt;/li&gt;
&lt;li&gt;Where does the traffic come from? A search-dependent site can lose it in one update.&lt;/li&gt;
&lt;li&gt;Subscriber consent and data-transfer rules — an acquired list is not a licence to email.&lt;/li&gt;
&lt;li&gt;Who stays after the sale? Team, contributors, community moderators.&lt;/li&gt;
&lt;li&gt;How do you introduce your product? A publication that starts promoting its new owner loses the trust that made it worth buying.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That last one explains why "buy distribution" so often destroys the thing it bought. Trust doesn't transfer with the asset.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The part the list skips: a solo builder can run ten of these
&lt;/h2&gt;

&lt;p&gt;Most 24-channel posts are written by agencies for companies with a budget and a team. Eight of the 24 basically require money (ads, sponsorships, bundles, acquisitions), and seven more require someone to negotiate with. Recut by "can one person actually keep this running":&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Channels&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Run alone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 founder/team audience · 7 free tools &amp;amp; OSS · 9 outreach · 11 site &amp;amp; search · 13 marketplaces · 14 launch platforms · 17 agent surfaces · 20 communities · 23 in-product distribution · 24 referrals&lt;/td&gt;
&lt;td&gt;Build once, they keep feeding traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Needs a counterpart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4 creators · 5 press &amp;amp; podcasts · 6 events · 10 education · 16 integrations · 18 agencies · 19 affiliates&lt;/td&gt;
&lt;td&gt;The key action is a conversation, not a draft&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Not yet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;8 ads · 12 paid review placements · 15 search ads · 21 acquiring a business · 22 bundles · the acquire paths of 2/3/5&lt;/td&gt;
&lt;td&gt;Either ongoing cash or diligence capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then three tests, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Can you measure it?&lt;/strong&gt; A channel you can't measure is hard to keep funding while you wait.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does it match how your customers buy?&lt;/strong&gt; Self-serve signups need you to exist &lt;em&gt;while they compare&lt;/em&gt;; demo-led sales need outreach, agencies and events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Will you see a first paying customer inside 90 days?&lt;/strong&gt; If not, park it — regardless of how good the channel looks on paper.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Fail any one and the channel waits. That is the whole prioritisation method; there is no fourth test.&lt;/p&gt;

&lt;p&gt;One disagreement with the source, for the record: it recommends starting with "one channel you build, one you partner with or pay for." For a solo builder I'd replace &lt;em&gt;pay&lt;/em&gt; with &lt;em&gt;partner&lt;/em&gt;. Paid reach consumes cash continuously; partnering consumes communication, and you're already paying that cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Two channels, two stages — then measure
&lt;/h2&gt;

&lt;p&gt;Pick two channels at &lt;strong&gt;different funnel stages&lt;/strong&gt;. Two channels in the same stage means you cover the same moment twice and stay invisible everywhere else. Two different stages means one catches people who don't know they have the problem, and one catches people choosing between options.&lt;/p&gt;

&lt;p&gt;Track four numbers per channel, and nothing else:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how many people it reached&lt;/li&gt;
&lt;li&gt;how many of those became customers&lt;/li&gt;
&lt;li&gt;what it cost, &lt;strong&gt;including your own hours&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;whether those customers stay, and what they spend over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two rules that save months:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep engagement separate from sales.&lt;/strong&gt; A post can pull huge engagement and zero customers; mixing them produces a channel that looks like it works and doesn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask new customers how they found you.&lt;/strong&gt; Podcasts, word of mouth and creator posts mostly never show up in click tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For affiliates and referrals, check that the referred customers are &lt;em&gt;good&lt;/em&gt; customers before you raise the payout.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Where the argument breaks
&lt;/h2&gt;

&lt;p&gt;Four limits, because a 24-cell table invites false confidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Specific numbers here are second-hand.&lt;/strong&gt; \$80M, 360M, 77.5% of Klaviyo's ARR, Dropbox's +60% — I'm repeating figures the source attributes to primary posts. Treat them as claims, not audit results.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Channels are not uncopyable, only harder to copy.&lt;/strong&gt; Platform rules change overnight: a search update, a store delisting, a feed algorithm. That's exactly why the three tests reward measurability and a 90-day horizon over channel count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale isn't transferable.&lt;/strong&gt; Base44, Supabase and Lovable all had someone pushing full-time. Doing the same actions alone gets you the actions, not the numbers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The framework is not new.&lt;/strong&gt; Grouping channels by funnel stage is a re-skin of the channel list in &lt;em&gt;Traction&lt;/em&gt; (Weinberg &amp;amp; Mares). What's new here is the fourth dimension — acquisition mode — and the admission that most of it isn't for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The one action worth taking today
&lt;/h2&gt;

&lt;p&gt;List every channel you currently have traffic from, tag each with one of four stages, and find the stage with nothing in it. That empty stage is where a competitor is winning customers you never get to meet. Then pick one channel from the "run alone" group, give it a 90-day test, and stop adding channels until one produces a paying customer.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally written in Chinese at &lt;a href="https://www.hubwiz.com/blog/cloneable-product-distribution-24-channels/" rel="noopener noreferrer"&gt;hubwiz.com&lt;/a&gt; (in Chinese). Based on &lt;a href="https://x.com/shannholmberg/status/2117202336451895796" rel="noopener noreferrer"&gt;Shann³'s article&lt;/a&gt;. Cover: aerial view of the Bremerhaven container terminal, Carsten Steger, CC BY-SA 4.0, via Wikimedia Commons.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>marketing</category>
      <category>saas</category>
      <category>startup</category>
      <category>business</category>
    </item>
    <item>
      <title>Claude Code Meets TIA Portal: One Prompt, a Compiled and Simulated PLC Program</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Tue, 27 Oct 2026 08:19:21 +0000</pubDate>
      <link>https://dev.to/tiaforge/claude-code-meets-tia-portal-one-prompt-a-compiled-and-simulated-plc-program-1h6c</link>
      <guid>https://dev.to/tiaforge/claude-code-meets-tia-portal-one-prompt-a-compiled-and-simulated-plc-program-1h6c</guid>
      <description>&lt;p&gt;Most "AI writes PLC code" posts stop at the SCL listing. The listing is the easy part. What matters is whether the thing lands in your project, compiles, downloads, and behaves — and whether you can tell when it doesn't.&lt;/p&gt;

&lt;p&gt;We ran that whole path on a real TIA Portal V19 machine: from an empty device, one requirement, one prompt, to a program that compiled with zero errors, downloaded to S7-PLCSIM Advanced, and passed three behavioural scenarios. Everything below is measured, including the parts that failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How this was measured.&lt;/strong&gt; 2026-10-27, Windows, TIA Portal V19 (Openness 19.0), S7-PLCSIM Advanced 6.0, gateway 1.3.2.0 driving the open project. The agent talked to TIA Portal over MCP, the same way any MCP client would.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the gateway gives an agent
&lt;/h2&gt;

&lt;p&gt;Five groups of capability, all against the project that is currently open in TIA Portal:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Read&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;project tree, block contents, tag tables, UDTs, cross-references, HMI screens, hardware config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Write&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;generate or rewrite SCL, create blocks and tables, write back, compile one block or the whole PLC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Configure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;add a CPU, plug modules, create subnets, wire IO systems, set PROFINET device names&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Verify&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;download to S7-PLCSIM Advanced, read and write variables, run scenarios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Archive&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;export SimaticML for offline diffing and version control&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This build exposes 34 tools across those five groups, plus one more for LAD work. Install it in whichever client you use — the tool surface is identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Hooking it up
&lt;/h2&gt;

&lt;p&gt;With Claude Code, it is two commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp add &lt;span class="nt"&gt;--transport&lt;/span&gt; http tialink http://192.168.1.110:8080/mcp
claude mcp list          &lt;span class="c"&gt;# confirm it connected, and how many tools it sees&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other clients differ only in where the config lives: Cursor (&lt;code&gt;.cursor/mcp.json&lt;/code&gt;), Claude Desktop (&lt;code&gt;claude_desktop_config.json&lt;/code&gt;), or a UI connector in WorkBuddy/CodeBuddy (remember to click &lt;strong&gt;Enable&lt;/strong&gt; — an added-but-disabled connector shows no tools and looks broken).&lt;/p&gt;

&lt;p&gt;One practical note: raise the client timeout. Adding a CPU took 34 s on an older device and 2.2 s on a current one; a download takes ~10 s. A 60 s default will fire on work that is merely slow, and you will misread it as a failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  One requirement, one prompt
&lt;/h2&gt;

&lt;p&gt;The requirement, as an engineer would write it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One start button starts motor 1; motor 1 running for 3 s starts motor 2 automatically.&lt;/li&gt;
&lt;li&gt;The stop button (normally closed) or a fault on either motor stops both immediately.&lt;/li&gt;
&lt;li&gt;Each motor has a run output; timing uses IEC timers.&lt;/li&gt;
&lt;li&gt;The logic lives in a reusable FB; OB1 calls it.&lt;/li&gt;
&lt;li&gt;I/O on M area: &lt;code&gt;M0.0&lt;/code&gt; start, &lt;code&gt;M0.1&lt;/code&gt; stop (normally closed), &lt;code&gt;M0.2&lt;/code&gt; fault, &lt;code&gt;M0.3&lt;/code&gt; motor 1, &lt;code&gt;M0.4&lt;/code&gt; motor 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prompt we actually sent (verbatim):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;【需求】两台电机的顺序启动控制
- 一台启动按钮启动 1 号电机；1 号电机运行 3 秒后自动启动 2 号电机
- 停止按钮（常闭）或任一电机故障时，1 号、2 号电机立即全部停止
- 两台电机各有运行输出，定时用 IEC 定时器
- 逻辑写成一个可复用的 FB，OB1 里调用它
- 输入输出用 M 区：M0.0 = 启动按钮，M0.1 = 停止（常闭，1 表示允许），M0.2 = 故障，
  M0.3 = 1 号电机，M0.4 = 2 号电机

【要求】
1. 自己决定步骤和工具，把它们下发到 PLC_3 并编译通过（0 错误）
2. 编译通过后下载到 S7-PLCSIM Advanced，创建全新的实例名，运行后用场景验证
3. 不要保存工程
4. 最后贴出完整的 SCL 源码和实测数值
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No SCL template, no step list — the sequence was the agent's choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it produced
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FUNCTION_BLOCK "FB_MotorSeq"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
   VAR_INPUT
      Start : Bool;             // start button (M0.0)
      StopPermit : Bool;        // stop, normally closed: 1 = allowed (M0.1)
      Fault : Bool;             // fault (M0.2)
      RunDelay : Time := T#3S;  // motor 2 delay
   END_VAR
   VAR_OUTPUT
      Motor1 : Bool;            // motor 1 running (M0.3)
      Motor2 : Bool;            // motor 2 running (M0.4)
   END_VAR
   VAR
      statRun : Bool;           // run latch
      statPermit : Bool;        // run permitted
      rtStart : R_TRIG;         // rising edge of the start button
      tonRun2 : TON_TIME;       // IEC on-delay timer
   END_VAR
BEGIN
    // Run is permitted when the normally-closed stop is closed and there is no fault
    #statPermit := #StopPermit AND NOT #Fault;

    // Latch on the start edge; stop or fault clears it immediately
    #rtStart(CLK := #Start);
    #statRun := (#rtStart.Q OR #statRun) AND #statPermit;

    // Motor 1 runs at once; motor 2 starts RunDelay after motor 1
    #Motor1 := #statRun;
    #tonRun2(IN := #Motor1,
             PT := #RunDelay);
    #Motor2 := #statRun AND #tonRun2.Q;
END_FUNCTION_BLOCK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And OB1, wiring it to the M area:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ORGANIZATION_BLOCK "Main"
{ S7_Optimized_Access := 'TRUE' }
VERSION : 0.1
BEGIN
    "DB_MotorSeq"(Start := %M0.0,
                  StopPermit := %M0.1,
                  Fault := %M0.2,
                  RunDelay := T#3s,
                  Motor1 =&amp;gt; %M0.3,
                  Motor2 =&amp;gt; %M0.4);
END_ORGANIZATION_BLOCK
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Compile: &lt;code&gt;state=Success, errorCount=0, warningCount=0&lt;/code&gt;, both &lt;code&gt;Main (OB1)&lt;/code&gt; and &lt;code&gt;FB_MotorSeq (FB1)&lt;/code&gt; cleaned.&lt;/p&gt;

&lt;p&gt;Two details worth reading in that code: the start is edge-triggered (&lt;code&gt;R_TRIG&lt;/code&gt;) — otherwise holding the button keeps it latched — and stop/fault runs through &lt;code&gt;#statPermit&lt;/code&gt;, a path that never touches the timer. That second one is exactly what the third scenario below checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  How long each step took
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;health probe (attached, project, device count, prerequisites)&lt;/td&gt;
&lt;td&gt;&amp;lt; 1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;look up the CPU model in the hardware catalogue&lt;/td&gt;
&lt;td&gt;~1 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;create the CPU (CPU 1516-3 PN/DP, FW V2.9)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2.2 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;import the FB from SCL source&lt;/td&gt;
&lt;td&gt;11.8 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;import the instance DB and OB1&lt;/td&gt;
&lt;td&gt;0.6 s each&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;compile&lt;/td&gt;
&lt;td&gt;1.4 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;create a virtual PLC and power it on&lt;/td&gt;
&lt;td&gt;~6 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;download to S7-PLCSIM Advanced&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9.6 s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;run, write variables, read variables&lt;/td&gt;
&lt;td&gt;&amp;lt; 100 ms per call&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One ordering constraint you cannot skip: &lt;strong&gt;import does not compile&lt;/strong&gt;. Right after import the block is not marked consistent, and even exporting it is refused (&lt;code&gt;Block is inconsistent (not compiled)&lt;/code&gt;). Import → compile → read back, in that order.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three scenarios
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn8xm518wodfilfz7gk1u.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fn8xm518wodfilfz7gk1u.png" alt="Timing: start pulse, motor 1 runs immediately, motor 2 starts 3.12 s later, then stop or fault drops both within the same scan" width="800" height="470"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A green compile only says the code is acceptable to the PLC. These three runs say what it does.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;Observed&lt;/th&gt;
&lt;th&gt;Read as&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Start&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;M0.1=1&lt;/code&gt;, pulse &lt;code&gt;M0.0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;motor 1 goes to 1 at once; &lt;strong&gt;motor 2 at +3.12 s&lt;/strong&gt;; both hold after the button is released&lt;/td&gt;
&lt;td&gt;delay is fed by "motor 1 running", not by the button&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stop&lt;/td&gt;
&lt;td&gt;both running, &lt;code&gt;M0.1 → 0&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;both drop &lt;strong&gt;within one sample&lt;/strong&gt;; restoring &lt;code&gt;M0.1=1&lt;/code&gt; does &lt;strong&gt;not&lt;/strong&gt; restart them&lt;/td&gt;
&lt;td&gt;the stop path is not gated by the timer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fault&lt;/td&gt;
&lt;td&gt;both running, &lt;code&gt;M0.2 → 1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;both drop &lt;strong&gt;within 13 ms (one scan)&lt;/strong&gt;; clearing the fault does not restart&lt;/td&gt;
&lt;td&gt;fault is an independent cut-out, and it does not self-heal&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The 3.12 s figure is 120 ms off the 3 s setting; that is one polling quantum (150 ms) plus scan jitter, not a logic error. The stop and fault cases are the ones worth insisting on: they are invisible at compile time, hard to reproduce on a machine, and they are what decides whether an operator has to press reset afterwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond "Success"
&lt;/h2&gt;

&lt;p&gt;A download that returns success still deserves a look at what actually landed. We exported the three blocks as SimaticML and checked three things: OB1 really calls the instance DB with those M addresses, the FB uses an IEC &lt;code&gt;TON&lt;/code&gt; and no stray instance, and the block metadata says &lt;code&gt;consistent&lt;/code&gt;. Sizes for the record: &lt;code&gt;Main&lt;/code&gt; 7,766 B, &lt;code&gt;FB_MotorCtrl&lt;/code&gt; 15,978 B (the earlier single-motor variant), &lt;code&gt;DB_MotorCtrl&lt;/code&gt; 6,903 B.&lt;/p&gt;

&lt;p&gt;The gateway's own notes make one behaviour explicit: &lt;strong&gt;nothing is persisted until you save&lt;/strong&gt;. Imports, hardware writes, deletes and compiles all live in memory. That is what makes "try it, look at the result, throw it away" safe — and it is also the trap: if you never save and assume you are done, closing TIA Portal loses the lot. Our habit: verify first, save once, at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  A checklist before you let it touch a real project
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read-only first.&lt;/strong&gt; Have it read the tree, blocks and cross-references; confirm it understood your project before it writes anything.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Preview writes for a week.&lt;/strong&gt; Compare its previewed diff against what a person would change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read back after every write.&lt;/strong&gt; Export or compare hashes; if it does not match, treat it as not written.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One project, one session, one writer at a time.&lt;/strong&gt; The Openness side is serial; parallel agents produce half-finished work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compile as a gate.&lt;/strong&gt; Passing does not mean correct, but failing must block the commit. This is the only gate you can fully automate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Archive before changing.&lt;/strong&gt; VCI or Git, exported as diffable text.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep an audit trail.&lt;/strong&gt; Who, when, in which mode, which object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not hand download or go-online to an unattended agent.&lt;/strong&gt; Man in the loop, one step at a time, with a rollback plan.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And the rule that outranks all of them: &lt;strong&gt;AI-generated SCL gets a human review&lt;/strong&gt;, especially safety logic (emergency stop, interlocks, F-programs). The tool guarantees it was written, not that it is right.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versions and boundaries
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;What we ran&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TIA Portal&lt;/td&gt;
&lt;td&gt;V19 / V20 supported; this run was V19 with Openness 19.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simulator&lt;/td&gt;
&lt;td&gt;S7-PLCSIM Advanced 6.0, Softbus&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CPU firmware&lt;/td&gt;
&lt;td&gt;matters more than expected — a V1.1 device was refused by the simulator, V2.9 worked&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment&lt;/td&gt;
&lt;td&gt;the gateway runs on the same Windows machine as TIA Portal; the agent can be anywhere that can reach its HTTP port&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool count&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The deployment line surprises people: the &lt;strong&gt;gateway&lt;/strong&gt; has to be on the TIA machine, the &lt;strong&gt;agent&lt;/strong&gt; does not. Every call in this article was made from a different computer over HTTP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it yourself
&lt;/h2&gt;

&lt;p&gt;You need two things: a Windows machine with TIA Portal V19 or V20, and the gateway installed on that same machine. It publishes its endpoint on &lt;code&gt;http://localhost:8080/&lt;/code&gt;, and the client side is the one line of config from earlier.&lt;/p&gt;

&lt;p&gt;Setup and version support: &lt;a href="https://tia.hubwiz.com/tia-link?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=claude-code-tia-portal" rel="noopener noreferrer"&gt;tia.hubwiz.com/tia-link&lt;/a&gt;. If you want the agent to understand the project before it starts writing, &lt;a href="https://tia.hubwiz.com/tia-context?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=claude-code-tia-portal" rel="noopener noreferrer"&gt;TIA-Context&lt;/a&gt; feeds it blocks, tag tables and call relationships.&lt;/p&gt;

&lt;p&gt;Once the loop works, the mistake to avoid is asking for too much at once. One block, one change with a test case in front of it, beats "refactor this project" every time.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Further reading (in Chinese): &lt;a href="https://www.hubwiz.com/blog/tia-portal-openness-mcp-write-guards/?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;AI changes a TIA Portal program: write guards and boundaries&lt;/a&gt; · &lt;a href="https://www.hubwiz.com/blog/opcua-mcp-hands-on/?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;OPC UA MCP hands-on&lt;/a&gt; · &lt;a href="https://www.hubwiz.com/blog/ai-industrial-stack/?utm_source=devto&amp;amp;utm_medium=syndication" rel="noopener noreferrer"&gt;AI × industrial / PLC guide&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Timings and error strings come from the 2026-10-27 run. AI-generated PLC code needs human review before it goes anywhere near production.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>automation</category>
    </item>
    <item>
      <title>Three Kinds of LAD-to-SCL Tooling, and Why Only One Belongs in a Real Project</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Tue, 27 Oct 2026 03:37:16 +0000</pubDate>
      <link>https://dev.to/tiaforge/three-kinds-of-lad-to-scl-tooling-and-why-only-one-belongs-in-a-real-project-6l0</link>
      <guid>https://dev.to/tiaforge/three-kinds-of-lad-to-scl-tooling-and-why-only-one-belongs-in-a-real-project-6l0</guid>
      <description>&lt;p&gt;&lt;strong&gt;"Which software converts ladder to SCL?" is the wrong question&lt;/strong&gt;, and not because the tools are bad. It's because three completely different classes of tool get filed under one name, and their failure modes have almost nothing in common. Comparing them on "accuracy" is comparing nothing.&lt;/p&gt;

&lt;p&gt;Here they are.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The three classes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;What it actually reads&lt;/th&gt;
&lt;th&gt;Ceiling&lt;/th&gt;
&lt;th&gt;Where it fails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Built into TIA Portal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LAD ↔ FBD, LAD → STL&lt;/td&gt;
&lt;td&gt;The blocks in your project&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Cannot produce SCL&lt;/strong&gt; — only FBD/STL&lt;/td&gt;
&lt;td&gt;Not SCL. STL is assembler-ish: it runs, but nobody can review it, and diffs show nothing semantic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;In-project add-in&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reads LAD blocks out of the project via TIA Openness / SimaticML, emits SCL source&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;The project itself&lt;/strong&gt; — interfaces, tag tables, instance DBs, network order, every part and every wire&lt;/td&gt;
&lt;td&gt;Whole-project coverage; anything its rules cannot handle must be surfaced as "review required"&lt;/td&gt;
&lt;td&gt;Semantic structures its rule set does not cover (edge memory bits, jumps, MCR, multiple coils) — the answer must be "flag it", never "guess"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Screenshot / paste-a-text converters&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Paste an image or a text blob, get code back&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Pixels or characters&lt;/strong&gt; — no tags, no instances, no network order&lt;/td&gt;
&lt;td&gt;One small network; demos and learning&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;The information was lost on the input side.&lt;/strong&gt; This is where "error rate is huge" comes from&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So the widely repeated claim that &lt;em&gt;third-party converters are inaccurate&lt;/em&gt; is true of class 3 and false of class 2. Class 2 and class 1 read the same data — the only difference is who generates the SCL.&lt;/p&gt;

&lt;p&gt;One more thing to retire: &lt;strong&gt;there is no "convert everything in one click"&lt;/strong&gt;. What decides the outcome is not the software; it's the ratio, in &lt;em&gt;your&lt;/em&gt; project, between structures that map mechanically and structures that only look like they do.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Fourteen structures, judged one by one
&lt;/h2&gt;

&lt;p&gt;The table below is derived from the semantics of the two languages, not from a sample — which is why every row also says how you verify it yourself instead of taking my word for it.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Structure&lt;/th&gt;
&lt;th&gt;What conversion can do&lt;/th&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;What you must do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Series/parallel contacts&lt;/td&gt;
&lt;td&gt;Boolean expression&lt;/td&gt;
&lt;td&gt;✅ Safe&lt;/td&gt;
&lt;td&gt;Check parentheses and operator precedence; don't rely on default associativity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Plain coil&lt;/td&gt;
&lt;td&gt;&lt;code&gt;x := expr;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Safe&lt;/td&gt;
&lt;td&gt;Confirm it isn't a multiple coil (row 10)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Set &lt;code&gt;(S)&lt;/code&gt; / Reset &lt;code&gt;(R)&lt;/code&gt; coil&lt;/td&gt;
&lt;td&gt;&lt;code&gt;IF cond THEN x := TRUE/FALSE; END_IF;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅ Converts — &lt;strong&gt;inspect the output&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;When the condition is false the value must &lt;strong&gt;hold&lt;/strong&gt;; an unconditional assignment has already broken the semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;RS&lt;/code&gt; / &lt;code&gt;SR&lt;/code&gt; boxes&lt;/td&gt;
&lt;td&gt;Calls the corresponding FB&lt;/td&gt;
&lt;td&gt;⚠️ Converts, &lt;strong&gt;trivially easy to pick the wrong one&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;They look identical in LAD; the difference only shows when set and reset are true in the same scan. Picking wrong does not error&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Positive / negative edge contacts&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;R_TRIG&lt;/code&gt; / &lt;code&gt;F_TRIG&lt;/code&gt; instance, or an explicit last-scan compare&lt;/td&gt;
&lt;td&gt;⚠️ &lt;strong&gt;The most commonly broken row&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;The edge bit must live in &lt;strong&gt;static&lt;/strong&gt; memory. Put it in TEMP and it is rebuilt every scan — the edge disappears, silently&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;TON&lt;/code&gt; / &lt;code&gt;TOF&lt;/code&gt; / &lt;code&gt;TP&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Calls the IEC timer FB&lt;/td&gt;
&lt;td&gt;⚠️ Converts; instance matters&lt;/td&gt;
&lt;td&gt;Instance must sit in STATIC or a multi-instance DB; check PT type/units, and whether "wrapped in an IF" matches the original EN behaviour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;CTU&lt;/code&gt; / &lt;code&gt;CTD&lt;/code&gt; / &lt;code&gt;CTUD&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Calls the counter FB&lt;/td&gt;
&lt;td&gt;⚠️ Same as above&lt;/td&gt;
&lt;td&gt;Instance location, preset, and the priority of reset vs. load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Multi-instance FB call with parameters&lt;/td&gt;
&lt;td&gt;&lt;code&gt;#myFB(IN1 := ..., IN2 := ...);&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;⚠️ Converts, &lt;strong&gt;parameters get dropped&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;A missing pin does &lt;strong&gt;not&lt;/strong&gt; error — it just keeps its previous value, so the bug surfaces only in one specific operating case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;JMP&lt;/code&gt; / &lt;code&gt;LABEL&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;Cannot convert&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;SCL has no &lt;code&gt;GOTO&lt;/code&gt;. Rewrite the control flow as a &lt;code&gt;CASE&lt;/code&gt; state machine or structured branching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Multiple coils (same variable written in several networks)&lt;/td&gt;
&lt;td&gt;Sequential assignments&lt;/td&gt;
&lt;td&gt;⚠️ Converts — &lt;strong&gt;result depends on order&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Make the write order explicit and check whether anything reads between the writes; which write gets read changes everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;MCR&lt;/code&gt; (master control relay)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;❌ &lt;strong&gt;Cannot convert&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Expand into an explicit condition chain: write out what the outputs do while the zone is disabled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EN&lt;/code&gt; / &lt;code&gt;ENO&lt;/code&gt; chain&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;EN&lt;/code&gt; → &lt;code&gt;IF&lt;/code&gt; wrapper&lt;/td&gt;
&lt;td&gt;⚠️ Half converts&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ENO&lt;/code&gt; routinely gets dropped → upstream fails, downstream keeps running. Check the return value or error bit explicitly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Implicit type conversion&lt;/td&gt;
&lt;td&gt;Explicit conversion functions&lt;/td&gt;
&lt;td&gt;⚠️ Converts — &lt;strong&gt;the boundaries move&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Re-check divide-by-zero, integer overflow, REAL precision, TIME and counter type limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Initialisation / first scan (OB100)&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;❌ Must be manual&lt;/td&gt;
&lt;td&gt;Start-up block, static initial values, first-scan behaviour. These only fail after a power cycle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pattern is easy to state: &lt;strong&gt;connection structure converts; state and timing do not.&lt;/strong&gt; Rows 5, 6, 8 and 12 are all "the picture carried this for you, and the text won't".&lt;/p&gt;

&lt;p&gt;And none of the fourteen will produce a compile error. Which is the real reason "it compiles" proves nothing about a LAD→SCL migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The brand question, briefly
&lt;/h2&gt;

&lt;p&gt;Search suggestions often put "Siemens" and "Mitsubishi" in the same sentence. There is no universal converter, and there cannot be: the pictures look alike, but three things differ underneath — device addressing (&lt;code&gt;I/Q/M/DB&lt;/code&gt; vs &lt;code&gt;X/Y/M/D&lt;/code&gt;), instruction set and function-block semantics, and how edges and timers are implemented. A TIA Portal add-in reads TIA Portal projects. Across vendors it's a re-implementation with a fresh I/O mapping, not a conversion.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. How to verify, without trusting any tool
&lt;/h2&gt;

&lt;p&gt;Three steps, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Compare interfaces and tags.&lt;/strong&gt; IN / OUT / IN_OUT / STATIC / TEMP, one by one; symbol names, types, retentivity. This alone kills most "looks like it runs" illusions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Run the same inputs twice.&lt;/strong&gt; Feed the original LAD block and the new SCL block an identical input sequence, then compare output timing and final values. Minimum coverage: first scan after power-up, start alone, stop alone, both at once, interlock opened mid-run, timer at the threshold, counter at the preset, manual/auto switchover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Localise the divergence to a network.&lt;/strong&gt; The first step where they differ maps back to one row of the table above — then it's either "converted but unchecked" or "should have been rewritten by hand".&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 2 needs two environments that can run. With TIA Portal and PLCSIM you have them. Without TIA Portal you can still run the SCL side on a cloud simulator that executes scan cycles and returns final values plus PASS/FAIL — I used &lt;a href="https://tia.hubwiz.com/sim/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-to-scl-tooling" rel="noopener noreferrer"&gt;PLC Cloud Sim&lt;/a&gt; for regression runs, and its MCP endpoint lets an agent replay the same case set. To be precise about the boundary: that verifies &lt;strong&gt;logical equivalence&lt;/strong&gt; only. It does not replace PLCSIM timing detail, hardware diagnostics, or commissioning on the machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. So which one
&lt;/h2&gt;

&lt;p&gt;For a real migration, class 2 for the bulk, your own judgement for rows 3–14, and class 3 nowhere near a project. Concretely, the add-in I use is &lt;a href="https://tia.hubwiz.com/lad2scl/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-to-scl-tooling" rel="noopener noreferrer"&gt;LAD2SCL&lt;/a&gt; — a TIA Portal add-in by Hubwiz that reads LAD blocks from the open project and emits SCL. In the interest of not overselling: &lt;strong&gt;only the V19 installer has run on a real machine so far&lt;/strong&gt;; V17/V18/V20/V21 are advertised on the page but have not been verified version by version, and the download buttons for them currently say "not released yet".&lt;/p&gt;

&lt;p&gt;The longer Chinese version, with the migration workflow and the pre-release checklist, is &lt;a href="https://www.hubwiz.com/blog/lad-to-scl-migration-guide-tia-portal/" rel="noopener noreferrer"&gt;here&lt;/a&gt; (in Chinese).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The 14-row table is a semantic analysis, not a benchmark. If you find a row that's wrong for your project, that's more useful than a star — tell me which one and what it did.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>plc</category>
      <category>automation</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Empty Folder to MP4 in 162 Seconds: What a Coding Agent Can't Do With Video</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Mon, 26 Oct 2026 04:29:50 +0000</pubDate>
      <link>https://dev.to/tiaforge/empty-folder-to-mp4-in-162-seconds-what-a-coding-agent-cant-do-with-video-2bo7</link>
      <guid>https://dev.to/tiaforge/empty-folder-to-mp4-in-162-seconds-what-a-coding-agent-cant-do-with-video-2bo7</guid>
      <description>&lt;p&gt;&lt;strong&gt;The one-paragraph version.&lt;/strong&gt; There are two unrelated technologies behind the phrase "AI video". One paints pixels from a prompt (Veo, Gemini Omni, Sora). The other renders a &lt;strong&gt;program&lt;/strong&gt; into frames — a video is a React component tree or an HTML file, and the output is deterministic. Only the second one is something a coding agent can own end to end: it writes files, reads type errors, renders, looks at frames. We ran the whole loop on a clean machine and timed it. Total to first MP4: &lt;strong&gt;about three minutes&lt;/strong&gt;. Then we hit the wall that every article about this topic skips — the agent has no way to know whether what it rendered is any good.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure.&lt;/strong&gt; All commands and timings below were run by us on 2026-10-26 (Windows 11, 8-core CPU, Node 24.21.0, npm 11.19.0, Remotion 4.0.538). Everything else — license terms, skill counts, design rationale — is quoted from the vendor's own docs and is linked in Sources. We did not benchmark cloud rendering, and we did not run a paid generative model.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Two things are called "AI video"
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Prompt → pixels&lt;/th&gt;
&lt;th&gt;Code → frames&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Examples&lt;/td&gt;
&lt;td&gt;Veo 3.1, Gemini Omni Flash, Sora&lt;/td&gt;
&lt;td&gt;Remotion, HyperFrames&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unit of authoring&lt;/td&gt;
&lt;td&gt;A sentence&lt;/td&gt;
&lt;td&gt;A file in git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output for the same input&lt;/td&gt;
&lt;td&gt;Different every time (sampling)&lt;/td&gt;
&lt;td&gt;Byte-identical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editing a detail&lt;/td&gt;
&lt;td&gt;Re-prompt, hope&lt;/td&gt;
&lt;td&gt;Change one number, re-render&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What the model must know&lt;/td&gt;
&lt;td&gt;Cinematography, physics, faces&lt;/td&gt;
&lt;td&gt;The API you gave it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where an agent fits&lt;/td&gt;
&lt;td&gt;Calls an API&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Owns the whole repo&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The distinction matters because the two have wildly different failure modes. A diffusion model fails by hallucinating a sixth finger; a render pipeline fails by throwing &lt;code&gt;TypeError: undefined is not a function&lt;/code&gt;. Coding agents are good at the second kind of failure and useless at the first.&lt;/p&gt;

&lt;p&gt;That is the whole reason this niche got hot in 2026: Remotion shipped &lt;strong&gt;Agent Skills&lt;/strong&gt; in January, HeyGen open-sourced &lt;strong&gt;HyperFrames&lt;/strong&gt; in April, and both are pure text — which is the only substrate a coding agent actually understands.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What "video as code" means mechanically
&lt;/h2&gt;

&lt;p&gt;The mental model is one line: &lt;strong&gt;a frame is a pure function of time.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCurrentFrame&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;          &lt;span class="c1"&gt;// 0, 1, 2, … 149&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;spring&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;damping&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;…&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is no timeline, no keyframe UI, no video editor. You write a component that, given a frame number, returns what that frame looks like. The renderer then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;bundles the project (Remotion) or loads &lt;code&gt;index.html&lt;/code&gt; as-is (HyperFrames),&lt;/li&gt;
&lt;li&gt;opens headless Chrome and &lt;strong&gt;seeks&lt;/strong&gt; each timestamp,&lt;/li&gt;
&lt;li&gt;screenshots the frame,&lt;/li&gt;
&lt;li&gt;pipes the frames into FFmpeg.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both frameworks do this. HyperFrames' README states the property that makes it usable in CI: &lt;em&gt;"The renderer seeks each frame in headless Chrome and encodes the result with FFmpeg, so the same input produces the same video."&lt;/em&gt; Remotion's pitch is the same idea with a React component tree as the authoring surface.&lt;/p&gt;

&lt;p&gt;Why agents like it: the artifact is TypeScript or HTML, the feedback loop is a compiler, and the diff is reviewable. &lt;code&gt;git diff&lt;/code&gt; on a video is a strange sentence that is now literally true.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The demo, with real numbers
&lt;/h2&gt;

&lt;p&gt;Four commands, no editor, no API key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-video@latest &lt;span class="nt"&gt;--yes&lt;/span&gt; &lt;span class="nt"&gt;--blank&lt;/span&gt; demo   &lt;span class="c"&gt;# scaffold&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;demo
npx skills add remotion-dev/skills &lt;span class="nt"&gt;-a&lt;/span&gt; claude-code &lt;span class="nt"&gt;-y&lt;/span&gt;   &lt;span class="c"&gt;# 12 skills → .claude/skills&lt;/span&gt;
npx remotion render MyComp out/video.mp4      &lt;span class="c"&gt;# render&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Measured, on the machine above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Wall time&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create-video --yes --blank&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1 m 56 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;almost entirely &lt;code&gt;npm install&lt;/code&gt;; dominates the clock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;skills add …&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6.0 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;12 skill folders written&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;first &lt;code&gt;remotion render&lt;/code&gt; (5 s @ 1080p)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;39.9 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;includes downloading headless Chrome&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;same render, warm&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;11.7 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;150 frames, 1080p30, &lt;code&gt;--concurrency&lt;/code&gt; default = cores/2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60 s @ 1080p&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;68.7 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1800 frames; ≈ 26 frames/s of output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;60 s @ 1080p, &lt;code&gt;--concurrency=2&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;84.4 s&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;forcing 2 workers is 23% slower on 8 cores&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things fall out of that table. First, &lt;strong&gt;render time is linear in duration&lt;/strong&gt;: 150 frames took 11.7 s, 1800 frames took 68.7 s — the same ~26 frames per second of output either way (we only measured one composition, so treat "per frame" as roughly constant, not proven constant). Second, the "three minutes to your first video" claim is real, but it is &lt;em&gt;scaffold&lt;/em&gt; time, not &lt;em&gt;authoring&lt;/em&gt; time: 116 s of install + 6 s of skills + 40 s of cold render ≈ 162 s, and &lt;strong&gt;the npm install is two-thirds of it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A note on the install command, because half the articles online get it wrong. Remotion's own docs now say &lt;code&gt;npx remotion skills add&lt;/code&gt;. The generic installer also works: &lt;code&gt;npx skills add remotion-dev/skills -a claude-code -y&lt;/code&gt;. &lt;strong&gt;They write to different directories&lt;/strong&gt; — the first put 12 skills in &lt;code&gt;.agents/skills/&lt;/code&gt; and created a &lt;code&gt;skills-lock.json&lt;/code&gt;; the second put 12 skills in &lt;code&gt;.claude/skills/&lt;/code&gt;. If your agent "can't see the skills", check which directory got them before you debug anything else.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. What the skills actually cost you in context
&lt;/h2&gt;

&lt;p&gt;People assume installing agent skills means stuffing a manual into the prompt. It doesn't, and the numbers are worth knowing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Skills installed&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontmatter (&lt;code&gt;name&lt;/code&gt; + &lt;code&gt;description&lt;/code&gt;) of all 12&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;1,031 chars ≈ 290 tokens&lt;/strong&gt; — this is the always-visible part&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full &lt;code&gt;SKILL.md&lt;/code&gt; bodies, all 12&lt;/td&gt;
&lt;td&gt;43 KB ≈ 12k tokens, &lt;strong&gt;read on demand&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Support files (docs, example &lt;code&gt;.tsx&lt;/code&gt;, SVGs, map data)&lt;/td&gt;
&lt;td&gt;285 files / 2.3 MB — opened only if the agent asks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The biggest single skill is &lt;code&gt;remotion-best-practices&lt;/code&gt;, whose &lt;code&gt;SKILL.md&lt;/code&gt; is a 4.2 KB router: it tells the agent which of the other eleven references to load for the task at hand. That's the whole design — pay ~300 tokens up front, buy 12k tokens of API knowledge. It also means the failure mode is silent: if the router doesn't fire, the agent writes Remotion from memory and you get plausible-looking code that renders a black screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The cost model nobody writes down
&lt;/h2&gt;

&lt;p&gt;Two numbers matter: license and compute. Compute you can measure (§3). License is where the surprise is.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Remotion&lt;/th&gt;
&lt;th&gt;HyperFrames&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;License&lt;/td&gt;
&lt;td&gt;Source-available &lt;strong&gt;Remotion License&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Apache 2.0&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free tier&lt;/td&gt;
&lt;td&gt;Individuals and companies of &lt;strong&gt;up to 3 people&lt;/strong&gt;; unlimited commercial use&lt;/td&gt;
&lt;td&gt;No per-render fees, no commercial-use threshold&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Paid tier&lt;/td&gt;
&lt;td&gt;Company License: &lt;strong&gt;Creators $25/seat/mo&lt;/strong&gt;, or &lt;strong&gt;Automators $0.01/render, $100/mo minimum&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Render engine&lt;/td&gt;
&lt;td&gt;Headless Chrome + FFmpeg&lt;/td&gt;
&lt;td&gt;Headless Chrome + FFmpeg&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authoring&lt;/td&gt;
&lt;td&gt;React components&lt;/td&gt;
&lt;td&gt;HTML + CSS + seekable animation (GSAP, Lottie, Three.js, WAAPI…)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The threshold is the thing to read twice. A three-person studio is free. Hire a fourth person and you are, by the license's own terms, in Company License territory — and if you're building a product that renders videos for &lt;em&gt;other people&lt;/em&gt;, that's the Automators plan at $0.01/render with a $100/month floor. For a tool that renders 50,000 personalized clips a month, $500 is fine. For a side project that renders nine, it is not.&lt;/p&gt;

&lt;p&gt;HyperFrames is the escape hatch: Apache 2.0, same rendering mechanism, different authoring bet (HTML instead of React). Its README is blunt about the trade-off — &lt;em&gt;"Remotion's bet is React components; HyperFrames' bet is plain HTML that humans and agents can both write easily."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;For the token side, one datapoint rather than a claim: NiharDaily's October 2026 hands-on reports &lt;strong&gt;$0.13&lt;/strong&gt; in model cost for a first prompt-to-MP4 run in Claude Code. Treat that as an order of magnitude, not a benchmark — it depends entirely on how many times the agent renders, reads a frame, and iterates.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Where it actually breaks
&lt;/h2&gt;

&lt;p&gt;This is the section that decides whether you ship or abandon, and it's the part the tutorials skip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The verification gap.&lt;/strong&gt; &lt;em&gt;"It compiles" is not "it looks good."&lt;/em&gt; An agent can run &lt;code&gt;remotion render&lt;/code&gt; and get a green exit code from a composition where the text is white on white, the logo is cropped, or the animation finished at frame 12. Digital Applied calls this the hardest self-verification problem in the agentic toolchain, and they're right: there is no unit test for "does this look right", and the visual-regression tooling that exists measures pixel differences, not intent.&lt;/p&gt;

&lt;p&gt;Practical workarounds, in order of usefulness:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Give the agent a component library, not a blank canvas.&lt;/strong&gt; Improvised motion is exactly where AI video looks cheap. If it composes &lt;code&gt;&amp;lt;LowerThird/&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;StatCard/&amp;gt;&lt;/code&gt; instead of inventing springs, the floor on quality rises enormously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make it render stills, not videos, while iterating.&lt;/strong&gt; &lt;code&gt;npx remotion still MyComp out/f.png --frame=45&lt;/code&gt; took &lt;strong&gt;6.7 s&lt;/strong&gt;; a full 5-second render takes 11.7 s and a 60-second one takes 68.7 s. Bundling is a fixed ~5 s cost, so the longer your composition, the more the still saves — and it catches most layout errors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Have it read the frame back.&lt;/strong&gt; Agents with vision can open &lt;code&gt;out/frame.png&lt;/code&gt;. Without this, you are the verification loop.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Everything else that bites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your first render is not your render time.&lt;/strong&gt; Headless Chrome download alone is most of the 40 s. Budget for it in CI warm-up, not in your demo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fonts and media load asynchronously.&lt;/strong&gt; A frame screenshot that races an image decode is a flaky test you'll spend a day on. Local assets, not CDN URLs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long videos stay linear.&lt;/strong&gt; 10 minutes at 1080p is ~11 minutes of render on this machine. That's fine locally, unacceptable in a request handler — this is what Remotion Lambda and HyperFrames' Lambda path exist for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The license threshold is a headcount trigger, not a usage trigger.&lt;/strong&gt; Track it before legal does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-generated is visible.&lt;/strong&gt; If you can tell a video was made from a prompt, the tool didn't fail; the brief did.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. When to use which
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You need&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Motion graphics, explainers, charts, lower-thirds, data-driven or parameterized clips&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Video as code&lt;/strong&gt; (Remotion / HyperFrames)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Photoreal footage, people, b-roll, "a rainy street in Osaka"&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;A generative model&lt;/strong&gt; (Veo 3.1 / Gemini Omni Flash), then assemble in code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A talking head&lt;/td&gt;
&lt;td&gt;An avatar product; neither of the above&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10,000 personalized variants of one template&lt;/td&gt;
&lt;td&gt;Video as code + Lambda; this is the killer use case&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One 30-second promo, once&lt;/td&gt;
&lt;td&gt;Neither. Open an editor.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest framing: video as code is not a cheaper way to make the video you were going to make in Premiere. It's a way to make videos that &lt;strong&gt;didn't exist as a category before&lt;/strong&gt; — one per customer, one per data update, one per pull request.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Checklist before you commit
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Decide the authoring surface first (React vs HTML). Migrating later is a rewrite, not a refactor.&lt;/li&gt;
&lt;li&gt;[ ] Read the license terms against your current headcount, not next year's.&lt;/li&gt;
&lt;li&gt;[ ] Ship a component library on day one; never let the agent invent motion.&lt;/li&gt;
&lt;li&gt;[ ] Use &lt;code&gt;remotion still&lt;/code&gt; (or &lt;code&gt;render --frames=0-59&lt;/code&gt;) for iteration; full render only on merge.&lt;/li&gt;
&lt;li&gt;[ ] Keep media local; pin fonts.&lt;/li&gt;
&lt;li&gt;[ ] Put a human on frame review — that loop is the product.&lt;/li&gt;
&lt;li&gt;[ ] Measure render seconds per output second on &lt;em&gt;your&lt;/em&gt; box; ours was 26 fps of output at 1080p.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Remotion docs — &lt;em&gt;Coding Agents&lt;/em&gt;: &lt;a href="https://www.remotion.dev/docs/ai/coding-agents" rel="noopener noreferrer"&gt;https://www.remotion.dev/docs/ai/coding-agents&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remotion docs — &lt;em&gt;Agent Skills&lt;/em&gt; (12 skills, installers): &lt;a href="https://www.remotion.dev/docs/ai/skills" rel="noopener noreferrer"&gt;https://www.remotion.dev/docs/ai/skills&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remotion — license and pricing: &lt;a href="https://www.remotion.pro/license" rel="noopener noreferrer"&gt;https://www.remotion.pro/license&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HyperFrames — repository and README (determinism, Apache 2.0, comparison table): &lt;a href="https://github.com/heygen-com/hyperframes" rel="noopener noreferrer"&gt;https://github.com/heygen-com/hyperframes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HyperFrames — docs: &lt;a href="https://hyperframes.heygen.com" rel="noopener noreferrer"&gt;https://hyperframes.heygen.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;HeyGen Research — &lt;em&gt;HTML to Video Was Not Easy: Here's How We Solved It&lt;/em&gt;, James Russo, 2026-06-22: &lt;a href="https://www.heygen.com/research/html-to-video" rel="noopener noreferrer"&gt;https://www.heygen.com/research/html-to-video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Digital Applied — &lt;em&gt;Video as Code: Remotion and the Agent Feedback Gap&lt;/em&gt;, 2026-08-11: &lt;a href="https://www.digitalapplied.com/blog/video-as-code-remotion-agentic-generation-2026" rel="noopener noreferrer"&gt;https://www.digitalapplied.com/blog/video-as-code-remotion-agentic-generation-2026&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NiharDaily — &lt;em&gt;Claude Code and Remotion: install the skill, render a video&lt;/em&gt;, 2026-10-17: &lt;a href="https://www.nihardaily.com/posts/claude-code-and-remotion-install-the-skill-render-a-video" rel="noopener noreferrer"&gt;https://www.nihardaily.com/posts/claude-code-and-remotion-install-the-skill-render-a-video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;snapcn — &lt;em&gt;Claude Code + Remotion&lt;/em&gt;, 2026-09-24: &lt;a href="https://snapcn.dev/claude-code-remotion-videos" rel="noopener noreferrer"&gt;https://snapcn.dev/claude-code-remotion-videos&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;VEED — &lt;em&gt;open-edit&lt;/em&gt;, agent-driven editing pipeline: &lt;a href="https://github.com/veedstudio/open-edit" rel="noopener noreferrer"&gt;https://github.com/veedstudio/open-edit&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further reading (in Chinese)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.hubwiz.com/blog/generative-media-stack/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=video-as-code-coding-agents" rel="noopener noreferrer"&gt;多模态生成完全指南 —— image / video / audio hub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hubwiz.com/blog/building-ai-generated-video-with-remotion/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=video-as-code-coding-agents" rel="noopener noreferrer"&gt;用 Remotion 构建 AI 生成视频 —— the "typed catalog" architecture that complements this one&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Commands and timings are our own (2026-10-26). Product claims are attributed above. Chinese original: &lt;a href="https://www.hubwiz.com/blog/video-as-code-coding-agents/" rel="noopener noreferrer"&gt;hubwiz&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Your coding agents don't need more messages — they need a handoff contract</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Sun, 25 Oct 2026 04:56:58 +0000</pubDate>
      <link>https://dev.to/tiaforge/your-coding-agents-dont-need-more-messages-they-need-a-handoff-contract-3lna</link>
      <guid>https://dev.to/tiaforge/your-coding-agents-dont-need-more-messages-they-need-a-handoff-contract-3lna</guid>
      <description>&lt;p&gt;I run several coding agents at once. The part that keeps costing me time is not the code they write — it is the note I hand from one to the next.&lt;/p&gt;

&lt;p&gt;When I write that note by hand, I write what hurts: &lt;em&gt;"CSV export loses the filters."&lt;/em&gt; That sentence is accurate and completely useless to an executor. It does not say which revision to start from, which command reproduces it, what output counts as correct, or who is allowed to say it is done.&lt;/p&gt;

&lt;p&gt;That is the whole thesis of this post, and it comes from four October 2026 write-ups by people who hit the same wall. Everything factual below is theirs; I reproduced none of it, so treat the numbers as claims with sources. Sources and disclosures are at the end.&lt;/p&gt;

&lt;h2&gt;
  
  
  A ticket describes the symptom. A contract describes the job.
&lt;/h2&gt;

&lt;p&gt;The author of the first write-up puts it in one line: a ticket can accurately describe a bug while leaving the executor unable to start.&lt;/p&gt;

&lt;p&gt;His framing is more useful than a tool comparison, because it applies before any tool enters the picture: &lt;em&gt;compare responsibilities, not labels.&lt;/em&gt; A task tracker can be part of a well-managed execution system. An agent runner can be badly managed. The difference is whether the handoff specifies four things.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;The question it must answer&lt;/th&gt;
&lt;th&gt;How it usually gets faked&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Which exact starting revision? Which behavior, constraints, allowed files?&lt;/td&gt;
&lt;td&gt;"Just change it on main"; a long conversation instead of a short, current spec&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Runner&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Who is the authorized operator? Is the environment actually ready?&lt;/td&gt;
&lt;td&gt;Naming an agent when nobody installed the deps or can grant repo access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Evidence&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Patch revision, commands actually run, results, and &lt;strong&gt;untested cases&lt;/strong&gt;?&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;"done"&lt;/code&gt; — a state label, not a verification record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Acceptance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Who decides the result satisfies the requirement?&lt;/td&gt;
&lt;td&gt;The executor quietly swapping the requirement for an easier reading&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note the asymmetry: three of these four are things an agent &lt;em&gt;cannot&lt;/em&gt; invent for itself. Acceptance in particular has to be a person or a role that is not the implementer, which is why "the agent said it was done" is not a verification story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The template
&lt;/h2&gt;

&lt;p&gt;The original template is for a CSV repair. Here it is generalized, with the author's own warning intact: values in angle brackets must come from the real project, and &lt;strong&gt;command names must not be invented&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;Preserve the selected filters in CSV export&lt;/span&gt;
&lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;&amp;lt;authorized repository&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;base_revision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;verified commit&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;reproduction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;Select status=open, then export CSV&lt;/span&gt;
  &lt;span class="na"&gt;expected&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;Export contains exactly the rows matching the filter&lt;/span&gt;
  &lt;span class="na"&gt;fixtures&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;&amp;lt;sanitized fixture path&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;Export query and focused regression coverage&lt;/span&gt;
  &lt;span class="na"&gt;excluded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Authentication, schema migrations, unrelated UI changes&lt;/span&gt;
&lt;span class="na"&gt;execution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;&amp;lt;developer or authorized agent operator&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;setup_command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;verified command from this repository&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;test_command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;&amp;lt;verified command from this repository&amp;gt;&lt;/span&gt;
  &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;Existing authorized development access only&lt;/span&gt;
&lt;span class="na"&gt;delivery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;patch_revision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;required&lt;/span&gt;
  &lt;span class="na"&gt;commands_and_results&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;required&lt;/span&gt;
  &lt;span class="na"&gt;sample_output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="s"&gt;sanitized CSV from the fixture&lt;/span&gt;
  &lt;span class="na"&gt;untested_cases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;        &lt;span class="s"&gt;required, even when empty&lt;/span&gt;
&lt;span class="na"&gt;acceptance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;product_owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Confirms filtering semantics&lt;/span&gt;
  &lt;span class="na"&gt;engineer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;      &lt;span class="s"&gt;Reviews implementation and regression evidence&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three rules ride along with it, and the second one is the one people break in a hurry:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Do not paste credentials into the contract.&lt;/strong&gt; The executor uses their own authorized tools and access.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An unavailable environment is an execution blocker, not an invitation to share an account.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;The template is a &lt;em&gt;proposed&lt;/em&gt; operating practice — not evidence that any platform fills in every field automatically.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;untested_cases: required, even when empty&lt;/code&gt; deserves its own sentence. Empty and "none" are different claims: the first is missing information, the second is a checkable statement.&lt;/p&gt;

&lt;h2&gt;
  
  
  What one handoff actually looks like
&lt;/h2&gt;

&lt;p&gt;The walkthrough in the source is worth keeping whole, because it shows where a human belongs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The product owner builds a fixture: three records, two open and one closed. The expected filtered export is &lt;strong&gt;exactly two rows&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;An engineer confirms the behavior and points at the export path.&lt;/li&gt;
&lt;li&gt;The executor &lt;strong&gt;first reproduces the failure at the recorded revision&lt;/strong&gt;. If it does not reproduce, the next useful delivery is not a patch — it is the attempted command, environment details, and observed output. Editing before resolving that mismatch risks fixing a different problem.&lt;/li&gt;
&lt;li&gt;Only then: a scoped change, plus coverage for the filtered export. The &lt;strong&gt;unfiltered&lt;/strong&gt; export gets checked too, because repairing one path can silently narrow the other.&lt;/li&gt;
&lt;li&gt;The delivery attaches the patch revision and the real output of the verified test command.&lt;/li&gt;
&lt;li&gt;Two reviewers, separately: the engineer reads the diff and the regression evidence; the product owner looks at the sanitized sample and confirms that "filtered" means what users think it means.&lt;/li&gt;
&lt;li&gt;If either review fails, the same task records the reason and the next attempt.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Honest note from the original: those are &lt;em&gt;proposed&lt;/em&gt; checks. No passing results were claimed. That restraint is part of the standard, not a weakness of the article.&lt;/p&gt;

&lt;h2&gt;
  
  
  The portable version: five fields, five sections of evidence
&lt;/h2&gt;

&lt;p&gt;The follow-up post targets a messier reality: one teammate uses a terminal agent, another uses an editor assistant. What transfers between them is &lt;strong&gt;the task contract&lt;/strong&gt; — inputs, allowed changes, required evidence, a reviewer. Conversation history is optional context.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;outcome&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;reject invalid worker-count configuration before startup&lt;/span&gt;
&lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;allowed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;config parser and focused parser tests&lt;/span&gt;
  &lt;span class="na"&gt;excluded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deployment, credentials, queue architecture&lt;/span&gt;
&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;record the real base commit&lt;/span&gt;
  &lt;span class="na"&gt;config_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;replace with the actual key&lt;/span&gt;
  &lt;span class="na"&gt;reproduction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;supply a synthetic invalid value&lt;/span&gt;
&lt;span class="na"&gt;acceptance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;omitted value preserves the current default&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;valid positive integer preserves behavior&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;zero, negative, and non-integer values are rejected&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;error names the key without printing secret configuration&lt;/span&gt;
&lt;span class="na"&gt;stop_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;the parser contract conflicts with documented behavior&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;the change requires a production configuration update&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;unrelated failures prevent a trustworthy comparison&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;stop_when&lt;/code&gt; is the field I stole immediately. It gives the executor permission to stop &lt;em&gt;and&lt;/em&gt; a reason to record, which is exactly what you want from an autonomous worker that is about to guess.&lt;/p&gt;

&lt;p&gt;The evidence protocol is five lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Before:      command, exit code, observed output, base revision
Change:      files and the reason each file is necessary
After:       same reproduction, focused tests, actual exit codes
Uncertainty: checks not run, and why
Review:      commit reference and named acceptance owner
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two sentences from that post are worth pinning above a desk:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"The model says validation is fixed" is a hypothesis. A command and its output are evidence that a check ran.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;…and the reviewer still has to judge whether that check covers the intended failure.&lt;/p&gt;

&lt;p&gt;Two gates that get skipped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eligibility comes before capability.&lt;/strong&gt; Access is a gate: the runner must already be authorized. A teammate with a strong model and no repository permission is not eligible just because someone could paste them a token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest non-results.&lt;/strong&gt; In the article's example the executor discovers that a worker count of zero &lt;em&gt;deliberately&lt;/em&gt; means automatic sizing. The correct move is to stop, return the fact, and revise the requirement — not to change working behavior to satisfy an invented test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pre-acceptance checks: verify the recorded revision, reproduce the named cases, look for accidental configuration disclosure in the error message, and confirm no deployment or unrelated cleanup slipped into the diff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the handoff an object (and holding the writes)
&lt;/h2&gt;

&lt;p&gt;The third write-up takes the opposite route: no template, a document model.&lt;/p&gt;

&lt;p&gt;Its observation is blunt — passing messages between agents is straightforward; &lt;strong&gt;keeping track of requirements, decisions, results, validation, and unfinished work is not&lt;/strong&gt;. A research agent handing off to a coding agent cannot send a summary: the receiver needs the original requirements, the constraints, the decisions already made, the results, the open questions, and the next steps.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;Work Order&lt;/code&gt; in, &lt;code&gt;Work Report&lt;/code&gt; back, with one model and one set of validation rules behind three entry points — a TypeScript package, an MCP server (so Claude Code and friends can create/accept/report/review through tools), and a Python implementation that runs the &lt;strong&gt;same shared test cases&lt;/strong&gt; and reports the same findings for the same documents.&lt;/p&gt;

&lt;p&gt;The interesting part is the MCP gateway in the middle. The worker does not touch real tools directly; every call goes through the gateway, which &lt;strong&gt;enforces that order's allowed tools and budget on each call and records the call as evidence&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Version 0.7 added a side-effect gate — staged tools:&lt;/p&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiz30i10us7qgsjqija4h.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiz30i10us7qgsjqija4h.jpg" alt="Staged tools: the worker's reads run straight through the gateway, but its gmail/send call is held, logged, and answered with a placeholder; the worker lists it in the report; on approve the gateway runs it once, in order" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Reads pass through, writes are held, and only an approval makes them run — once. (Image: the source post at dev.to, rehosted.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Mechanics worth copying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reads run normally.&lt;/strong&gt; Only staged calls wait, so the worker can still research.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writes are held.&lt;/strong&gt; The gateway counts and logs the call with its arguments but does not forward it; the worker gets a placeholder like &lt;code&gt;pending:gmail/send#3&lt;/code&gt; and lists it under &lt;code&gt;effects&lt;/code&gt; in its report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The manager decides.&lt;/strong&gt; Approve → the gateway runs the held calls once, in order. Send it back → they are dropped, and &lt;em&gt;nothing has to be undone because nothing ran&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Four details the author says only showed up in real workflows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Partial approval.&lt;/strong&gt; Approve the work but run only chosen effects: &lt;code&gt;effects: ["gmail/send#3"]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependent effects.&lt;/strong&gt; A later held call can reference an earlier one — &lt;code&gt;{"$ref": "github/create_issue#3", "pointer": "/structuredContent/number"}&lt;/code&gt; — and the gateway fills it in at execution time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At most once, by reconciliation.&lt;/strong&gt; Before running anything, the gateway checks every effect in the report against what it actually logged. Mismatch → nothing runs. Re-running &lt;code&gt;--commit&lt;/code&gt; never double-sends, even after a crash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sub-agents cannot route around it.&lt;/strong&gt; A worker cannot hand a staged tool to a sub-agent: the sub-agent's writes would execute when the &lt;em&gt;worker&lt;/em&gt; approves, before the manager has seen anything.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is also &lt;code&gt;progress()&lt;/code&gt; for in-flight notes and &lt;code&gt;trackRecord()&lt;/code&gt; — a worker's history of first-time approvals, rework, failures, expiries, and budget spent. Judge a worker by that, not by its self-report.&lt;/p&gt;

&lt;p&gt;Disclosure: the author is building these packages, so read it as "someone doing the thing", not neutral review.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ledger: derive health, never store it
&lt;/h2&gt;

&lt;p&gt;Writing a contract solves &lt;em&gt;what&lt;/em&gt; to hand over. The Agent Board write-up solves the other half: whether you can still see the whole board afterwards.&lt;/p&gt;

&lt;p&gt;The problem statement is three questions you cannot answer when agents run in parallel: &lt;strong&gt;is it still working, is it stuck, and how much of the subscription is it burning?&lt;/strong&gt; The original requirement was blunter — one always-accurate page. The author then names why that is hard: this is a distributed systems problem. Agents start and die outside any single process, report asynchronously, and &lt;strong&gt;lie by omission when they crash&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;His answer: the board is a projection; the ledger is the only source of truth. Four invariants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Register before spawn.&lt;/strong&gt; The task row exists before the process does, so nothing started through the supported path can run unrecorded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown stays unknown.&lt;/strong&gt; No estimated percentages, no inferred exit codes, no guessed usage. A Claude session without a reliable PID shows &lt;code&gt;unknown&lt;/code&gt; and its Stop button is disabled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Derive, do not store, health.&lt;/strong&gt; Status is a fact written by a producer; health is computed at read time from status plus idle time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never block the agent.&lt;/strong&gt; Instrumentation fails open: a busy ledger drops an event rather than adding latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Three producers, one append-only ledger
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Launch wrapper.&lt;/strong&gt; Writes the row, then spawns the agent in JSON-event mode; heartbeats every 15 s while the child lives; records the exit code; a spawn failure is recorded as failed with exit 127 instead of vanishing; signal forwarding is installed before the prompt is sent, so stop reaches the child. A test asserts the ordering: a fake agent blocks before its first event while the test polls the ledger and asserts the row already exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lifecycle hooks.&lt;/strong&gt; Session start/stop, prompt submit, helper start/stop, tool events before and after each call. Lifecycle hooks run synchronously, tool hooks asynchronously. Each hook gets a &lt;strong&gt;25 ms lock wait and a 150 ms work budget&lt;/strong&gt;, targeting an exit under 200 ms; on timeout it logs and drops the event; it always exits zero and &lt;strong&gt;never writes to stdout&lt;/strong&gt; — anything printed would be injected into the agent's context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconciler, every 5 s.&lt;/strong&gt; A running task whose process is gone with no exit becomes &lt;code&gt;lost&lt;/code&gt;. A running task idle for more than 600 s becomes &lt;code&gt;stalled&lt;/code&gt;, flipping back on the next event. It also scans the process table for Codex processes that never went through the wrapper, walks up to three parent levels to avoid double counting, and records them as &lt;code&gt;untracked&lt;/code&gt;, keyed by PID.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fksrdv6fh5jeuyncwew4t.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fksrdv6fh5jeuyncwew4t.jpg" alt="Seven recorded statuses and four derived health bands" width="790" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Seven statuses; health is a read-time function, so the board cannot drift from the ledger. (Redrawn from the source's section 5.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Progress exists only when there is a denominator
&lt;/h3&gt;

&lt;p&gt;My favourite rule in the whole write-up: percent complete is shown only when a denominator was declared. Runs launched with a step count ask the agent to emit &lt;code&gt;PROGRESS n/total&lt;/code&gt;, parsed from messages; Claude tasks report checklist items; everything else says "progress not reported". Even then it is the agent's own claim — &lt;strong&gt;the timeline is the evidence&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Admission control: one writer, two slots
&lt;/h3&gt;

&lt;p&gt;Concurrency per repository is bounded by kernel advisory locks, with the lock root derived from a hash of the checkout path. A writing run takes an exclusive writer lock, then one of two slot locks, tried without blocking. No sentinel files, no sleeps, no polling loops — a crashed holder releases when the kernel closes the file.&lt;/p&gt;

&lt;p&gt;The cap of two came from evidence, not taste: on day one, with everything at high effort, the Codex weekly allowance dropped to &lt;strong&gt;11% left&lt;/strong&gt;, and a third concurrent stream stalled while two ran. Runs also carry a token budget — 40 million by default — counted from the agent's own token events; over budget, the run is stopped and exits 125.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the ledger actually recorded (2026-09-08 → 10-23)
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measure&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Task rows / events&lt;/td&gt;
&lt;td&gt;28,149 / 540,175&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;By source&lt;/td&gt;
&lt;td&gt;8,331 Codex, 7,267 Claude sessions, 12,373 Claude helpers, 178 other&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wrapper-launched Codex runs&lt;/td&gt;
&lt;td&gt;1,380 done, 116 failed, 267 lost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Codex processes found outside the wrapper&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;6,568 — all ended as lost&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mean wall time, done runs&lt;/td&gt;
&lt;td&gt;7.95 min over 803 runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review verdicts&lt;/td&gt;
&lt;td&gt;160 APPROVE, 337 FIX, 87 INVALID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;188 methods in 22 Python files + 12 browser scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The largest number is the useful one: most Codex rows were never launched by the wrapper at all. They are processes the reconciler caught running on their own, recorded as untracked, then lost. That is the detector working — and a measure of how much agent activity is otherwise invisible.&lt;/p&gt;

&lt;h3&gt;
  
  
  Five failures worth stealing
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The kill chain reaction.&lt;/strong&gt; Killing a reviewer produced a signal exit → parsed as INVALID → triggered the fix loop → spawned a new worker &lt;em&gt;and&lt;/em&gt; a new review. Each kill produced two or three more tasks. Fix: a reviewer stopped by a signal ends the chain with a &lt;code&gt;STOPPED&lt;/code&gt; verdict.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Budget burn, itemized.&lt;/strong&gt; Day one: 36 tasks, ~24 agent hours in 36 hours, all high effort. The breakdown: re-reading context ~25%, oversized command output 18%, repeated aggregate verification 12%, high effort on routine work 12%. Roles, bounded packets, a release window and the two-run cap all came out of that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Phantom helpers.&lt;/strong&gt; Helpers that never closed showed as stalled for hundreds of hours and flooded the view. Aggregation by project plus a 15-minute idle cutoff replaced one sprite per helper.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lock contention.&lt;/strong&gt; Builds in the board's own repo failed with &lt;code&gt;database is locked&lt;/code&gt; while a rendering service held the ledger. Operating rule: pause that service during builds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ready before verified.&lt;/strong&gt; UI work was reported ready before anyone opened it in a real browser with real data. New rule: no UI is ready until someone has looked at a screenshot.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The review gate: packet, grammar, one fix round
&lt;/h2&gt;

&lt;p&gt;Implementation runs as the role &lt;code&gt;worker&lt;/code&gt;, followed by an isolated review. The reviewer gets &lt;strong&gt;a packet, not a conversation&lt;/strong&gt;: the task-local diff, at most 200 lines of test output, the worker report, and the path to the original brief — inside a 256 KB budget. When the budget is tight, unchanged context and non-test file bodies are dropped, with recovery paths the reviewer can open.&lt;/p&gt;

&lt;p&gt;The verdict grammar is strict: exactly &lt;code&gt;APPROVE&lt;/code&gt;, or a numbered list of fixes. Anything else is classified &lt;code&gt;INVALID&lt;/code&gt;. A &lt;code&gt;FIX&lt;/code&gt; returns to the worker &lt;strong&gt;once&lt;/strong&gt;; a second review follows; still unresolved → exit 2, surfaced in a human queue. Nothing escalates automatically. Reviewers can be two different models, in which case both must approve and disagreements are labelled.&lt;/p&gt;

&lt;p&gt;584 verdicts on record: 160 APPROVE, 337 FIX, 87 INVALID. &lt;strong&gt;Fix requests outnumber approvals two to one&lt;/strong&gt; — and the author says that is the point of the loop, not a bad sign.&lt;/p&gt;

&lt;p&gt;The human queue is computed, not curated, from five sources (tasks failed in the last two hours, reviews still asking for changes, manually raised needs, needs raised by a live Claude session, work items waiting on the owner), with three suppression rules (acknowledged after the latest signal; a later run in the same directory or with the same title succeeded; dedupe per task, keep the newest signal). Display cap: five per lane. In the author's words — needs you, maximum five, real and current only.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the "discussion" part goes wrong
&lt;/h2&gt;

&lt;p&gt;Handoffs are half the problem. The other half is agents disagreeing about a plan.&lt;/p&gt;

&lt;p&gt;A vendor write-up (CEO's column, so read it as an argument) describes a consensus cycle: agents share draft plans, surface conflicts, revise, repeat. It settles many disagreements — and when it cannot, the system escalates to a human. Then the important observation: &lt;strong&gt;the safe thing has a cost curve.&lt;/strong&gt; One team escalating occasionally is governance; fifty teams escalating every unresolved disagreement is a flooded queue, and flooded people approve by default. That is automation bias (Parasuraman &amp;amp; Manzey 2010; the EU AI Act names it in Article 14). A rubber stamp and a working control produce the same output while conditions are normal.&lt;/p&gt;

&lt;p&gt;So the constraint is not agent capacity — that is cheap and getting cheaper. &lt;strong&gt;It is the handful of people who understand the operation well enough to settle an argument.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The triage that follows is the part I actually use. Take the last twenty decisions your team pushed to a person and sort them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pile one:&lt;/strong&gt; the parties disagreed about &lt;em&gt;what will happen&lt;/em&gt; — different data, different history, different confidence in the same lever. This pile does not need a person's judgement; it needs both parties to see the same evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pile two:&lt;/strong&gt; they agreed on what will happen and disagreed about &lt;em&gt;what to want&lt;/em&gt; — throughput against energy cost, this customer's order against that one's. This pile is where a person's day should go.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your queue is mostly pile one, you are running an evidence problem through a governance process, and adding approvers will not fix it.&lt;/p&gt;

&lt;p&gt;The proposed split is clean: &lt;strong&gt;agents strategize, mathematics closes the deal.&lt;/strong&gt; Each agent decides only two things — eight weights over delivery slots, and a walk-away floor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;U(o) = w1·u1(o1) + w2·u2(o2) + … + w8·u8(o8)   accept only if U(o) ≥ r
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agreement itself is produced by a bargaining engine running alternating offers. No language model makes an offer, concedes a point, or declares a deal; the same positions in produce the same agreement out, auditable to the individual offer, settling in about a second — and when there is genuinely nothing to trade, the agents walk away, which is also correct.&lt;/p&gt;

&lt;p&gt;That gives you a third stage between "force consensus" and "page a human": a mediator proposes candidate settlements, filtered so nothing breaching a constraint is ever offered; each agent judges only whether a candidate beats the one on the table, and any agent can veto — without revealing its weights. What reaches the person is not two drafts and a deadlock notice, but a short list of what could not be settled and who vetoed what.&lt;/p&gt;

&lt;p&gt;The companion piece on roles adds four short rules that keep a "team" from becoming a demo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A role is one job and one checkable output.&lt;/strong&gt; "Research competitors and return a claims table with a source column" is a role. "Help with marketing" is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Boundaries belong in tools, not prompts.&lt;/strong&gt; A reviewer reads the diff and posts comments; it cannot merge. Instructions can be ignored or misread; a missing tool cannot be used.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the handoff explicit:&lt;/strong&gt; name the artefact, the reason it moved, and what the receiver must check — and log the handoff, so you can see where a decision changed hands.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cap the coordinator:&lt;/strong&gt; max reassignments, a time limit, and a rule for what happens after a task fails twice. It should also point at evidence per task, not "all complete".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the sequencing advice I keep repeating to myself: start with &lt;strong&gt;two roles and one handoff&lt;/strong&gt;, and get it boring before adding a third.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the three layers together
&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F722lofoz0on8jmfotr81.jpg" 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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F722lofoz0on8jmfotr81.jpg" alt="Three layers: handoff contract (revision, commands, expected output, acceptor, open items), ledger (append-only, health derived), gates (packet, APPROVE or numbered fixes, one fix round, then a human)" width="790" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Handoff contract for the next agent; ledger for the machine; gate before side effects. (Drawn by the author from the four sources above.)&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Artifact&lt;/th&gt;
&lt;th&gt;Written by&lt;/th&gt;
&lt;th&gt;Read by&lt;/th&gt;
&lt;th&gt;If missing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Handoff&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;HANDOFF.md&lt;/code&gt; (five fields + five evidence sections)&lt;/td&gt;
&lt;td&gt;Upstream owner or upstream agent&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;The next agent's start script&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The next agent guesses, or asks you — which is where we started&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ledger&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;tasks.tsv&lt;/code&gt; or SQLite, append-only&lt;/td&gt;
&lt;td&gt;Wrapper, hooks, reconciler&lt;/td&gt;
&lt;td&gt;Board, gates, coordinator&lt;/td&gt;
&lt;td&gt;No answer to "who is running, stuck how long, spent what"; crashes lie by omission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gate&lt;/td&gt;
&lt;td&gt;Review packet + verdict + approval&lt;/td&gt;
&lt;td&gt;Independent reviewer or authorized human&lt;/td&gt;
&lt;td&gt;Merge, deploy, anything with consequences&lt;/td&gt;
&lt;td&gt;You reject the report after the email has already gone out&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three things actually get automated here — none of them is "make the model smarter":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate the contract at the end of the upstream task&lt;/strong&gt;: template plus a validator that checks the four fields are present, &lt;code&gt;acceptor ≠ implementer&lt;/code&gt;, and &lt;code&gt;base_commit&lt;/code&gt; actually exists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consume the contract at the start of the downstream task&lt;/strong&gt;: the launcher injects &lt;code&gt;HANDOFF.md&lt;/code&gt; into the first turn, instead of you pasting it into a chat box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gate before the side effect&lt;/strong&gt;: not "reject the report afterwards", but "hold the write until approved". That is exactly why staged tools matters — a rejection leaves nothing to undo.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  A three-week rollout with pass/fail criteria
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Week 1 — contract plus validator.&lt;/strong&gt; &lt;code&gt;HANDOFF.md&lt;/code&gt; in the repo, a ~30-line script. Pass: ten consecutive tasks where no downstream agent comes back asking for missing information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 2 — wrapper plus reconciler.&lt;/strong&gt; Register the row before spawn; heartbeat every 15 s; mark &lt;code&gt;lost&lt;/code&gt; when a PID disappears without an exit; mark &lt;code&gt;stalled&lt;/code&gt; past 10 minutes idle. Pass: you can answer "who is running, stuck how long, spent what" at any moment, and &lt;code&gt;unknown&lt;/code&gt; was never silently filled with 0.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Week 3 — reviewer plus grammar plus one fix round.&lt;/strong&gt; Packet, not conversation; verdicts limited to APPROVE or a numbered list; a single fix round; a human queue capped at five. Pass: the fix/approve ratio is stable, and the queue stays under five — if it does not, fix the process before adding approvers.&lt;/p&gt;

&lt;p&gt;The discussion layer goes after week 2: give plans an independent read-only reviewer, cap the loop at two rounds, and escalate — otherwise you build the flooded approver from the previous section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cautions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The ledger only covers runs that go through the wrapper. Those 6,568 outside processes are the proof: you must assume a share of activity is invisible.&lt;/li&gt;
&lt;li&gt;Heartbeats prove liveness, not progress.&lt;/li&gt;
&lt;li&gt;Lifecycle hooks cannot get a reliable PID for Claude sessions, so the board cannot stop them; progress counts are self-reported; budgets come from the agent's own token events.&lt;/li&gt;
&lt;li&gt;A strict verdict grammar has a price: 87 of 584 verdicts were INVALID (~15%). Watch what INVALID looks like, or you will fix formatting instead of behaviour.&lt;/li&gt;
&lt;li&gt;Locking collides with builds (&lt;code&gt;database is locked&lt;/code&gt;); the rule is to pause the lock holder during builds.&lt;/li&gt;
&lt;li&gt;"Ready before verified" is the most common self-deception; for UI, nobody has verified it until someone looked at a screenshot with real data.&lt;/li&gt;
&lt;li&gt;Never put credentials in a handoff contract.&lt;/li&gt;
&lt;li&gt;The four fields prevent "cannot start". They do not prevent "did it wrong" — that needs the evidence package and the acceptor. Do not merge the two jobs.&lt;/li&gt;
&lt;li&gt;Thresholds quoted here (256 KB packet, 200 log lines, 15 s heartbeat, 10 min idle, 40 M tokens, two slots) are one operator's values, not universal standards — recalibrate for your repo and budget.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;A task record is not an execution contract: a handoff template for coding agents&lt;/em&gt;, dev.to, 2026-10-12 — &lt;a href="https://dev.to/hiroshi_takamura_c851fe71/a-task-record-is-not-an-execution-contract-a-handoff-template-for-coding-agents-4bp8"&gt;https://dev.to/hiroshi_takamura_c851fe71/a-task-record-is-not-an-execution-contract-a-handoff-template-for-coding-agents-4bp8&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;A portable handoff contract for teammates using different AI tools&lt;/em&gt;, dev.to, 2026-10-15 — &lt;a href="https://dev.to/hiroshi_takamura_c851fe71/a-portable-handoff-contract-for-teammates-using-different-ai-tools-4ige"&gt;https://dev.to/hiroshi_takamura_c851fe71/a-portable-handoff-contract-for-teammates-using-different-ai-tools-4ige&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Stop AI Agents from Acting Before You Approve: Work Orders for TypeScript, Python and MCP&lt;/em&gt;, dev.to, 2026-10-16 — &lt;a href="https://dev.to/murthyg/how-to-build-reliable-work-handoffs-between-ai-agents-in-typescript-1neh"&gt;https://dev.to/murthyg/how-to-build-reliable-work-handoffs-between-ai-agents-in-typescript-1neh&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sam Ochoa, &lt;em&gt;Agent Board: coordinating concurrent AI coding agents with a shared task ledger&lt;/em&gt; (WP-05), 2026-10-23 — &lt;a href="https://samuelochoa.com/papers/agent-board/" rel="noopener noreferrer"&gt;https://samuelochoa.com/papers/agent-board/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Pieter van Schalkwyk, &lt;em&gt;Should Multi-Agent Systems Negotiate?&lt;/em&gt;, XMPro, 2026-10-19 — &lt;a href="https://xmpro.com/should-multi-agent-systems-negotiate/" rel="noopener noreferrer"&gt;https://xmpro.com/should-multi-agent-systems-negotiate/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Running a Team of AI Agents: Roles, Boundaries, and the Handoffs That Break&lt;/em&gt;, dev.to, 2026-10-24 — &lt;a href="https://dev.to/flisk/running-a-team-of-ai-agents-roles-boundaries-and-the-handoffs-that-break-4fe9"&gt;https://dev.to/flisk/running-a-team-of-ai-agents-roles-boundaries-and-the-handoffs-that-break-4fe9&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Disclosures.&lt;/strong&gt; The first two posts disclose AI assistance in drafting and state that the worked example was not run against a production repository, and reference a vendor's product page for the workflow model. The third is written by the author of the packages it describes. Agent Board is one operator's single-machine record (its own limitations section applies). The negotiation piece is a vendor CEO's column. Nothing here was independently reproduced by me.&lt;/p&gt;

&lt;p&gt;A longer Chinese version with the field-level contracts, the seven-state machine and the full failure list lives at &lt;a href="https://www.hubwiz.com/blog/agent-handoff-contract-and-ledger/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=agent-handoff-contract" rel="noopener noreferrer"&gt;hubwiz.com/blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>One machine, ten coding agents: giving each one its own identity</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Sat, 24 Oct 2026 06:14:27 +0000</pubDate>
      <link>https://dev.to/tiaforge/one-machine-ten-coding-agents-giving-each-one-its-own-identity-e20</link>
      <guid>https://dev.to/tiaforge/one-machine-ten-coding-agents-giving-each-one-its-own-identity-e20</guid>
      <description>&lt;p&gt;"An agent did it" stops being an answer the moment you run more than one.&lt;/p&gt;

&lt;p&gt;On a single developer machine you can now have Claude Code, Codex, OpenCode, Copilot, Antigravity, or &lt;code&gt;pi&lt;/code&gt; all working in the same repo, sometimes in the same hour. The default setup — one &lt;code&gt;git config user.name&lt;/code&gt;, one GitHub login, one &lt;code&gt;.env&lt;/code&gt; — works fine right up until something goes wrong and you have to answer a question that has no answer: &lt;em&gt;which&lt;/em&gt; agent wrote this, and what was it allowed to touch?&lt;/p&gt;

&lt;p&gt;This post compiles five write-ups from September–October 2026 where teams actually did this, including the mistakes. Everything below is attributed at the end; nothing here was reproduced by me, so treat the numbers as claims with sources rather than verified facts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost of one shared identity
&lt;/h2&gt;

&lt;p&gt;Vitruvian Software runs ten agents in one monorepo — Forge on Bazel, Atlas on infrastructure, Ridge on Kubernetes, Wren on application code, Scout on tests and CI, Aegis on security review, Quill on docs. Until recently, GitHub saw all of them as &lt;strong&gt;one person&lt;/strong&gt;: every push, PR, and merge went out under the repository owner's account.&lt;/p&gt;

&lt;p&gt;That looked harmless and cost them three things:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you think you have&lt;/th&gt;
&lt;th&gt;What you actually get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Attribution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Ridge opened #1440" is a claim in a chat log. Neither git nor GitHub can back it up.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Peer review&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;GitHub blocks self-approval, and every PR looks self-authored — so a security agent cannot give a valid approval on an infra agent's diff.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Audit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The trail says one human did everything, including work that human never saw.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The second one is the one that hurts. If you want agents to check each other, the platform has to believe they are different identities. With one identity, the best you can do is have an agent leave a comment and hope someone reads it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 1 — Git: a token decides &lt;em&gt;who can push&lt;/em&gt;, an environment variable decides &lt;em&gt;who signed&lt;/em&gt;
&lt;/h2&gt;

&lt;p&gt;This is the failure that silently corrupts history, and it is easy to miss.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 1) who pushes (API + git push)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GH_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;installation token&amp;gt;"&lt;/span&gt;

&lt;span class="c"&gt;# 2) who is named in the commit — completely independent of the token&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIT_AUTHOR_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"vitruvian-wren-agent[bot]"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIT_AUTHOR_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;bot_user_id&amp;gt;+vitruvian-wren-agent[bot]@users.noreply.github.com"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIT_COMMITTER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_AUTHOR_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIT_COMMITTER_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GIT_AUTHOR_EMAIL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Vitruvian set only the token first. Result: &lt;strong&gt;the PR showed the bot, and the commit log still showed the machine owner&lt;/strong&gt; — because git takes authorship from local config and environment, not from whoever pushes.&lt;/p&gt;

&lt;p&gt;Three details worth copying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;strong&gt;bot's noreply address&lt;/strong&gt;. GitHub ties it to the App, so commits link back to &lt;code&gt;xxx[bot]&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One-hour installation tokens are a feature.&lt;/strong&gt; The only long-lived secret is the private key, which lives in a vault; the token is minted per session and never pasted anywhere.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;both&lt;/strong&gt; &lt;code&gt;GIT_AUTHOR_*&lt;/code&gt; and &lt;code&gt;GIT_COMMITTER_*&lt;/code&gt;. Change only the author and a rebase or cherry-pick leaks the real operator into the committer field.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Layer 2 — Platform: GitHub Apps, not one bot account per agent
&lt;/h2&gt;

&lt;p&gt;The obvious fix is a machine user per agent. GitHub's terms allow &lt;strong&gt;one free machine account per person&lt;/strong&gt;, so ten agents would mean ten accounts the terms don't cover plus ten sets of credentials to rotate. GitHub Apps have no such limit: an org can own as many as it likes, each gets a real &lt;code&gt;name[bot]&lt;/code&gt; identity that genuinely authors commits and submits reviews, and each authenticates with short-lived installation tokens instead of passwords or PATs. They named them &lt;code&gt;vitruvian-&amp;lt;agent&amp;gt;-agent&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The question the docs don't answer
&lt;/h3&gt;

&lt;p&gt;Before building, they needed to know whether an App's approval counts toward a &lt;code&gt;require 1 approving review&lt;/code&gt; rule. The docs on protected branches say approvals come from "people with write permissions" or a designated code owner — Apps aren't mentioned either way.&lt;/p&gt;

&lt;p&gt;So they measured it on a canary repo with &lt;code&gt;required_approving_review_count: 1&lt;/code&gt; and &lt;code&gt;enforce_admins: true&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;before   reviewDecision=REVIEW_REQUIRED  mergeable_state=blocked
POST /pulls/1/reviews   event=APPROVE   (App installation token)
after    reviewDecision=APPROVED         mergeable_state=unstable
         review  &amp;lt;app-slug&amp;gt;[bot]  type=Bot  state=APPROVED
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It counts. (&lt;code&gt;unstable&lt;/code&gt; was an unrelated pending check.) One limit surfaced: &lt;strong&gt;an App cannot be a code owner&lt;/strong&gt; — &lt;code&gt;CODEOWNERS&lt;/code&gt; accepts users, teams, and email addresses only. So they turned off "require code owner review" and used the approval count as the lever.&lt;/p&gt;

&lt;h3&gt;
  
  
  The step you cannot automate
&lt;/h3&gt;

&lt;p&gt;App creation has no API. You either fill in a web form or use the &lt;strong&gt;App manifest flow&lt;/strong&gt;, which is better because it returns the private key in an API response instead of a browser download:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vitruvian-&amp;lt;agent&amp;gt;-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_permissions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"contents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"pull_requests"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"metadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"checks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"read"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"workflows"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"default_events"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub redirects back with a &lt;strong&gt;one-time code&lt;/strong&gt; you exchange within the hour for the App id and private key. That is the only copy of the key: straight into the vault, response deleted.&lt;/p&gt;

&lt;p&gt;Two traps: the install screen &lt;strong&gt;preselects "All repositories"&lt;/strong&gt; (accept it and every agent can write to every repo in the org — choose "Only select repositories"), and &lt;code&gt;workflows: write&lt;/code&gt; is only for agents that should touch CI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make "who can write where" reviewable
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;repo_config:agentApps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;beacon&lt;/span&gt;
    &lt;span class="na"&gt;installationId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;152031070"&lt;/span&gt;
  &lt;span class="c1"&gt;# ...one entry per agent&lt;/span&gt;
&lt;span class="na"&gt;repo_config:requiredApprovals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pinning each App's installation to the repo turns authorization into &lt;strong&gt;a diff you review&lt;/strong&gt;, not a checkbox on a settings page nobody opens.&lt;/p&gt;

&lt;p&gt;And ordering matters. &lt;code&gt;requiredApprovals: "1"&lt;/code&gt; must be &lt;strong&gt;last&lt;/strong&gt;. Enable required reviews before the agents have identities and there is nobody who can approve: every PR waits at &lt;code&gt;REVIEW_REQUIRED&lt;/code&gt; forever, and release-please / Dependabot auto-merges get stuck too, because a ruleset bypass covers direct merges but never auto-merge into a merge queue.&lt;/p&gt;

&lt;p&gt;Safe order: create and install one App per agent → put keys in the vault and point each harness at its key → add every agent to &lt;code&gt;agentApps&lt;/code&gt; → &lt;em&gt;then&lt;/em&gt; raise &lt;code&gt;requiredApprovals&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Day to day, an agent takes on its identity with one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;bazel run //tools/agent-app &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;env &lt;/span&gt;wren&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks the agent up in a &lt;strong&gt;committed registry&lt;/strong&gt; (App ids, installation ids, bot user ids are all public identifiers), signs a JWT, exchanges it for an installation token, and exports both the token &lt;em&gt;and&lt;/em&gt; the git identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 3 — Inside the commit: who writes the three lines
&lt;/h2&gt;

&lt;p&gt;A git author field holds a name and an email. It cannot hold "which job, which tool, which model". Obi (a Rust agent execution engine) appends three trailers instead:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Oby-Job: 3f2a9c1e
Oby-Agent: Codex
Oby-Model: gpt-6.1-sol
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now a teammate reading &lt;code&gt;git log&lt;/code&gt; can tell which code an agent wrote and with what, without asking the owner — and the owner can query a model's track record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;--grep&lt;/span&gt; &lt;span class="s2"&gt;"Oby-Model: gpt-6.1-sol"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why the engine must write them, not the agent
&lt;/h3&gt;

&lt;p&gt;This is the most transferable idea in the whole design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trailers are written during &lt;code&gt;keep_commits&lt;/code&gt;, when a finished job's commits are packed back into the engine's clone;&lt;/li&gt;
&lt;li&gt;each new commit is rewritten directly — read the raw object with &lt;code&gt;git cat-file commit&lt;/code&gt;, repoint parents, append the trailers, write the new object with &lt;code&gt;git hash-object -t commit -w --stdin&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;deliberately not&lt;/strong&gt; &lt;code&gt;git commit-tree&lt;/code&gt; or &lt;code&gt;git interpret-trailers&lt;/code&gt;, which read &lt;code&gt;commit.gpgSign&lt;/code&gt; / &lt;code&gt;trailer.*&lt;/code&gt; and can change the bytes;&lt;/li&gt;
&lt;li&gt;tree, author, committer, and both dates stay exactly as they were;&lt;/li&gt;
&lt;li&gt;if the agent wrote &lt;code&gt;Oby-*&lt;/code&gt; lines itself, they are &lt;strong&gt;removed first&lt;/strong&gt; — only the engine gets to say this;&lt;/li&gt;
&lt;li&gt;writing the same trailers twice produces the same commit id (idempotent).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two more choices: &lt;code&gt;Oby-Agent&lt;/code&gt; records the &lt;strong&gt;tool&lt;/strong&gt; (&lt;code&gt;Claude Code&lt;/code&gt;, &lt;code&gt;Codex&lt;/code&gt;, &lt;code&gt;Cursor&lt;/code&gt;), not an account; and nothing ever records an account name, email, or hostname, &lt;strong&gt;because commits can be public&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Compare with "make the agent add a &lt;code&gt;Co-authored-by&lt;/code&gt; line": the agent can forget, can get it wrong, and can be talked out of it by an injected instruction. &lt;strong&gt;Attribution written by a layer that does not reason is attribution you can trust.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 4 — Granularity: one action, one commit
&lt;/h2&gt;

&lt;p&gt;Orquesta takes the other route: don't rewrite trailers, make every action a commit. Their argument is that today's tools either dump changes into the working tree or produce one mega-commit at the end ("AI refactor") — and that isn't traceability, it's a black box with a receipt.&lt;/p&gt;

&lt;p&gt;The mechanism:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the agent works on a &lt;strong&gt;real branch&lt;/strong&gt; cut from base;&lt;/li&gt;
&lt;li&gt;every file edit, code-changing shell command, and test-fix cycle is committed, with a Conventional-Commits-style message;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;author field is the agent&lt;/strong&gt;, via a separate git config:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config user.name  &lt;span class="s2"&gt;"Orquesta Agent"&lt;/span&gt;
git config user.email &lt;span class="s2"&gt;"agent@orquesta.local"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;read-only commands produce &lt;strong&gt;no&lt;/strong&gt; commit (nothing changed), but they are logged in the activity feed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The payoff is surgical rollback: when an agent both adds an endpoint and fixes an unrelated bug in one session, and the endpoint regresses, you revert one commit and keep the fix. Noise is handled by asking the agent to combine logically identical changes, plus a squash step before merge — granularity during execution, clean history after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Layer 5 — Process and credentials: give a key to exactly one agent
&lt;/h2&gt;

&lt;p&gt;Identity is not just a name; it is &lt;strong&gt;what the process can read&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Bill Mongan runs nine agents side by side on a small always-on Ubuntu box (~14 GiB RAM): Claude Code, Codex, Antigravity, Grok Build, Hermes, GitHub Copilot, two separate copies of OpenCode, and &lt;code&gt;pi&lt;/code&gt;. Herdr (an agent-aware terminal multiplexer) gives each one a labelled tab and &lt;strong&gt;its own directory&lt;/strong&gt; under &lt;code&gt;~/agents/&amp;lt;name&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;That directory separation matters more than expected: most CLIs resolve "resume the latest conversation" relative to the current directory, and several ask you to trust a workspace before touching it. One directory per agent keeps histories, trust decisions, and scratch files apart.&lt;/p&gt;

&lt;p&gt;Most agents share a single &lt;code&gt;mcpproxy&lt;/code&gt; instance for tools. One tool talks to a service that needs a personal API key, and only one agent should hold it. The trick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the provider declares the secret &lt;strong&gt;both&lt;/strong&gt; as a server-side env var and as a caller-supplied header;&lt;/li&gt;
&lt;li&gt;the proxy's &lt;code&gt;.env&lt;/code&gt; gets the base URL only, and the key is &lt;strong&gt;left unset&lt;/strong&gt; — so the proxy has no credential to hand out;&lt;/li&gt;
&lt;li&gt;the key lives in one scoped OpenCode's private env file and is forwarded as a header:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json-doc"&gt;&lt;code&gt;&lt;span class="nl"&gt;"mcp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"mcpproxy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"remote"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:8888/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"oauth"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"X-MCPProxy-Service-Key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{env:SERVICE_API_KEY}"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;the launcher is a small wrapper: &lt;code&gt;set -a; source service.env; set +a; exec opencode&lt;/code&gt;. &lt;code&gt;set -a&lt;/code&gt; exports every assignment, &lt;code&gt;exec&lt;/code&gt; avoids leaving a shell process holding a copy — &lt;strong&gt;so the variable exists in that process tree and nowhere else&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Other agents can still see the tool; their calls simply fail to authenticate. When they need the capability, they &lt;strong&gt;delegate&lt;/strong&gt; the task to the scoped agent instead of holding the key.&lt;/p&gt;

&lt;h3&gt;
  
  
  And the author is explicit that this is not a hard boundary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;any agent with a shell, running as the same Unix user, can read that &lt;code&gt;service.env&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the header travels over plain HTTP, acceptable only because the proxy listens on loopback;&lt;/li&gt;
&lt;li&gt;real isolation between agents would mean separate Unix users or containers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So: &lt;strong&gt;least privilege by process environment, not a security boundary — and not treated as one.&lt;/strong&gt; Its value is that other agents can't use the key by accident, and can't leak it through a tool that dumps environment variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  What identity does not stop
&lt;/h2&gt;

&lt;p&gt;Be clear about this, or you'll think you're done. Vitruvian published two incidents from their own setup:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Incident&lt;/th&gt;
&lt;th&gt;What happened&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Merge queue bypass&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;main&lt;/code&gt; let repo admins skip the merge queue as a human emergency path. Agent sessions under that account &lt;strong&gt;inherited the bypass&lt;/strong&gt;: 5 of 40 merges skipped the queue in two days; one of them had been rejected by the queue &lt;strong&gt;eight minutes earlier&lt;/strong&gt;, and main went red.&lt;/td&gt;
&lt;td&gt;A pre-tool hook refuses &lt;code&gt;gh pr merge --admin&lt;/code&gt;, direct merges via REST/GraphQL, and pushes to &lt;code&gt;main&lt;/code&gt; — and tells the agent to fix what the queue rejected. A human who really needs the bypass runs it in their own terminal, where the hook doesn't apply.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;The worktree that uninstalled Argo CD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The homelab Pulumi config is gitignored, so it doesn't exist in a worktree → every toggle read &lt;code&gt;false&lt;/code&gt; → one &lt;code&gt;pulumi up&lt;/code&gt; removed Argo CD, its namespace, and every Application it managed.&lt;/td&gt;
&lt;td&gt;The program now refuses to run unless the config states the setting explicitly; and "never apply from a worktree" lives in &lt;code&gt;AGENTS.md&lt;/code&gt; &lt;strong&gt;next to the incident date&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Their conclusion is worth quoting in full:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Per-agent identity is a convention we keep, written into the &lt;code&gt;AGENTS.md&lt;/code&gt; that every coding tool in the repo reads: use your own name; minting as another agent puts their name on your work. It makes honest work attributable and reviewable. It doesn't make dishonest work impossible.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;What actually stops corner-cutting is the other layer: &lt;strong&gt;guardrails written after real incidents&lt;/strong&gt;, where every fix ships with the check that would catch it again.&lt;/p&gt;

&lt;p&gt;Safeguard's October write-up names the five risks of "running as you": excess authority, blind audit trails, prompt injection, credential reach (tokens in env vars and dotfiles are readable by anything you run), and long sessions that accumulate context and access. Their rollout advice is practical: start with the highest-risk accounts and tasks (production, releases, cloud admin), keep everyday coding in a sandbox but remove long-lived secrets — and remember that &lt;strong&gt;if a scoped token takes a day and a ticket, developers will keep using their own identity&lt;/strong&gt;. Tooling matters as much as policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  A weekend checklist (dependency order, not priority order)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Decide who appears in history.&lt;/strong&gt; Give each agent its own &lt;code&gt;GIT_AUTHOR_*&lt;/code&gt; / &lt;code&gt;GIT_COMMITTER_*&lt;/code&gt; and a resolvable email. Ten minutes, immediate payoff: &lt;code&gt;git log&lt;/code&gt; becomes readable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the platform agree.&lt;/strong&gt; One GitHub App per agent (manifest flow), key in a vault, installation tokens only, "Only select repositories".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Put attribution inside the commit.&lt;/strong&gt; Either an engine that rewrites trailers (Obi) or one-commit-per-action with a distinct author (Orquesta). Do not rely on the agent to remember.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Narrow what the process can read.&lt;/strong&gt; Per-agent config dirs and env files (mode 0600), header-injected or brokered credentials, nothing sensitive in a shared &lt;code&gt;.env&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raise required reviews last.&lt;/strong&gt; After step 2, so a &lt;em&gt;different&lt;/em&gt; identity can approve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write guardrails, not hopes.&lt;/strong&gt; Incident date in &lt;code&gt;AGENTS.md&lt;/code&gt;, every fix paired with a test that fails if it regresses, and "identity is attribution, not enforcement" as a stated design assumption.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  If it's just you and three agents
&lt;/h2&gt;

&lt;p&gt;The sources above are all team-scale. A lighter ladder:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Where you are&lt;/th&gt;
&lt;th&gt;Smallest useful move&lt;/th&gt;
&lt;th&gt;Why it's enough&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Three agents on one &lt;code&gt;user.name&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;per-agent &lt;code&gt;GIT_AUTHOR_*&lt;/code&gt; / &lt;code&gt;GIT_COMMITTER_*&lt;/code&gt; at launch&lt;/td&gt;
&lt;td&gt;One hour to a readable &lt;code&gt;git log&lt;/code&gt;, zero platform changes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want them to review each other&lt;/td&gt;
&lt;td&gt;trailers + branch prefixes (&lt;code&gt;agent/&amp;lt;name&amp;gt;/&amp;lt;topic&amp;gt;&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Without platform identity, "peer review" is still human review; first make authorship legible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You want branch protection to actually bite&lt;/td&gt;
&lt;td&gt;then GitHub Apps (or at least separate accounts)&lt;/td&gt;
&lt;td&gt;"The platform accepts these are two identities" is the one thing env vars can't buy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;You hold sensitive credentials&lt;/td&gt;
&lt;td&gt;one config dir + one env file (0600) per agent&lt;/td&gt;
&lt;td&gt;Keeps a key inside one process tree; the rest can't misuse it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A wrapper is enough to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# ~/bin/agent-run — usage: agent-run wren claude&lt;/span&gt;
agent-run&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;shift
  export &lt;/span&gt;&lt;span class="nv"&gt;GIT_AUTHOR_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIT_AUTHOR_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;@agents.local"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIT_COMMITTER_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GIT_COMMITTER_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;@agents.local"&lt;/span&gt;
  &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PI_CODING_AGENT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.pi/agents/&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;   &lt;span class="c"&gt;# swap for your tool's config-dir variable&lt;/span&gt;
  &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$@&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And one check that fails loudly when you got it wrong:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git log &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'%an &amp;lt;%ae&amp;gt; | %cn &amp;lt;%ce&amp;gt;'&lt;/span&gt;   &lt;span class="c"&gt;# expect the agent name, not yours&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Boundaries
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;This is a compilation, not a reproduction. The App-approval measurement, the trailer rewriting, the git-native flow, and the nine-agent box all come from the authors' own environments.&lt;/li&gt;
&lt;li&gt;Platform behaviour changes; "does an App approval count" and "can an App be a code owner" are both in the "not in the docs, measure it" category. Re-verify on a canary repo before relying on any number here.&lt;/li&gt;
&lt;li&gt;Naming (&lt;code&gt;vitruvian-*&lt;/code&gt;, &lt;code&gt;Oby-*&lt;/code&gt;) is incidental. What transfers is the structure: &lt;em&gt;who writes attribution, where it is stored, and which layer holds the credential.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Adjacent but different: sandboxing, egress limits, approval gates. Those answer "what can it do". This post answers "who does it count as".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Vitruvian Software, &lt;em&gt;Every AI agent gets its own GitHub identity&lt;/em&gt;, 2026-10-09 — &lt;a href="https://vitruviansoftware.dev/blog/2026/10/09/every-ai-agent-gets-its-own-github-identity/" rel="noopener noreferrer"&gt;https://vitruviansoftware.dev/blog/2026/10/09/every-ai-agent-gets-its-own-github-identity/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Obi (&lt;code&gt;obi-ai-bot/oby&lt;/code&gt;) issue #1068, &lt;em&gt;obid: commits say which job, agent and model wrote them&lt;/em&gt;, 2026-10-22 — &lt;a href="https://github.com/obi-ai-bot/oby/issues/1068" rel="noopener noreferrer"&gt;https://github.com/obi-ai-bot/oby/issues/1068&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Orquesta, &lt;em&gt;Git-Native AI Development: Every Action is a Commit&lt;/em&gt;, 2026-10-23 — &lt;a href="https://getorquesta.com/blog/git-native-ai-development-every-action-is-a-commit-2026-10-23" rel="noopener noreferrer"&gt;https://getorquesta.com/blog/git-native-ai-development-every-action-is-a-commit-2026-10-23&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;William M. Mongan, &lt;em&gt;Herding Agents: Multi-Agent Coordination with Herdr and Per-Agent MCP Credentials&lt;/em&gt;, 2026-10-04 — &lt;a href="https://www.billmongan.com/posts/2026/10/herdr-multiagent-mcpproxy/" rel="noopener noreferrer"&gt;https://www.billmongan.com/posts/2026/10/herdr-multiagent-mcpproxy/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Safeguard Security Team, &lt;em&gt;What are the identity risks when an AI coding agent runs as you&lt;/em&gt;, 2026-10-18 — &lt;a href="https://safeguard.sh/resources/blog/when-your-ai-coding-agent-runs-as-you-identity-risks-and-fixes" rel="noopener noreferrer"&gt;https://safeguard.sh/resources/blog/when-your-ai-coding-agent-runs-as-you-identity-risks-and-fixes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Background: Sumant Thakur, &lt;em&gt;I Ran Multiple Coding Agents. The Code Was the Easy Part.&lt;/em&gt;, 2026-09-30 — &lt;a href="https://sumantthakur.substack.com/p/multi-agent-coding-workflow" rel="noopener noreferrer"&gt;https://sumantthakur.substack.com/p/multi-agent-coding-workflow&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Chinese original (full version, with the platform-specific details): &lt;a href="https://www.hubwiz.com/blog/multiple-coding-agents-identity-git-attribution/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=multiple-coding-agents-identity" rel="noopener noreferrer"&gt;多 Agent 同机：Git 作者、凭据与提交归属怎么分&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;More on this site (in Chinese): &lt;a href="https://www.hubwiz.com/blog/vibe-coding-stack/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=multiple-coding-agents-identity" rel="noopener noreferrer"&gt;AI 编程 / Vibe Coding 完全指南&lt;/a&gt; · &lt;a href="https://www.hubwiz.com/blog/agent-stack/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=multiple-coding-agents-identity" rel="noopener noreferrer"&gt;Agent / MCP / 自动化完全指南&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>git</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>113 Siemens Ladder Instructions, One Page Each — and the Sections That Actually Save You Time</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Thu, 22 Oct 2026 08:42:11 +0000</pubDate>
      <link>https://dev.to/tiaforge/113-siemens-ladder-instructions-one-page-each-and-the-sections-that-actually-save-you-time-2pog</link>
      <guid>https://dev.to/tiaforge/113-siemens-ladder-instructions-one-page-each-and-the-sections-that-actually-save-you-time-2pog</guid>
      <description>&lt;p&gt;&lt;strong&gt;When you look up a ladder instruction mid-commissioning, the expensive part is never remembering what the symbol looks like.&lt;/strong&gt; It's the next two questions: &lt;em&gt;how is this different from the instruction next to it&lt;/em&gt;, and &lt;em&gt;what happens if I pick the wrong one&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://tia.hubwiz.com/learn-lad/instructions/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;Learn-LAD's LAD instruction handbook&lt;/a&gt; answers exactly those. It's &lt;strong&gt;113 S7-1200/1500 instructions in 13 groups, one page each&lt;/strong&gt;, with a fixed seven-section structure per page: one-sentence summary, symbol and pins, minimal example, how it works (truth/timing table), typical uses, common pitfalls, related examples. Each page also carries an "try it in TIA" panel: pick an example block, and &lt;a href="https://tia.hubwiz.com/tia-link?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;TiaLink&lt;/a&gt; imports it into the TIA Portal project you currently have open — pre-check first, no overwriting of same-named objects by default, no auto-save after import.&lt;/p&gt;

&lt;p&gt;It is not a replacement for the TIA help. The help tells you &lt;em&gt;what a pin is&lt;/em&gt;; this tells you &lt;em&gt;which instruction to pick, and how it fails&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where people actually get stuck
&lt;/h2&gt;

&lt;p&gt;Everyone learns contacts, coils and timers once. The real problems show up later, and they cluster into four shapes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Neighbours look identical.&lt;/strong&gt; &lt;code&gt;TON&lt;/code&gt; / &lt;code&gt;TOF&lt;/code&gt; / &lt;code&gt;TP&lt;/code&gt; are all "timers". &lt;code&gt;SCoil&lt;/code&gt; and &lt;code&gt;Sr&lt;/code&gt; both latch. The failure always happens between two instructions that resemble each other.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You know the definition, not the consequence.&lt;/strong&gt; "&lt;code&gt;ET&lt;/code&gt; resets when &lt;code&gt;IN&lt;/code&gt; goes to 0" reads as trivia — until you use it as an accumulated run-time counter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parameter help stops at parameters.&lt;/strong&gt; TIA help will tell you &lt;code&gt;PT&lt;/code&gt; is the preset time. It won't warn you that a one-scan pulse never reaches &lt;code&gt;PT&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You understand the page, then rebuild the network by hand&lt;/strong&gt; — and introduce new mistakes in the rebuild.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the useful test for any instruction reference is five criteria:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Criterion&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;th&gt;What the handbook does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pin table with directions&lt;/td&gt;
&lt;td&gt;A flow input (RLO) is not a value input&lt;/td&gt;
&lt;td&gt;Every page has one, labelled flow input / input / flow output / output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A minimal example&lt;/td&gt;
&lt;td&gt;Definitions don't stick; a three-rung network does&lt;/td&gt;
&lt;td&gt;Every page has a rendered ladder plus variable notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Truth or timing table&lt;/td&gt;
&lt;td&gt;"When is it 1" must be derivable scan by scan&lt;/td&gt;
&lt;td&gt;Timers, counters and edges get state tables&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"How it differs from its neighbour"&lt;/td&gt;
&lt;td&gt;Misuse happens between lookalikes&lt;/td&gt;
&lt;td&gt;A fixed "common pitfalls" section; its first line is usually "X is not Y"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Context&lt;/td&gt;
&lt;td&gt;A single instruction is meaningless outside a program&lt;/td&gt;
&lt;td&gt;Related examples graded basic/advanced/full, plus related instructions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  2. Scale: 113 instructions, 13 groups
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Group&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;th&gt;Representative instructions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Bit logic&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Contact, Coil, SCoil, RCoil, Not, SBitfield, RBitfield&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flip-flops&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Sr (set-dominant), Rs (reset-dominant)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Edge&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;PContact, NContact, PBox, NBox, PCoil, NCoil, R_TRIG, F_TRIG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timers&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;TON, TOF, TP, TONR, CoilTON, CoilTOF, CoilTP, CoilTONR, PtCoil, ResetIECTimerCoil&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Counters&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;CTU, CTD, CTUD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Comparison&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Eq, Lt, Gt, Le, Ge, Ne, InRange, OutRange, OK, NOK, IS_ARRAY, IS_NULL, NOT_NULL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Word logic&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;SEL, And, Or, Xor, Inv, Mux, Demux, DECO, ENCO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;LEN, CONCAT, LEFT, RIGHT, MID, FIND, INSERT, DELETE, REPLACE, S_Move&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shift/rotate&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Shl, Shr, Rl, Rr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Math&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;Add, Sub, Mul, Div, Mod, LIMIT, MIN, MAX, Abs, Inc, Dec, trig/log/rounding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Move&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Move, Swap, MoveBlockI/U, FillBlockI/U, Lower_Bound, Upper_Bound, GATHER, SCATTER, Serialize, Deserialize&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conversion&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Convert, Scale_X, Normalize, Round, Trunc, Ceil, Floor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Program control&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Jump, JumpNot, Switch, JmpList&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three editorial choices say a lot about the intent:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;CoilTON&lt;/code&gt; / &lt;code&gt;CoilTOF&lt;/code&gt; / &lt;code&gt;CoilTP&lt;/code&gt; / &lt;code&gt;CoilTONR&lt;/code&gt; get their own pages.&lt;/strong&gt; Coil-form timers and box-form timers differ in syntax &lt;em&gt;and&lt;/em&gt; in where they may be placed; mixing them is a standard commissioning incident.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;OK&lt;/code&gt; / &lt;code&gt;NOK&lt;/code&gt; / &lt;code&gt;IS_ARRAY&lt;/code&gt; / &lt;code&gt;IS_NULL&lt;/code&gt; / &lt;code&gt;NOT_NULL&lt;/code&gt; are filed under comparison.&lt;/strong&gt; These aren't comparisons of magnitude; they're validity checks, and you cannot write reusable FBs without them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Only four program-control instructions.&lt;/strong&gt; No attempt to mirror the whole instruction set — just the jump forms ladder actually uses.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every page header also carries &lt;strong&gt;applicable CPUs (S7-1200/1500), the version basis (TIA V19), and aliases&lt;/strong&gt; — &lt;code&gt;TON&lt;/code&gt; is alias-mapped to "on-delay timer / SD", &lt;code&gt;TP&lt;/code&gt; to "pulse timer / SP". The alias line matters when you're reading old drawings: the sheet says &lt;code&gt;SD&lt;/code&gt;, TIA says &lt;code&gt;TON&lt;/code&gt;, same thing.&lt;/p&gt;

&lt;p&gt;One implementation detail worth knowing: pin names and directions come from the &lt;strong&gt;&lt;code&gt;lad-ir&lt;/code&gt; instruction catalogue&lt;/strong&gt;, and the figures are rendered by &lt;strong&gt;&lt;code&gt;lad-render&lt;/code&gt;&lt;/strong&gt; — the same renderer used by the example library, so a handbook figure and an example figure look identical side by side.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What one page contains (TON as the example)
&lt;/h2&gt;

&lt;p&gt;Open &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/ton/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;TON — on-delay timer&lt;/a&gt;. Top to bottom, seven sections. The order is itself a reading strategy: &lt;strong&gt;usable first, mechanism second, boundary last.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1) One sentence.&lt;/strong&gt; "&lt;code&gt;TON&lt;/code&gt; is an on-delay timer: &lt;code&gt;Q&lt;/code&gt; goes to 1 only after &lt;code&gt;IN&lt;/code&gt; has been continuously 1 for &lt;code&gt;PT&lt;/code&gt;; as soon as &lt;code&gt;IN&lt;/code&gt; drops, &lt;code&gt;Q&lt;/code&gt; and &lt;code&gt;ET&lt;/code&gt; reset." Trigger condition, decision condition, reset condition — drop any one of the three and the instruction gets misused.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2) Symbol and pins.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pin&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;IN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;flow input&lt;/td&gt;
&lt;td&gt;Enable; starts timing while 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;input&lt;/td&gt;
&lt;td&gt;Preset time (Time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Q&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;flow output&lt;/td&gt;
&lt;td&gt;1 after elapsed; resets when &lt;code&gt;IN&lt;/code&gt; drops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;output&lt;/td&gt;
&lt;td&gt;Elapsed time; returns to 0 when &lt;code&gt;IN&lt;/code&gt; is 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Plus three metadata lines that decide whether it can live in an FC at all: &lt;strong&gt;group: timers&lt;/strong&gt;, &lt;strong&gt;needs an instance&lt;/strong&gt;, &lt;strong&gt;template value: time type&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3) Minimal example.&lt;/strong&gt; &lt;code&gt;Start&lt;/code&gt; held for 5 s, then &lt;code&gt;Motor&lt;/code&gt; is set; &lt;code&gt;Start&lt;/code&gt; dropping resets &lt;code&gt;Motor&lt;/code&gt; immediately. The example uses a static multi-instance &lt;code&gt;Timer&lt;/code&gt; inside an FB; in an OB/FC you can use a global IEC timer instance DB instead. The figure notes that &lt;code&gt;#&lt;/code&gt; means a block-local variable, and that real projects may use global tags or DB variables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4) How it works (state table).&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;code&gt;IN&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;Elapsed&lt;/th&gt;
&lt;th&gt;&lt;code&gt;Q&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;ET&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&amp;lt; &lt;code&gt;PT&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;increments from 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;≥ &lt;code&gt;PT&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;stops at &lt;code&gt;PT&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1 → 0&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;back to 0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Four notes follow, and each one is a pitfall in disguise: &lt;code&gt;IN&lt;/code&gt; must be &lt;em&gt;continuous&lt;/em&gt;; &lt;code&gt;PT&lt;/code&gt; versus &lt;code&gt;ET&lt;/code&gt;; &lt;code&gt;ET&lt;/code&gt; stops growing once the time is reached; never call the same instance from multiple places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5) Typical uses.&lt;/strong&gt; Delay-on start (3–5 s confirmation before energising a motor); debounce / in-position confirmation (drop &lt;code&gt;PT&lt;/code&gt; to 200–500 ms and use &lt;code&gt;Q&lt;/code&gt; as "signal is stable"); timeout alarm (start timing on the command, alarm if "done" never arrives). That third one adds the premise most people miss: &lt;strong&gt;you usually need a &lt;code&gt;NOT Done&lt;/code&gt; condition in series&lt;/strong&gt;, and if &lt;code&gt;Start&lt;/code&gt; is a momentary button you must first latch it with &lt;code&gt;SCoil&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6) Common pitfalls.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TON is not TP&lt;/strong&gt;: &lt;code&gt;TON&lt;/code&gt; holds &lt;code&gt;Q&lt;/code&gt; until &lt;code&gt;IN&lt;/code&gt; drops; &lt;code&gt;TP&lt;/code&gt; is a fixed pulse that &lt;code&gt;IN&lt;/code&gt; cannot cut short.&lt;br&gt;
&lt;strong&gt;TON is not TOF&lt;/strong&gt;: &lt;code&gt;TOF&lt;/code&gt; is off-delay; &lt;code&gt;Q&lt;/code&gt; stays 1 for &lt;code&gt;PT&lt;/code&gt; &lt;em&gt;after&lt;/em&gt; &lt;code&gt;IN&lt;/code&gt; drops.&lt;br&gt;
&lt;strong&gt;A short pulse never reaches &lt;code&gt;TON&lt;/code&gt;&lt;/strong&gt;: a one-scan pulse never gets to &lt;code&gt;PT&lt;/code&gt;.&lt;br&gt;
&lt;strong&gt;&lt;code&gt;ET&lt;/code&gt; resets&lt;/strong&gt;: with &lt;code&gt;IN = 0&lt;/code&gt;, &lt;code&gt;ET&lt;/code&gt; goes back to 0 — don't use it as an accumulator.&lt;br&gt;
&lt;strong&gt;Instance conflicts&lt;/strong&gt;: calling the same instance from multiple networks overwrites the timing.&lt;br&gt;
&lt;strong&gt;Missing &lt;code&gt;PT&lt;/code&gt;&lt;/strong&gt;: TIA reports an unused parameter; SCL calls like &lt;code&gt;TON(IN:=...)&lt;/code&gt; lose it easily.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Five of the six describe &lt;em&gt;failure shapes&lt;/em&gt;, not definitions. That's where the information density of this handbook actually lives — it reads like it was written from observed mistakes, not from a parameter table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7) Related examples and related instructions.&lt;/strong&gt; Two graded examples (delayed motor start with pump control; three-motor staggered start/stop), each with a one-line summary and a difficulty tag, plus two lateral links (&lt;code&gt;SCoil&lt;/code&gt;, &lt;code&gt;TOF&lt;/code&gt;) — "the next thing you will confuse this with".&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bonus: the "try it in TIA" panel.&lt;/strong&gt; This is the difference from a normal documentation site. A block selector offers variants (&lt;code&gt;TON&lt;/code&gt; has minimal example / signal debounce / timeout alarm). Importing then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;goes through &lt;strong&gt;TiaLink into the project you currently have open&lt;/strong&gt;, with &lt;strong&gt;pre-check, no overwrite of same-named objects by default, and no auto-save&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;does &lt;strong&gt;not&lt;/strong&gt; run the block: you still call it from &lt;code&gt;OB1&lt;/code&gt; and wire real variables yourself;&lt;/li&gt;
&lt;li&gt;uses &lt;code&gt;http://localhost:8080&lt;/code&gt; by default, or &lt;code&gt;?tialink=&lt;/code&gt; appended to the page URL;&lt;/li&gt;
&lt;li&gt;accepts the &lt;code&gt;--api-token&lt;/code&gt; value if TiaLink was started with one — stored only in the browser's localStorage, never uploaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"No overwrite" plus "no auto-save" keeps the risk on the reversible side: the import puts blocks into the project; whether they stay is still your decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Stress test: the three families people confuse
&lt;/h2&gt;

&lt;h3&gt;
  
  
  4.1 The timer trio — TON / TOF / TP
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;TON&lt;/code&gt; on-delay&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;TOF&lt;/code&gt; off-delay&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;TP&lt;/code&gt; pulse&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When timing starts&lt;/td&gt;
&lt;td&gt;while &lt;code&gt;IN&lt;/code&gt; is 1&lt;/td&gt;
&lt;td&gt;when &lt;code&gt;IN&lt;/code&gt; goes 1 → 0&lt;/td&gt;
&lt;td&gt;on the rising edge of &lt;code&gt;IN&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;When &lt;code&gt;Q&lt;/code&gt; turns on&lt;/td&gt;
&lt;td&gt;after &lt;code&gt;PT&lt;/code&gt; with &lt;code&gt;IN&lt;/code&gt; still 1&lt;/td&gt;
&lt;td&gt;immediately, while &lt;code&gt;IN&lt;/code&gt; is 1&lt;/td&gt;
&lt;td&gt;for a fixed &lt;code&gt;PT&lt;/code&gt; after the edge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;IN&lt;/code&gt; drops mid-way&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Q&lt;/code&gt; and &lt;code&gt;ET&lt;/code&gt; reset instantly&lt;/td&gt;
&lt;td&gt;this is when timing begins&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;does not cut the pulse&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A one-scan pulse&lt;/td&gt;
&lt;td&gt;never reaches &lt;code&gt;PT&lt;/code&gt;; &lt;code&gt;Q&lt;/code&gt; never turns on&lt;/td&gt;
&lt;td&gt;is stretched to the full &lt;code&gt;PT&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;is the normal operating mode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical use&lt;/td&gt;
&lt;td&gt;start delay, debounce, timeout alarm&lt;/td&gt;
&lt;td&gt;fan run-on, conveyor clearing&lt;/td&gt;
&lt;td&gt;fixed-width valve pulse, start pulse&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two pitfalls are mirror images of the same physical fact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;TON&lt;/code&gt;'s pitfall is &lt;strong&gt;"a short pulse never reaches &lt;code&gt;PT&lt;/code&gt;"&lt;/strong&gt; — so don't use it to catch pulses; use &lt;code&gt;TP&lt;/code&gt; or latch an edge.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;TOF&lt;/code&gt;'s pitfall is &lt;strong&gt;"a short pulse is stretched to the full &lt;code&gt;PT&lt;/code&gt;"&lt;/strong&gt; — so don't use it as a pulse filter; it amplifies one scan into the whole preset time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You only see that pairing when the two pages sit next to each other.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;TP&lt;/code&gt; also carries an honest footnote: &lt;strong&gt;a second rising edge during the pulse usually does not extend it, but confirm the behaviour on your specific CPU/firmware.&lt;/strong&gt; "I can't guarantee this in a document" is more useful than a confident wrong answer — it tells you to measure.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Bit logic — Coil / SCoil / RCoil / Sr / Rs
&lt;/h3&gt;

&lt;p&gt;Five ways to write a 1, five different semantics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instruction&lt;/th&gt;
&lt;th&gt;Form&lt;/th&gt;
&lt;th&gt;Behaviour&lt;/th&gt;
&lt;th&gt;How it resets&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Coil&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;coil&lt;/td&gt;
&lt;td&gt;follows RLO every scan&lt;/td&gt;
&lt;td&gt;automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SCoil&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;coil&lt;/td&gt;
&lt;td&gt;sets and &lt;strong&gt;holds&lt;/strong&gt; while RLO = 1&lt;/td&gt;
&lt;td&gt;only &lt;code&gt;RCoil&lt;/code&gt; or writing 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RCoil&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;coil&lt;/td&gt;
&lt;td&gt;resets to 0 while RLO = 1&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Sr&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;box&lt;/td&gt;
&lt;td&gt;set-dominant latch&lt;/td&gt;
&lt;td&gt;via the &lt;code&gt;r1&lt;/code&gt; input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Rs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;box&lt;/td&gt;
&lt;td&gt;reset-dominant latch&lt;/td&gt;
&lt;td&gt;reset wins when both are 1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;Sr&lt;/code&gt;'s pitfall list is entirely semantic, and every item is a real defect: &lt;strong&gt;&lt;code&gt;operand&lt;/code&gt; cannot be an Output&lt;/strong&gt; (TIA does not allow reading outputs — use InOut or Static); &lt;strong&gt;don't confuse it with &lt;code&gt;SCoil&lt;/code&gt;/&lt;code&gt;RCoil&lt;/code&gt;&lt;/strong&gt; (box versus coil); &lt;strong&gt;pick the dominance according to the process&lt;/strong&gt;; &lt;code&gt;q&lt;/code&gt; is an RLO output you can continue in series; and &lt;strong&gt;scan order&lt;/strong&gt; — when a latch bit is written from several places, the result depends on scan order.&lt;/p&gt;

&lt;p&gt;That last one is really the thesis of ladder programming: &lt;strong&gt;write the same bit from several networks and you don't own a value — you own whatever the last network to reach it wrote.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Edges — PContact / PBox / PCoil / R_TRIG
&lt;/h3&gt;

&lt;p&gt;Edge instructions are the source of two production classics: the missed pulse and the double count. Eight pages split them by &lt;em&gt;what they watch&lt;/em&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instruction&lt;/th&gt;
&lt;th&gt;Watches the edge of&lt;/th&gt;
&lt;th&gt;Instance needed&lt;/th&gt;
&lt;th&gt;Output duration&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PContact&lt;/code&gt; / &lt;code&gt;NContact&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;an &lt;strong&gt;operand&lt;/strong&gt; (0→1 / 1→0)&lt;/td&gt;
&lt;td&gt;no, but needs an edge memory bit&lt;/td&gt;
&lt;td&gt;one scan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PBox&lt;/code&gt; / &lt;code&gt;NBox&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;the &lt;strong&gt;RLO on the left&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;no, edge memory bit&lt;/td&gt;
&lt;td&gt;one scan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;PCoil&lt;/code&gt; / &lt;code&gt;NCoil&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;RLO edge, writes an operand&lt;/td&gt;
&lt;td&gt;no, edge memory bit&lt;/td&gt;
&lt;td&gt;one scan&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;R_TRIG&lt;/code&gt; / &lt;code&gt;F_TRIG&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;&lt;code&gt;CLK&lt;/code&gt;&lt;/strong&gt;, &lt;code&gt;Q&lt;/code&gt; pulses&lt;/td&gt;
&lt;td&gt;yes, an instance&lt;/td&gt;
&lt;td&gt;one scan&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;PContact&lt;/code&gt; explains the mechanism: the &lt;code&gt;bit&lt;/code&gt; is edge memory that stores the operand's previous state, and &lt;strong&gt;each edge contact needs its own &lt;code&gt;bit&lt;/code&gt;&lt;/strong&gt;; &lt;code&gt;out = 1&lt;/code&gt; only in the scan where the operand goes 0 → 1, and &lt;code&gt;bit&lt;/code&gt; updates at the end of the scan.&lt;/p&gt;

&lt;p&gt;Its pitfalls double as a troubleshooting checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No preceding logic&lt;/strong&gt;: putting &lt;code&gt;PContact&lt;/code&gt; first in a rung violates TIA's edge rules — place an &lt;code&gt;Enable&lt;/code&gt; / &lt;code&gt;Not&lt;/code&gt; / contact before it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared edge bits&lt;/strong&gt;: two &lt;code&gt;PContact&lt;/code&gt; sharing one &lt;code&gt;bit&lt;/code&gt; overwrite each other and drop pulses.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-retentive edge bits&lt;/strong&gt;: a &lt;code&gt;bit&lt;/code&gt; in Temp is reset on every call — use Static, a global &lt;code&gt;M&lt;/code&gt;, or a DB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Used as a level&lt;/strong&gt;: it is high for exactly one scan; if you need persistence, follow it with &lt;code&gt;SCoil&lt;/code&gt; or a latch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skipped networks&lt;/strong&gt;: if a &lt;code&gt;Jump&lt;/code&gt; skips the network, the edge isn't recorded — and when the scan returns, the edge may be misinterpreted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is rarely written down anywhere, yet it's the standard path by which "code that never executes" turns into "an occasional unexpected motion".&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Three ways to use it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Firefighting (the 30-second rule).&lt;/strong&gt; One sentence → pin table → pitfalls. Skip the rest. The one-sentence line tells you whether the semantic is the one you want, the pins confirm the wiring, the pitfalls confirm whether your current code &lt;em&gt;is&lt;/em&gt; the failure shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Studying a group (read the pitfalls as a comparison table).&lt;/strong&gt; A single page doesn't stick, but read all ten timer pages' pitfalls and you get a map of &lt;em&gt;when not to use which timer&lt;/em&gt;; read the eight edge pages and you get a leak checklist. That's more durable than memorising symbols.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feeding pages to an AI as a checklist.&lt;/strong&gt; This is the biggest change in how the work is done now. When an AI generates or modifies ladder/SCL, the errors concentrate in four places: &lt;strong&gt;wrong timer family, shared edge bits, double coils, instance conflicts&lt;/strong&gt; — and the pitfalls section of each page is a ready-made checklist. Practically: paste the relevant page (summary + pins + mechanism + pitfalls) as context and ask the model to check the code against it, instead of generating from memory.&lt;/p&gt;

&lt;p&gt;Two companion pieces in the same series: &lt;a href="https://www.hubwiz.com/blog/ai-plc-programming-7-lessons-verification/" rel="noopener noreferrer"&gt;7 lessons from AI PLC programming&lt;/a&gt; (the four verification layers — structural validation → TIA compile → cloud simulation → virtual PLC/field — and what each layer does &lt;em&gt;not&lt;/em&gt; prove) and &lt;a href="https://www.hubwiz.com/blog/siemens-plc-ladder-example-library/" rel="noopener noreferrer"&gt;the Siemens ladder example library&lt;/a&gt; (how 60+ examples are arranged into a practice path). Both are in Chinese.&lt;/p&gt;

&lt;p&gt;⚠️ Keep the handbook's own disclaimer in view: &lt;strong&gt;instruction pages cover semantics and usage only; they do not replace the TIA help or a process/safety review. Safety-related conclusions require human confirmation.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. From "found it" to "ran it" in four steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Look it up&lt;/strong&gt; — confirm semantics and pitfalls on the instruction page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import&lt;/strong&gt; — use the "try it in TIA" panel to pull an example block (e.g. &lt;code&gt;TON&lt;/code&gt; → &lt;em&gt;timeout alarm&lt;/em&gt;) into your open project via TiaLink: pre-check, no overwrite, no auto-save.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wire it&lt;/strong&gt; — call the block from &lt;code&gt;OB1&lt;/code&gt; and connect real variables. It does not run by itself; this step &lt;em&gt;is&lt;/em&gt; the pin comprehension.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prove it&lt;/strong&gt; — run the timing in cloud simulation, then watch IO behaviour on a virtual PLC. For the layer split, see &lt;a href="https://www.hubwiz.com/blog/ai-plc-programming-7-lessons-verification/" rel="noopener noreferrer"&gt;7 lessons&lt;/a&gt;; for practice without hardware, see &lt;a href="https://www.hubwiz.com/blog/virtual-plc-lab-factory-io-openplc-modbus-tcp/" rel="noopener noreferrer"&gt;Factory I/O + OpenPLC + Modbus TCP&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Compared to pasting a snippet into your project, only step 3 is extra work — and it's what buys you the knowledge of &lt;em&gt;which scan cycle each line runs in&lt;/em&gt;. The classic counter-example: during a refactor &lt;code&gt;TP&lt;/code&gt; was written as &lt;code&gt;TON&lt;/code&gt;; compile reported 0 errors and 0 warnings; pressing the button started nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. What it deliberately doesn't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't replace the TIA help.&lt;/strong&gt; Semantics, examples and pitfalls live here; firmware and CPU specifics stay with the official documentation and your own measurements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It doesn't replace a process review.&lt;/strong&gt; Especially for safety (e-stops, safety doors, F-CPUs and F-blocks) — the pages state plainly that safety conclusions require human confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The scope is S7-1200/1500 on TIA V19.&lt;/strong&gt; S7-300/400-era S5 timers (&lt;code&gt;S_ODT&lt;/code&gt; and friends) are a different semantic family; don't transplant them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown behaviour is labelled unknown.&lt;/strong&gt; Where a cross-version detail can't be guaranteed (e.g. &lt;code&gt;TP&lt;/code&gt; retriggering mid-pulse), the page says "confirm on your CPU" instead of inventing an answer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Examples are minimal readable shapes, not production code.&lt;/strong&gt; No process interlocks, no fault handling — add them yourself and review them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Import is not adoption.&lt;/strong&gt; No overwrite, no auto-save: the decision stays on your side. That's protection and responsibility at the same time.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Sources and verification notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Handbook index (113 instructions, 13 groups): &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;tia.hubwiz.com/learn-lad/instructions/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Instruction pages quoted: &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/ton/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;TON&lt;/a&gt;, &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/tof/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;TOF&lt;/a&gt;, &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/tp/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;TP&lt;/a&gt;, &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/ctu/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;CTU&lt;/a&gt;, &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/scoil/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;SCoil&lt;/a&gt;, &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/sr/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;Sr&lt;/a&gt;, &lt;a href="https://tia.hubwiz.com/learn-lad/instructions/pcontact/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=lad-instruction-handbook-siemens" rel="noopener noreferrer"&gt;PContact&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Page structure, counts, pin tables, pitfalls and import behaviour were read from the live pages on 2026-10-22. Nothing here was independently re-measured on hardware; instruction semantics remain governed by the TIA help and the process/safety review.&lt;/li&gt;
&lt;li&gt;Cover photo: Pexels #257736 (Pexels License).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further reading (in Chinese)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.hubwiz.com/blog/ai-industrial-stack/" rel="noopener noreferrer"&gt;AI × Industrial / PLC guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hubwiz.com/blog/siemens-plc-ladder-example-library/" rel="noopener noreferrer"&gt;Siemens ladder example library: 60+ importable exercises&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hubwiz.com/blog/ai-plc-programming-7-lessons-verification/" rel="noopener noreferrer"&gt;7 lessons from AI PLC programming, each with an acceptance check&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hubwiz.com/blog/lad-to-scl-migration-guide-tia-portal/" rel="noopener noreferrer"&gt;Ladder to SCL migration guide for TIA Portal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hubwiz.com/blog/ai-plc-program-review-lesson/" rel="noopener noreferrer"&gt;Pre-commissioning program review with an AI agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.hubwiz.com/blog/virtual-plc-lab-factory-io-openplc-modbus-tcp/" rel="noopener noreferrer"&gt;Practicing PLC without hardware: Factory I/O + OpenPLC&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article was originally published in Chinese on &lt;a href="https://www.hubwiz.com/blog/lad-instruction-handbook-siemens/" rel="noopener noreferrer"&gt;hubwiz.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>plc</category>
      <category>automation</category>
      <category>programming</category>
      <category>tooling</category>
    </item>
    <item>
      <title>image-blaster: the real cost of one image 3D world</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Wed, 21 Oct 2026 15:12:45 +0000</pubDate>
      <link>https://dev.to/tiaforge/image-blaster-the-real-cost-of-one-image-3d-world-1n4d</link>
      <guid>https://dev.to/tiaforge/image-blaster-the-real-cost-of-one-image-3d-world-1n4d</guid>
      <description>&lt;p&gt;&lt;strong&gt;One image in, a walkable 3D environment out in under five minutes.&lt;/strong&gt; That is the pitch on &lt;a href="https://github.com/neilsonnn/image-blaster" rel="noopener noreferrer"&gt;&lt;code&gt;neilsonnn/image-blaster&lt;/code&gt;&lt;/a&gt; — 11.8k stars, MIT, front page of Hacker News in May 2026. Built by a World Labs team member (not an official World Labs product), it chains &lt;strong&gt;Marble&lt;/strong&gt;, &lt;strong&gt;Hunyuan 3D&lt;/strong&gt;, &lt;strong&gt;nano-banana&lt;/strong&gt; and &lt;strong&gt;ElevenLabs&lt;/strong&gt; behind a Claude Code skill.&lt;/p&gt;

&lt;p&gt;Two questions decide whether you can use it, and the eight English write-ups I could find answer neither:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What does one run actually cost?&lt;/strong&gt; (Short answer: the world is fixed at ~$1.34; every dynamic object adds ~$0.46.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Can you drop the outputs into an engine?&lt;/strong&gt; (Short answer: not before you apply a scale correction the CLI never applies for you.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both answers are below, with code references and prices as of 2026-10-21.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually is
&lt;/h2&gt;

&lt;p&gt;Not an app — a &lt;strong&gt;skill pack for Claude Code&lt;/strong&gt;. You clone the repo, run &lt;code&gt;claude&lt;/code&gt; inside it, hand Claude two API keys (World Labs + FAL), drop an image into &lt;code&gt;input/&lt;/code&gt;, and say &lt;code&gt;blast it and confirm each step with me&lt;/code&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Repo&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;neilsonnn/image-blaster&lt;/code&gt;, MIT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timeline&lt;/td&gt;
&lt;td&gt;created 2026-04-21, last push 2026-05-15; 11,794 ★ (snapshot 2026-10-21)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Form&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.claude/skills/*&lt;/code&gt; + Node scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires&lt;/td&gt;
&lt;td&gt;Claude Code CLI + &lt;strong&gt;World Labs key&lt;/strong&gt; + &lt;strong&gt;FAL key&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Outputs&lt;/td&gt;
&lt;td&gt;dynamic objects &lt;code&gt;.glb&lt;/code&gt;/&lt;code&gt;.obj&lt;/code&gt;; static environment &lt;code&gt;.spz&lt;/code&gt;; SFX &lt;code&gt;.mp3&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The three outputs are &lt;strong&gt;not the same kind of thing&lt;/strong&gt;, and that is where every downstream problem starts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;What it is not&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Static environment&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.spz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a Gaussian-splat scene you can walk through&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;not a mesh&lt;/strong&gt; — no faces, no UVs; needs splat render support&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dynamic objects&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.glb&lt;/code&gt; / &lt;code&gt;.obj&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;one real mesh per object&lt;/td&gt;
&lt;td&gt;not a scene — you place them yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SFX&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.mp3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ambient loop + per-object impact&lt;/td&gt;
&lt;td&gt;not a mixed audio system&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pipeline, as it actually executes (read from the scripts, not the README):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input/ image
  ├─① clean plate / object cut-outs   fal-ai/nano-banana-2/edit          $0.08/img
  ├─② static environment             World Labs marble-1.1              1,500 credits
  │     └→ .spz (full_res + 500k + 100k + 150k downloaded)
  │     └→ collider mesh .glb (100–200k triangles)
  ├─③ each dynamic object            fal-ai/hunyuan-3d/v3.1/pro/...     $0.375/call
  │                                  (or .../rapid $0.225, fal-ai/meshy/v6 $0.80)
  └─④ SFX                            fal-ai/elevenlabs/sound-effects/v2 $0.002/sec
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details that matter and are easy to miss:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every object costs one image edit &lt;em&gt;before&lt;/em&gt; the 3D call.&lt;/strong&gt; The &lt;code&gt;image-blast-3d&lt;/code&gt; skill instructs Claude to first write a cut-out prompt ("Isolate the &lt;code&gt;&amp;lt;object&amp;gt;&lt;/code&gt; … white background, centered, tight crop"). So an object is &lt;em&gt;edit + 3D&lt;/em&gt;, not &lt;em&gt;3D&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SFX go through FAL&lt;/strong&gt;, not ElevenLabs directly (&lt;code&gt;fal-ai/elevenlabs/sound-effects/v2&lt;/code&gt;). If you price this off ElevenLabs credit tables you will be off by an order of magnitude. The skill's defaults: ambient &lt;code&gt;--loop --count 2 --duration-seconds 10&lt;/code&gt;, per object &lt;code&gt;--count 4 --duration-seconds 1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The bill
&lt;/h2&gt;

&lt;p&gt;Prices below are the vendors' own pages, checked 2026-10-21.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;Price&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;World Labs credits&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$1.00 = 1,250 credits&lt;/strong&gt;; minimum purchase $5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docs.worldlabs.ai/api/pricing&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;World generation &lt;code&gt;marble-1.1&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,500 credits = $1.20&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pano generation (non-pano image)&lt;/td&gt;
&lt;td&gt;+80 credits = &lt;strong&gt;$0.064&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HQ mesh export&lt;/td&gt;
&lt;td&gt;3,500 credits = &lt;strong&gt;$2.80&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;same (&lt;strong&gt;this pipeline never calls it&lt;/strong&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image edit &lt;code&gt;nano-banana-2/edit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;$0.08 / image&lt;/strong&gt; (2K/4K ×1.5)&lt;/td&gt;
&lt;td&gt;FAL model page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object 3D &lt;code&gt;hunyuan-3d/v3.1/pro&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.375 / generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FAL model page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object 3D &lt;code&gt;.../rapid&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.225 / generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FAL model page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Object 3D &lt;code&gt;meshy/v6&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.80 / generation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FAL model page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SFX &lt;code&gt;elevenlabs/sound-effects/v2&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$0.002 / second&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;FAL model page&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which reduces to a very simple structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fixed per run   = world 1.264 (1,500 + 80 credits) + one plate edit 0.08   ≈ $1.34
per object      = cut-out edit 0.08 + 3D 0.375 + impact SFX 4s × 0.002     ≈ $0.46
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scene&lt;/th&gt;
&lt;th&gt;Math&lt;/th&gt;
&lt;th&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 object&lt;/td&gt;
&lt;td&gt;1.34 + 0.46&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;≈ $1.81&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5 objects (one room)&lt;/td&gt;
&lt;td&gt;1.34 + 5 × 0.46&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;≈ $3.66&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 objects (street / level)&lt;/td&gt;
&lt;td&gt;1.34 + 20 × 0.46&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;≈ $10.60&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 objects on &lt;code&gt;rapid&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1.34 + 20 × (0.08 + 0.225 + 0.008)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;≈ $7.62&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 objects on &lt;code&gt;meshy v6&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;1.34 + 20 × (0.08 + 0.80 + 0.008)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;≈ $19.10&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three non-obvious consequences:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The world is the cheap part.&lt;/strong&gt; Marble renders an entire environment for $1.26; five small props cost twice that. Your budget is a function of &lt;em&gt;object count&lt;/em&gt;, not canvas size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The $2.80 HQ mesh export is not in the loop.&lt;/strong&gt; &lt;code&gt;generate-world.mjs&lt;/code&gt; only pulls &lt;code&gt;assets.mesh.collider_mesh_url&lt;/code&gt; — the 100–200k-triangle &lt;em&gt;collision&lt;/em&gt; mesh. If you want a static mesh you can actually look at, you have to go back to Marble and export the HQ version separately: ~600k triangles with textures (or ~1M with vertex colors), up to an hour, rate-limited to 4/hour, Pro plan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failures are billable.&lt;/strong&gt; Unhappy with face count? Re-run: another $0.375. Bad cut-out? Another $0.08. This is exactly why the skill's default posture is &lt;code&gt;confirm each step with me&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Scope: these are &lt;strong&gt;estimates derived from the pipeline structure and public unit prices&lt;/strong&gt;, not our own invoice. No retries, no plan differences (Free can generate worlds but cannot export; Standard covers splat/collider exports; Pro adds HQ mesh + commercial rights).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The part nobody writes about: the &lt;code&gt;.spz&lt;/code&gt; is not in metric space
&lt;/h2&gt;

&lt;p&gt;World Labs documents this in &lt;code&gt;api/rendering-spz&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Marble SPZ files are exported in the generated asset coordinate frame. When you render them outside Marble, use &lt;code&gt;assets.splats.semantics_metadata&lt;/code&gt; to convert Gaussian centers and Gaussian sizes into metric, ground-aligned coordinates.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metric_scale_factor&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;raw generated units → &lt;strong&gt;meters&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ground_plane_offset&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;puts the metric ground plane at &lt;strong&gt;y = 0&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;flip_y&lt;/code&gt; (optional)&lt;/td&gt;
&lt;td&gt;Y-axis orientation (SPZ defaults to RUB, Unreal is Z-up)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So: is it handled? I grepped the whole repo (2026-10-21, &lt;code&gt;main&lt;/code&gt;):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Location&lt;/th&gt;
&lt;th&gt;Reads &lt;code&gt;semantics_metadata&lt;/code&gt;?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;app/src/components/WorldViewer.tsx&lt;/code&gt; (bundled React viewer)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Yes&lt;/strong&gt; — &lt;code&gt;metric_scale_factor&lt;/code&gt;, &lt;code&gt;ground_plane_offset&lt;/code&gt;, &lt;code&gt;flip_y&lt;/code&gt;, defaults 1 / 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;.claude/scripts/**&lt;/code&gt; (the CLI pipeline that hands you the files)&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt; — downloads &lt;code&gt;spz_urls&lt;/code&gt; and &lt;code&gt;collider_mesh_url&lt;/code&gt;, nothing else&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which means: &lt;strong&gt;it looks right in the bundled viewer because the viewer applies the correction, and it is wrong in Unity / Unreal / Blender / three.js because the exported files don't carry it.&lt;/strong&gt; Symptoms: a door 30+ units tall, everything floating above or sunk into the floor, and props misaligned against the splat they were generated from.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// three.js: the loader reads the file, it does not fix the frame&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;world&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SPZLoader&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;loadAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;semantics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metric_scale_factor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setScalar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;world&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;semantics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ground_plane_offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the same factor to the collider mesh (&lt;em&gt;inference&lt;/em&gt; — the docs only spell this out for splats, but both come from the same generated frame).&lt;/p&gt;

&lt;h2&gt;
  
  
  What the outputs really are
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;th&gt;Official spec&lt;/th&gt;
&lt;th&gt;Who reads it natively&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Splat &lt;code&gt;.spz&lt;/code&gt; (full res)&lt;/td&gt;
&lt;td&gt;~2M splats&lt;/td&gt;
&lt;td&gt;three.js (&lt;code&gt;SPZLoader&lt;/code&gt; in addons); Unity / Unreal / Blender / Houdini need community splat plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Splat &lt;code&gt;.spz&lt;/code&gt; (low res)&lt;/td&gt;
&lt;td&gt;~500k / 100k / 150k&lt;/td&gt;
&lt;td&gt;same; lower res is what you actually want in-engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Splat &lt;code&gt;.ply&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;~2M / ~500k&lt;/td&gt;
&lt;td&gt;widest compatibility, much bigger — SPZ is roughly &lt;strong&gt;10× smaller&lt;/strong&gt; than the equivalent PLY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Collider &lt;code&gt;.glb&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;100–200k triangles&lt;/td&gt;
&lt;td&gt;any engine; it is a physics proxy, not a render mesh&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HQ &lt;code&gt;.glb&lt;/code&gt; (Pro)&lt;/td&gt;
&lt;td&gt;~600k textured / ~1M vertex-color&lt;/td&gt;
&lt;td&gt;any engine; &lt;strong&gt;not produced by this pipeline&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Ten-minute acceptance checklist
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;th&gt;If it fails&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Units are meters&lt;/td&gt;
&lt;td&gt;measure a known reference (2 m door)&lt;/td&gt;
&lt;td&gt;apply &lt;code&gt;metric_scale_factor&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Ground at y = 0&lt;/td&gt;
&lt;td&gt;splat floor on the zero plane&lt;/td&gt;
&lt;td&gt;subtract &lt;code&gt;ground_plane_offset&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Orientation&lt;/td&gt;
&lt;td&gt;upside down → &lt;code&gt;flip_y&lt;/code&gt;; rotated → world yaw&lt;/td&gt;
&lt;td&gt;flip / rotate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Splat and mesh share scale&lt;/td&gt;
&lt;td&gt;overlay the same chair twice&lt;/td&gt;
&lt;td&gt;use one factor for both&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Face count holds up close&lt;/td&gt;
&lt;td&gt;zoom to player distance&lt;/td&gt;
&lt;td&gt;re-run with &lt;code&gt;--face-count&lt;/code&gt; (billed again)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Collider ≠ render mesh&lt;/td&gt;
&lt;td&gt;100–200k tri file is the collider&lt;/td&gt;
&lt;td&gt;keep both, don't swap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;File size&lt;/td&gt;
&lt;td&gt;prefer low-res &lt;code&gt;.spz&lt;/code&gt; in-engine&lt;/td&gt;
&lt;td&gt;see the payload comparison link below&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Licensing&lt;/td&gt;
&lt;td&gt;code is MIT; &lt;strong&gt;assets are generated by third-party APIs&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Marble commercial rights sit on Pro; check each vendor&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Four things that will bite you
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Static/dynamic split errors → ghosting.&lt;/strong&gt; The splat already contains the chair; generate a mesh of the chair and now you have two. Wrong split = double image, and an extra object you didn't want costs $0.46.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The splat/mesh boundary leaks.&lt;/strong&gt; Splats have no geometry, cast no shadows, don't receive light like meshes. Contact shadows and occlusion at the seam won't match. &lt;em&gt;(Inference: add AO blob decals under meshes, or cover the contact band with mesh.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 50k default is the skill's, not the API's.&lt;/strong&gt; Hunyuan's own default is 500,000 faces. Background props are fine; hero objects look generated up close.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SFX are materials, not an audio system.&lt;/strong&gt; Two 10-second ambient loops, four 1-second impacts per object. No rolling, no sustained sounds, no material variation. Non-loop output gets trimmed and loudness-normalized by &lt;code&gt;ffmpeg&lt;/code&gt;; loop output is left raw for seamlessness.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  When not to use it
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;You want&lt;/th&gt;
&lt;th&gt;Why not&lt;/th&gt;
&lt;th&gt;Instead&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;One model, no scene&lt;/td&gt;
&lt;td&gt;the $1.34 world is wasted&lt;/td&gt;
&lt;td&gt;call the FAL model directly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Precise dimensions&lt;/td&gt;
&lt;td&gt;generation isn't dimension-driven&lt;/td&gt;
&lt;td&gt;parametric CAD workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Editable topology / clean UVs&lt;/td&gt;
&lt;td&gt;you get "scanned" topology&lt;/td&gt;
&lt;td&gt;rebuild from the form (Mesh2CAD route)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline / on-prem&lt;/td&gt;
&lt;td&gt;the whole chain is two cloud APIs&lt;/td&gt;
&lt;td&gt;local text-to-3D (e.g. TRELLIS.2 in-browser)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team doesn't use Claude Code&lt;/td&gt;
&lt;td&gt;it's a skill pack, not a GUI app&lt;/td&gt;
&lt;td&gt;drive the scripts yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clean commercial rights&lt;/td&gt;
&lt;td&gt;assets come from multiple vendors&lt;/td&gt;
&lt;td&gt;verify each license&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The one thing it is genuinely good at: &lt;strong&gt;you have an image — concept art, a film still, a photo of a room — and you need a prototype you can walk around in within five minutes&lt;/strong&gt; to align the space, test framing, or show a client. For that, it beats hand-building the scene by a wide margin.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Scope and sources: repo README, `.claude/skills/&lt;/em&gt;&lt;code&gt; and &lt;/code&gt;.claude/scripts/&lt;em&gt;&lt;code&gt; (2026-10-21, &lt;/code&gt;main&lt;code&gt;); World Labs docs (&lt;/code&gt;api/pricing&lt;code&gt;, &lt;/code&gt;marble/export/specs&lt;code&gt;, &lt;/code&gt;marble/export/gaussian-splat&lt;code&gt;, &lt;/code&gt;api/rendering-spz&lt;code&gt;); FAL model pages; Niantic &lt;/code&gt;spz&lt;code&gt; format notes and three.js &lt;/code&gt;SPZLoader`. **No end-to-end run was performed&lt;/em&gt;* — costs are structural estimates from public unit prices, and mesh/engine/license constraints come from documentation rather than our own tests. The author is a World Labs team member; this is their project, not an official World Labs product.*&lt;/p&gt;

&lt;p&gt;&lt;em&gt;More from us: &lt;a href="https://bimant.com/blog/image-blaster-image-to-3d-world-skill/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image-blaster" rel="noopener noreferrer"&gt;the full Chinese write-up with the same billing breakdown&lt;/a&gt; · &lt;a href="https://bimant.com/blog/ai-3d/?utm_source=devto&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image-blaster" rel="noopener noreferrer"&gt;AI × 3D guides on BimAnt&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>3d</category>
      <category>gamedev</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Compiling Clean Isn't Proof: Have AI Review a PLC Program Before You Commission It</title>
      <dc:creator>tia-forge</dc:creator>
      <pubDate>Wed, 21 Oct 2026 12:40:08 +0000</pubDate>
      <link>https://dev.to/tiaforge/compiling-clean-isnt-proof-have-ai-review-a-plc-program-before-you-commission-it-17fn</link>
      <guid>https://dev.to/tiaforge/compiling-clean-isnt-proof-have-ai-review-a-plc-program-before-you-commission-it-17fn</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: The most dangerous illusion when you inherit a TIA Portal program is "it compiles with 0 errors and 0 warnings, so it's fine." The compiler checks syntax and consistency — not cross-network logic bugs like "the infeed conveyor runs when no box is present." Lesson 5 of the free, text-based &lt;a href="https://tia.hubwiz.com/learn-lad/ai/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=ai-plc-program-review-lesson" rel="noopener noreferrer"&gt;PLC AI in Practice&lt;/a&gt; course series (&lt;a href="https://tia.hubwiz.com/learn-lad/ai/plc-program-review/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=ai-plc-program-review-lesson" rel="noopener noreferrer"&gt;Lesson 5 here&lt;/a&gt;, ~35 minutes) closes exactly that gap: it has AI perform a &lt;strong&gt;read-only&lt;/strong&gt; program review against a fixed checklist, and produce an evidence-backed, severity-graded, reproducible review report before commissioning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this lesson sits in the series
&lt;/h2&gt;

&lt;p&gt;"PLC AI in Practice" is a free, text-and-figure course for TIA Portal engineers. Its main thread breaks "daring to let AI touch a real project" into individually verifiable steps: understand a legacy project → assess change impact → write a block to a clean compile → get the program running in a virtual PLC. Lesson 5 is the last gate before commissioning: &lt;strong&gt;what in this program will go wrong, and what do you fix first?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The lesson deliberately separates "program review" from "code commentary." Asking an AI to review a PLC program with one sentence ("review this code please") gets you a pile of style notes — naming, comments, network splitting. All of that sounds reasonable, but not a single line of it &lt;em&gt;proves the machine will stop when it should&lt;/em&gt;. The course defines review strictly: &lt;strong&gt;read-only, checklist-driven, defects only if they come with a failure scenario&lt;/strong&gt;. Style preferences and candidates without failure scenarios are kept out of "findings."&lt;/p&gt;

&lt;h2&gt;
  
  
  The method: one task brief, four gates
&lt;/h2&gt;

&lt;p&gt;The core deliverable of the lesson isn't knowledge — it's a reusable &lt;strong&gt;review task brief&lt;/strong&gt; you can paste to an AI agent. Its structure is worth describing in its own right:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 1 — what to review.&lt;/strong&gt; A fixed 7-category checklist: structure &amp;amp; calls; state &amp;amp; retention (set/reset pairs, power-up recovery); timing &amp;amp; scan (double coils, edge storage, timer types); interlocks &amp;amp; safety; data &amp;amp; addressing (type/range, I/O addresses vs. hardware channels); maintainability; testability. The checklist stops the agent from reviewing only what it is comfortable with.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 2 — evidence.&lt;/strong&gt; Every candidate finding must carry seven elements: evidence (block + network + tag), the failure scenario (which inputs/timing/hardware state triggers it and what happens), a severity grade (P0 downtime/safety → P3 needs confirmation), confidence (confirmed / inferred / cannot confirm), a minimal-change fix suggestion, a verification method, and — critically — a &lt;strong&gt;counter-evidence pass&lt;/strong&gt;: is there another network, an external write, or a process requirement that makes this conclusion wrong? "No failure scenario, no findings section" is a hard constraint in the brief.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 3 — global view.&lt;/strong&gt; The lesson builds an &lt;strong&gt;output → writers table&lt;/strong&gt;: for every output, list every network and instruction that writes it. Multi-driver outputs, broken set/reset pairs, and scan-order issues are invisible if you look at one network at a time; you have to reconcile per output, globally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gate 4 — read-only.&lt;/strong&gt; During review, no object is modified and the project is not saved; the report ends with a read-only declaration. Review first, produce the report; fixing is a separate, later process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The course actually ran it: target and scoreboard
&lt;/h2&gt;

&lt;p&gt;This isn't a pure methodology lesson. It runs the review against the &lt;strong&gt;Sorting Boxes (Factory IO)&lt;/strong&gt; open-source example: an OB1 with 18 ladder networks plus one SCL communications FC, 31 tags, and 5 system instance DBs. The example deliberately preserves real defects from the original project — ideal review practice material.&lt;/p&gt;

&lt;p&gt;Per the lesson's own published results (I have not independently re-run them; treat the numbers as the course's):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2 × P1&lt;/strong&gt;: right-hand sorting doesn't track box type — with a left-type box, both chains can fire simultaneously; and the infeed conveyor is driven by three networks, so it runs when no box is present, and its "stop" doesn't hold;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 × P2&lt;/strong&gt;: the ejector gets a pulse exactly one scan period long (10 ms) — an actuator may never see it; plus a compiler warning "inputs or outputs used that do not exist in the configured hardware";&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 × P3&lt;/strong&gt;: no e-stop / safety logic inside the program — explicitly marked "cannot confirm": the original machine may have a hardwired safety circuit, and that's for electrical drawings and a safety review to decide;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1 false positive, eliminated by counter-evidence&lt;/strong&gt;: the exported SCL text contains concatenation artifacts that look like compile failures — but when TIA actually compiles the block, it's 0 errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last item is, to me, the most valuable part of the whole lesson. &lt;strong&gt;The biggest risk of AI-assisted review is not missing a defect; it's treating "looks like a problem" as "confirmed defect."&lt;/strong&gt; The lesson dedicates an entire step to counter-evidence: for every candidate, ask three questions — is there another network or external write that reverses the conclusion? Is the signal maintained or a pulse — does the failure scenario still hold? Is there a process requirement that makes this intentional? Three scary-looking candidates get downgraded or eliminated that way, and only the provable ones survive into the report.&lt;/p&gt;

&lt;p&gt;This is a general lesson for anyone using AI to review code (well beyond PLC): &lt;strong&gt;trust the compiler for compile results — never substitute text export for an actual compile, and always keep "confirmed" and "inferred" as separate labels.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling, simulation, and humans are not interchangeable
&lt;/h2&gt;

&lt;p&gt;The course hammers on the boundaries between three verification methods: &lt;strong&gt;compiling&lt;/strong&gt; catches syntax and consistency, but not logic errors; &lt;strong&gt;simulation / scan-by-scan reasoning&lt;/strong&gt; validates logical behavior, but not wiring, physical channels, or safety circuits; &lt;strong&gt;humans&lt;/strong&gt; own process intent, hardware configuration, and safety review. Every finding must state its verification method — none of the three may impersonate another.&lt;/p&gt;

&lt;p&gt;Safety is treated with particular rigor: the course's stance is "AI assists the review; conclusions must be human-verified." E-stop and safety functions must be confirmed by qualified personnel; the report is not a safety certification, and "the program doesn't contain it" must never be inverted into "the machine is unsafe." That red line matches what we've argued on this blog all along — AI assists, humans gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who it's for, and what you need
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good fit&lt;/strong&gt;: electrical/automation engineers inheriting someone else's (or their own past) TIA Portal projects; teams wanting AI in code review without knowing how to constrain it; anyone who wants to turn the 7-category checklist into a team review template — the course explicitly suggests using "7 categories + evidence + counter-evidence + severity" as an MR/review template. It also recommends taking one of &lt;em&gt;your own&lt;/em&gt; legacy projects, running the brief against it read-only, and only then deciding what's worth fixing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites&lt;/strong&gt;: a Windows machine with TIA Portal V19, &lt;a href="https://tia.hubwiz.com/tia-link?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=ai-plc-program-review-lesson" rel="noopener noreferrer"&gt;TiaLink&lt;/a&gt; (an MCP gateway that connects TIA Portal to your AI agent; it runs locally, so project data stays in the plant), and any Streamable-HTTP MCP client (Claude Code, Cursor, VS Code, ...). The lesson is ~35 minutes, free, no videos, nothing to buy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Not for you&lt;/strong&gt; if you're looking for a one-click "AI will optimize my code" button — the methodology here points the other way: zero changes during review, human verification of every finding before any fix enters a change process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Further reading (all in Chinese, on our blog)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.hubwiz.com/blog/ai-plc-programming-7-lessons-verification/" rel="noopener noreferrer"&gt;AI PLC Programming: 7 Free Hands-On Lessons, Each With a Verifiable Gate&lt;/a&gt; — our overview of the whole 7-lesson series;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.hubwiz.com/blog/taking-over-legacy-plc-program-4-steps/" rel="noopener noreferrer"&gt;Taking Over a Legacy PLC Program: A 4-Step Forensics Routine&lt;/a&gt; — evidence gathering and baselining &lt;em&gt;before&lt;/em&gt; the review;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.hubwiz.com/blog/siemens-plc-ladder-example-library/" rel="noopener noreferrer"&gt;Learn-LAD Ladder Example Library&lt;/a&gt; — where the Sorting Boxes example comes from;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.hubwiz.com/blog/tia-portal-openness-mcp-write-guards/" rel="noopener noreferrer"&gt;AI Editing TIA Portal Projects: Write Protection in TIA Openness MCP Tools&lt;/a&gt; — write-protection design patterns when letting AI modify projects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you'd rather read the primary source: &lt;a href="https://tia.hubwiz.com/learn-lad/ai/plc-program-review/?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=ai-plc-program-review-lesson" rel="noopener noreferrer"&gt;Lesson 5, in Chinese, at Learn-LAD&lt;/a&gt; — the task brief and the report template are copy-paste ready.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Disclosure: all measured numbers above (2×P1, 2×P2, 1×P3, 1 eliminated false positive; 18 networks; 31 tags; 5 system instance DBs; 10 ms pulse) come from the course page's own published run, not from an independent re-test by this publication. This article is a syndicated English translation of &lt;a href="https://www.hubwiz.com/blog/ai-plc-program-review-lesson/" rel="noopener noreferrer"&gt;our original Chinese post&lt;/a&gt; and originally appeared there.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>plc</category>
      <category>automation</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
