<?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: Vasiliy Shilov</title>
    <description>The latest articles on DEV Community by Vasiliy Shilov (@uxter).</description>
    <link>https://dev.to/uxter</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3800482%2Ff87d2997-d09e-4192-9ff4-3bf4d2aada62.png</url>
      <title>DEV Community: Vasiliy Shilov</title>
      <link>https://dev.to/uxter</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/uxter"/>
    <language>en</language>
    <item>
      <title>Inspecting @cursor/sdk: what npm installs - and what it doesn't decide for you</title>
      <dc:creator>Vasiliy Shilov</dc:creator>
      <pubDate>Wed, 29 Apr 2026 20:27:33 +0000</pubDate>
      <link>https://dev.to/uxter/inspecting-cursorsdk-what-npm-installs-and-what-it-doesnt-decide-for-you-190h</link>
      <guid>https://dev.to/uxter/inspecting-cursorsdk-what-npm-installs-and-what-it-doesnt-decide-for-you-190h</guid>
      <description>&lt;h2&gt;
  
  
  How this started
&lt;/h2&gt;

&lt;p&gt;A few hours ago I scrolled past Cursor's own post. It said they're &lt;strong&gt;introducing the Cursor SDK&lt;/strong&gt; so you can build agents with &lt;strong&gt;the same runtime, harness, and models&lt;/strong&gt; that power Cursor - CI/CD, end-to-end automations, agents embedded in products. The post also named several companies as examples.&lt;/p&gt;

&lt;p&gt;My brain did the thing every tired developer does: &lt;em&gt;same runtime&lt;/em&gt; -&amp;gt; &lt;em&gt;I'll read the code&lt;/em&gt; -&amp;gt; &lt;em&gt;I'll see how sandboxing works&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Official announcement: &lt;a href="https://www.linkedin.com/posts/cursorai_were-introducing-the-cursor-sdk-so-you-can-ugcPost-7455265719660740608-Y8DW/" rel="noopener noreferrer"&gt;Cursor on LinkedIn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Spoiler: the SDK &lt;strong&gt;is&lt;/strong&gt; on npm. What ships &lt;strong&gt;isn't human-readable application source&lt;/strong&gt; laid out for audit - it's compiled bundles (still plain text on disk, &lt;strong&gt;not intended&lt;/strong&gt; for line-by-line inspection of logic).&lt;/p&gt;




&lt;h2&gt;
  
  
  What I expected vs what I got
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Expectation&lt;/th&gt;
&lt;th&gt;Reality&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Clone-adjacent transparency&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@cursor/sdk&lt;/code&gt; is &lt;strong&gt;compiled bundles&lt;/strong&gt; (&lt;code&gt;dist/cjs&lt;/code&gt;, &lt;code&gt;dist/esm&lt;/code&gt;), not a source tree you can grep like your own repo - &lt;strong&gt;not intended&lt;/strong&gt; for line-by-line inspection (nothing stops you opening the file; it's not the packaging intent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"I'll verify security in the agent loop"&lt;/td&gt;
&lt;td&gt;You verify &lt;strong&gt;your&lt;/strong&gt; process boundary (user, container, network). The types hint at &lt;strong&gt;&lt;code&gt;local.sandboxOptions.enabled&lt;/code&gt;&lt;/strong&gt; - a boolean, not a full FS ACL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open license vibes&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LICENSE.md&lt;/code&gt; is short and proprietary (Anysphere, tied to &lt;a href="https://cursor.com/terms-of-service" rel="noopener noreferrer"&gt;Terms of Service&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;No pitchforks - just alignment: &lt;strong&gt;proprietary code on my laptop is a contract&lt;/strong&gt;, not a community repo. I'm fine with that when I &lt;em&gt;choose&lt;/em&gt; it. I had simply mixed up "SDK on npm" with "something I can read like first-party source."&lt;/p&gt;




&lt;h2&gt;
  
  
  What actually sits in &lt;code&gt;node_modules/@cursor&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;I pinned what I saw from &lt;strong&gt;&lt;code&gt;@cursor/sdk@1.0.9&lt;/code&gt;&lt;/strong&gt; (public beta in &lt;code&gt;package.json&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;@cursor/sdk&lt;/code&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name / version:&lt;/strong&gt; &lt;code&gt;@cursor/sdk&lt;/code&gt; &lt;strong&gt;1.0.9&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Description:&lt;/strong&gt; &lt;em&gt;TypeScript SDK for Cursor agents (public beta).&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entry:&lt;/strong&gt; &lt;code&gt;main&lt;/code&gt; -&amp;gt; &lt;code&gt;./dist/cjs/index.js&lt;/code&gt;, ESM -&amp;gt; &lt;code&gt;./dist/esm/index.js&lt;/code&gt;, types in &lt;code&gt;dist&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exports:&lt;/strong&gt; &lt;code&gt;"."&lt;/code&gt; and &lt;code&gt;"./agent"&lt;/code&gt; both resolve to the same built entrypoints (so &lt;code&gt;import '@cursor/sdk/agent'&lt;/code&gt; isn't a second codebase - it's the same bundle, different export path)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime deps&lt;/strong&gt; (what npm actually installs for consumers):
&lt;code&gt;@bufbuild/protobuf&lt;/code&gt;, &lt;code&gt;@connectrpc/connect&lt;/code&gt;, &lt;code&gt;@connectrpc/connect-node&lt;/code&gt;, &lt;code&gt;@statsig/js-client&lt;/code&gt;, &lt;code&gt;sqlite3&lt;/code&gt;, &lt;code&gt;zod&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optional dependencies:&lt;/strong&gt; platform packages that pull native-ish payloads -
&lt;code&gt;@cursor/sdk-darwin-arm64&lt;/code&gt;, &lt;code&gt;@cursor/sdk-darwin-x64&lt;/code&gt;, &lt;code&gt;@cursor/sdk-linux-arm64&lt;/code&gt;, &lt;code&gt;@cursor/sdk-linux-x64&lt;/code&gt;, &lt;code&gt;@cursor/sdk-win32-x64&lt;/code&gt; (same version pin)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On disk it's still &lt;strong&gt;Connect RPC + protobuf + sqlite + zod + Statsig&lt;/strong&gt; on paper - plus a &lt;strong&gt;large bundled &lt;code&gt;index.js&lt;/code&gt; for each of CJS and ESM&lt;/strong&gt; (~5 MB each in 1.0.9). Good for shipping; &lt;strong&gt;not packaged&lt;/strong&gt; for comfortable line-by-line review of behavior (the bundle is readable; ergonomics aren't those of a source drop).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Connect RPC (Buf).&lt;/strong&gt; Dependencies like &lt;code&gt;@connectrpc/connect&lt;/code&gt;, &lt;code&gt;@connectrpc/connect-node&lt;/code&gt;, and &lt;code&gt;@bufbuild/protobuf&lt;/code&gt; point at &lt;strong&gt;Protobuf over HTTP&lt;/strong&gt; - the stack Buf popularised as "typed RPC without reinventing REST." That doesn't reveal application logic, but it's a &lt;strong&gt;stack choice&lt;/strong&gt;: typical patterns are &lt;strong&gt;generated contracts&lt;/strong&gt; and fewer stringly-typed boundaries than ad hoc JSON.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Statsig.&lt;/strong&gt; The tree includes &lt;code&gt;@statsig/js-client&lt;/code&gt; (Statsig's own product category is feature flags and related tooling). I'm not going to guess what it does in this SDK at runtime; it's listed here so you can &lt;strong&gt;check Cursor's docs and your own network traces&lt;/strong&gt; if that matters for your policy. More on treating unknown egress below.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;@cursor/sdk-darwin-x64&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Separate tiny package, same version &lt;strong&gt;1.0.9&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Description:&lt;/strong&gt; &lt;em&gt;Ripgrep binary for darwin-x64, bundled for &lt;a class="mentioned-user" href="https://dev.to/cursor"&gt;@cursor&lt;/a&gt;/sdk.&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraints:&lt;/strong&gt; &lt;code&gt;os: ["darwin"]&lt;/code&gt;, &lt;code&gt;cpu: ["x64"]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ships:&lt;/strong&gt; &lt;code&gt;bin/**/*&lt;/code&gt; (plus &lt;code&gt;package.json&lt;/code&gt; / &lt;code&gt;README&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So one "platform" slice is literally &lt;strong&gt;ripgrep for macOS x64&lt;/strong&gt; - a native binary &lt;strong&gt;inside&lt;/strong&gt; npm. On &lt;strong&gt;Linux x64&lt;/strong&gt; the optional package is &lt;strong&gt;&lt;code&gt;@cursor/sdk-linux-x64&lt;/code&gt;&lt;/strong&gt; with &lt;strong&gt;&lt;code&gt;bin/rg&lt;/code&gt;&lt;/strong&gt; - same pattern. That's normal for tooling; it's also another reminder that &lt;strong&gt;trust isn't abstract&lt;/strong&gt;: you're executing vendor-supplied code &lt;em&gt;and&lt;/em&gt; vendor-supplied binaries.&lt;/p&gt;

&lt;h3&gt;
  
  
  License (the one file I didn't need a source map for)
&lt;/h3&gt;

&lt;p&gt;From &lt;code&gt;LICENSE.md&lt;/code&gt; in the package:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;© Anysphere Inc. All rights reserved. Use is subject to Cursor's Terms of Service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Short. Clear. &lt;strong&gt;Not&lt;/strong&gt; OSI-approved openness.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security: still not "in the box"
&lt;/h2&gt;

&lt;p&gt;The SDK types expose things like &lt;strong&gt;&lt;code&gt;local.cwd&lt;/code&gt;&lt;/strong&gt; (workspace) and &lt;strong&gt;&lt;code&gt;local.sandboxOptions.enabled&lt;/code&gt;&lt;/strong&gt; - useful switches, not a full policy engine. There is &lt;strong&gt;no&lt;/strong&gt; fine-grained filesystem ACL in the public types (no per-path allow/deny lists, no separate read/write policies per tree). So &lt;strong&gt;&lt;code&gt;enabled&lt;/code&gt; helps but is not enough&lt;/strong&gt; without everything below.&lt;/p&gt;

&lt;h3&gt;
  
  
  Privacy, telemetry, and dependencies
&lt;/h3&gt;

&lt;p&gt;If &lt;strong&gt;&lt;code&gt;@statsig/js-client&lt;/code&gt;&lt;/strong&gt; (or any other listed dependency) is a concern for &lt;strong&gt;air-gapped use&lt;/strong&gt;, &lt;strong&gt;strict CI determinism&lt;/strong&gt;, or &lt;strong&gt;third-party analytics policies&lt;/strong&gt;, the practical step is the same as for any SDK: &lt;strong&gt;Cursor's docs / ToS&lt;/strong&gt;, and &lt;strong&gt;validate egress&lt;/strong&gt; in &lt;em&gt;your&lt;/em&gt; environment (proxy, firewall logs, packet capture if your policy allows).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Egress allowlisting&lt;/strong&gt; applies both to &lt;strong&gt;RPC/API/model traffic you already expect&lt;/strong&gt; and to &lt;strong&gt;anything else&lt;/strong&gt; the process might open - without assuming good or bad intent; just know what leaves the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  Checklist: safe local use of &lt;code&gt;@cursor/sdk&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Treat the SDK as the &lt;em&gt;driver&lt;/em&gt;; security comes from the OS, container, network, permissions, and your own guardrails (audit, approvals, limits).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommended baseline (any OS)&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run the agent in an &lt;strong&gt;isolated environment&lt;/strong&gt; (container, VM, or dedicated user).&lt;/li&gt;
&lt;li&gt;Use a &lt;strong&gt;dedicated workspace per task&lt;/strong&gt; (&lt;code&gt;cwd&lt;/code&gt;), not your home directory or multi-project trees with secrets.&lt;/li&gt;
&lt;li&gt;Set &lt;strong&gt;&lt;code&gt;local.sandboxOptions.enabled: true&lt;/code&gt;&lt;/strong&gt; for local agents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restrict outbound network&lt;/strong&gt; (allowlist only what you need).&lt;/li&gt;
&lt;li&gt;Require &lt;strong&gt;human approval&lt;/strong&gt; for risky actions (&lt;code&gt;delete&lt;/code&gt;, bulk writes, destructive &lt;code&gt;shell&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log and audit&lt;/strong&gt; prompts, tool calls, shell commands, and file changes.&lt;/li&gt;
&lt;li&gt;Apply &lt;strong&gt;timeouts and step/output limits&lt;/strong&gt; to stop runaway runs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin dependencies&lt;/strong&gt;, use lockfiles, and avoid arbitrary installs at runtime.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Filesystem&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One workspace directory per job; avoid pointing &lt;code&gt;cwd&lt;/code&gt; at trees that hold SSH keys, cloud creds, or personal data.&lt;/li&gt;
&lt;li&gt;Prefer &lt;strong&gt;read-only&lt;/strong&gt; mounts for source and a &lt;strong&gt;writable scratch&lt;/strong&gt; for artifacts only.&lt;/li&gt;
&lt;li&gt;Run as a &lt;strong&gt;non-root / non-admin&lt;/strong&gt; user dedicated to the agent.&lt;/li&gt;
&lt;li&gt;Block access to typical secret locations unless strictly required: &lt;code&gt;~/.ssh&lt;/code&gt;; &lt;code&gt;~/.aws&lt;/code&gt;, &lt;code&gt;~/.config/gcloud&lt;/code&gt;, similar cloud CLI dirs; password managers, browser profiles, credential stores.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Secrets and tokens&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer &lt;strong&gt;short-lived&lt;/strong&gt; credentials.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Redact&lt;/strong&gt; secrets in logs and artifacts.&lt;/li&gt;
&lt;li&gt;Avoid baking long-lived tokens into env unless necessary; rotate regularly.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tools and automation risk&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;shell&lt;/code&gt; is usually the highest risk; &lt;code&gt;write&lt;/code&gt; / &lt;code&gt;delete&lt;/code&gt; are next. Apply least privilege: disable or gate tools you do not need, and require confirmation for destructive operations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Network&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use an &lt;strong&gt;egress allowlist&lt;/strong&gt; (only package registries and APIs you actually use).&lt;/li&gt;
&lt;li&gt;Block access to internal RFC1918 ranges and cloud metadata endpoints unless required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Audit and observability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Log at minimum: &lt;strong&gt;who / when / what prompt / which tools / which commands / what changed / outcome&lt;/strong&gt;. Essential for incident review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Supply chain&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Commit &lt;strong&gt;lockfiles&lt;/strong&gt;; pin critical versions.&lt;/li&gt;
&lt;li&gt;Scan dependencies for known issues.&lt;/li&gt;
&lt;li&gt;Do not allow unrestricted &lt;code&gt;npm install&lt;/code&gt; / package pulls inside the agent runtime without review.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pre-flight&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Isolated runtime (user, container, or VM).&lt;/li&gt;
&lt;li&gt;[ ] Narrow &lt;code&gt;cwd&lt;/code&gt; to a dedicated workspace.&lt;/li&gt;
&lt;li&gt;[ ] Sandbox on: &lt;strong&gt;&lt;code&gt;local.sandboxOptions.enabled&lt;/code&gt;&lt;/strong&gt; for local &lt;strong&gt;agent&lt;/strong&gt; options, or &lt;strong&gt;&lt;code&gt;sandboxOptions&lt;/code&gt;&lt;/strong&gt; on &lt;strong&gt;&lt;code&gt;createLocalExecutor&lt;/code&gt;&lt;/strong&gt; (same boolean - pick the API you use).&lt;/li&gt;
&lt;li&gt;[ ] Network restricted (allowlist).&lt;/li&gt;
&lt;li&gt;[ ] Secrets not exposed on the agent's filesystem or env.&lt;/li&gt;
&lt;li&gt;[ ] Dangerous tools gated or approved manually.&lt;/li&gt;
&lt;li&gt;[ ] Logging and audit enabled.&lt;/li&gt;
&lt;li&gt;[ ] Timeouts and step/output limits set.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Linux (short)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dedicated system user, &lt;strong&gt;no sudo&lt;/strong&gt;; per-run directory (e.g. &lt;code&gt;/srv/agent-runs/&amp;lt;id&amp;gt;&lt;/code&gt;), minimal home for that user.&lt;/li&gt;
&lt;li&gt;Prefer &lt;strong&gt;containers&lt;/strong&gt;: read-only root, single writable mount for work; &lt;code&gt;--cap-drop=ALL&lt;/code&gt;, &lt;code&gt;no-new-privileges&lt;/code&gt;, resource limits.&lt;/li&gt;
&lt;li&gt;Egress: &lt;strong&gt;nftables/iptables&lt;/strong&gt; or Kubernetes/CNI policies.&lt;/li&gt;
&lt;li&gt;Optional hardening: &lt;strong&gt;systemd&lt;/strong&gt; (&lt;code&gt;NoNewPrivileges&lt;/code&gt;, &lt;code&gt;ProtectSystem&lt;/code&gt;, &lt;code&gt;ProtectHome&lt;/code&gt;), &lt;strong&gt;cgroups&lt;/strong&gt; / &lt;strong&gt;ulimits&lt;/strong&gt;, &lt;strong&gt;seccomp&lt;/strong&gt; / &lt;strong&gt;AppArmor&lt;/strong&gt; / &lt;strong&gt;SELinux&lt;/strong&gt; where available.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;macOS (short)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Separate local user&lt;/strong&gt; for the agent (not administrator); dedicated folder for work - do not reuse your personal home for untrusted automation.&lt;/li&gt;
&lt;li&gt;No Linux-style containers by default - &lt;strong&gt;user separation + strict permissions&lt;/strong&gt;; consider &lt;strong&gt;VM or Linux VM&lt;/strong&gt; (Lima, Colima, UTM) for stronger boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TCC&lt;/strong&gt;: avoid &lt;strong&gt;Full Disk Access&lt;/strong&gt; unless required; don't grant broad disk access unnecessarily; keep &lt;strong&gt;Keychain&lt;/strong&gt; / &lt;strong&gt;iCloud Drive&lt;/strong&gt; off reachable paths unless intentional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application firewall&lt;/strong&gt; plus egress allowlist; block localhost/internal services if not needed.&lt;/li&gt;
&lt;li&gt;Same SDK settings: tight &lt;strong&gt;&lt;code&gt;local.cwd&lt;/code&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;code&gt;local.sandboxOptions.enabled: true&lt;/code&gt;&lt;/strong&gt;, with OS isolation as the real barrier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That checklist is &lt;strong&gt;operations&lt;/strong&gt;: isolation, egress, logs. API details: &lt;a href="https://cursor.com/docs/api/sdk/typescript" rel="noopener noreferrer"&gt;Cursor TypeScript SDK docs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Admissibility&lt;/strong&gt; (next section) sits on top of that - who may propose which actions before anything runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  The missing layer: admissibility
&lt;/h2&gt;

&lt;p&gt;This part is &lt;strong&gt;my framing&lt;/strong&gt;, not something missing from a README by mistake.&lt;/p&gt;

&lt;p&gt;The Cursor SDK gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a runtime
&lt;/li&gt;
&lt;li&gt;tools (read, write, shell)
&lt;/li&gt;
&lt;li&gt;an agent loop
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What &lt;strong&gt;you&lt;/strong&gt; still have to supply, in any serious deployment, is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;a definition of what is allowed to happen&lt;/strong&gt; - or accept that only culture and prompts stand between intent and execution.&lt;/p&gt;

&lt;p&gt;One common mental model is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent -&amp;gt; executes actions -&amp;gt; system changes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stricter pattern many teams want is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent -&amp;gt; proposes actions -&amp;gt; policy validates -&amp;gt; executor applies
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;one style leans on &lt;em&gt;trusting&lt;/em&gt; the agent within a sandbox flag
&lt;/li&gt;
&lt;li&gt;another splits &lt;strong&gt;policy&lt;/strong&gt; (what may exist) from &lt;strong&gt;execution&lt;/strong&gt; (what actually runs), so disallowed actions are &lt;strong&gt;unrepresentable&lt;/strong&gt; or &lt;strong&gt;rejected before an executor touches disk or shell&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That &lt;strong&gt;admissibility&lt;/strong&gt; layer - policies that decide whether a proposed action may exist at all - is where I spend time in design reviews, regardless of whether the SDK bundle is open source.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing
&lt;/h2&gt;

&lt;p&gt;The LinkedIn post describes &lt;strong&gt;same product surface, packaged for automation&lt;/strong&gt;. I had conflated that with &lt;strong&gt;shipping source&lt;/strong&gt; I could audit line-by-line like my own repo; &lt;code&gt;npm install&lt;/code&gt; showed &lt;strong&gt;published packages and a license&lt;/strong&gt;, not a &lt;strong&gt;navigable source tree&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you're embedding this on a machine that also holds sensitive data - treat it like any other proprietary runtime: &lt;strong&gt;isolate, limit, log, and read the license.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If readable sources or a published threat model for local execution appear later, I'll read them with interest.&lt;/p&gt;

&lt;p&gt;The SDK is easy to &lt;strong&gt;ship and wire up&lt;/strong&gt;; &lt;strong&gt;safety&lt;/strong&gt; still rides on &lt;strong&gt;your&lt;/strong&gt; threat model, isolation, egress, logging, and any admissibility rules &lt;strong&gt;outside&lt;/strong&gt; what one npm package implies. That isn't replaced by prompts or &lt;strong&gt;&lt;code&gt;local.sandboxOptions.enabled&lt;/code&gt;&lt;/strong&gt; alone.&lt;/p&gt;

&lt;p&gt;How do you run proprietary agents in CI - sandbox flags only, or gVisor/Firecracker (or similar) around the process?&lt;/p&gt;

</description>
      <category>cursor</category>
      <category>security</category>
      <category>sdk</category>
      <category>proprietary</category>
    </item>
    <item>
      <title>When diffs outrun gates: admissibility, not vibes</title>
      <dc:creator>Vasiliy Shilov</dc:creator>
      <pubDate>Thu, 23 Apr 2026 11:42:45 +0000</pubDate>
      <link>https://dev.to/uxter/when-diffs-outrun-gates-admissibility-not-vibes-3d63</link>
      <guid>https://dev.to/uxter/when-diffs-outrun-gates-admissibility-not-vibes-3d63</guid>
      <description>&lt;p&gt;When diffs land faster than your gates can reject bad ones, the repo feels limitless.&lt;/p&gt;

&lt;p&gt;That used to be mostly human typing speed. Now it is often assistant-proposed patches, merged on vibe when they "look right". Same geometry, faster collapse if admission lags.&lt;/p&gt;

&lt;p&gt;It is not. You are trading away room to change the thing later.&lt;/p&gt;

&lt;p&gt;Freedom in software is not how much you can ship. It is how much you can still change safely six months out.&lt;/p&gt;

&lt;p&gt;That is not a one-time "we fixed how we ship". It is evolution in the &lt;a href="https://martinfowler.com/articles/evo-arch-forward.html" rel="noopener noreferrer"&gt;evolutionary architecture&lt;/a&gt; sense (Fowler's foreword to Ford/Parsons/Kua): the product moves in steps, and the admission regime moves with it - new gates, retired checks, shifted seams - as explicit change, not folklore baked into tribal merge habits.&lt;/p&gt;

&lt;p&gt;Control has to scale with proposal rate - not with how fast someone can read patches. So the blunt move: replace review with rejection, opinion with admissibility. You are not rubber-stamping plausibility; you refuse invalid transitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proposal
  -&amp;gt; machine constraints (reject / accept)
  -&amp;gt; repo state (only admissible states land)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Correctness is enforced before merge, not argued after.&lt;/p&gt;

&lt;p&gt;If staying correct still means "someone read the whole diff", you lose at high volume. Proposal outruns reading; verification has to be mechanical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The correction loop (this is the engine)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proposal
  -&amp;gt; tool says no (structured, machine-readable errors)
  -&amp;gt; patch again
  -&amp;gt; repeat until merge carries proof
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment teaches through errors - not through a thread of explanations. Good failure messages converge; vague ones thrash.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure pattern is predictable
&lt;/h2&gt;

&lt;p&gt;The problem is rarely "we shipped a bug". It is "we let implicit behavior through".&lt;/p&gt;

&lt;p&gt;Then the same chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;not rejected
  -&amp;gt; observable
  -&amp;gt; depended on (Hyrum)
  -&amp;gt; contracted
  -&amp;gt; expensive to fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plasticity drops: features keep landing, safe edits shrink.&lt;/p&gt;

&lt;p&gt;High proposal volume makes the middle steps cheap: easy to merge plausible patches, easy to skip intent and invariants, hard for admission to keep up.&lt;/p&gt;

&lt;p&gt;AI-assist raises proposal rate. Vibe-coding lowers the cost of &lt;em&gt;acceptance&lt;/em&gt; - merge on feel, argue after. Together they widen the gap between output volume and proof.&lt;/p&gt;

&lt;p&gt;You get more "looks right", less "we can defend this change".&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;The failure mode is unbounded admissibility.&lt;/p&gt;

&lt;p&gt;If invalid states are representable and mergeable, something will eventually occupy them - especially under pressure. The same dynamic is spelled out as &lt;a href="https://www.hyrumslaw.com/" rel="noopener noreferrer"&gt;Hyrum's Law&lt;/a&gt; and in more depth in &lt;a href="https://abseil.io/resources/swe-book/html/ch01.html" rel="noopener noreferrer"&gt;Software Engineering at Google, chapter 1&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Scalable control is not better prose in the PR. It is correctness you can enforce in CI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Law
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Proposal scales.&lt;br&gt;
Only constraints scale correctness.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The stack (admissibility layers, not a checklist)
&lt;/h2&gt;

&lt;p&gt;Each layer shrinks what can exist without proof.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) State space - illegal states unrepresentable
&lt;/h3&gt;

&lt;p&gt;If a state is representable, it will be produced.&lt;/p&gt;

&lt;p&gt;Shrink the surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;opaque / branded types at boundaries&lt;/li&gt;
&lt;li&gt;ADTs + exhaustiveness so new cases do not compile until handled&lt;/li&gt;
&lt;li&gt;total-ish functions where "partial" hides bugs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is state topology, not typing for aesthetics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge fails if:&lt;/strong&gt; new state / case escapes the model.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Topology - graph with enforced edges
&lt;/h3&gt;

&lt;p&gt;You do not architecture-review line by line. You kill invalid edges.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;layer rules, forbidden imports&lt;/li&gt;
&lt;li&gt;private / internal modules so reachability is smaller&lt;/li&gt;
&lt;li&gt;one real way to cross a boundary (protocol, schema, bus)&lt;/li&gt;
&lt;li&gt;automate the graph in CI (&lt;a href="https://www.archunit.org/" rel="noopener noreferrer"&gt;ArchUnit&lt;/a&gt; for Java, &lt;a href="https://github.com/sverweij/dependency-cruiser" rel="noopener noreferrer"&gt;dependency-cruiser&lt;/a&gt; for TS)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Merge fails if:&lt;/strong&gt; dependency rule or boundary breaks.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Contracts - what may exist
&lt;/h3&gt;

&lt;p&gt;Contracts are not discovered in the patch. They are declared first; implementation proves conformance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;interface / trait as shape&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://spec.openapis.org/oas/latest.html" rel="noopener noreferrer"&gt;OpenAPI&lt;/a&gt; / Protobuf / &lt;a href="https://json-schema.org/" rel="noopener noreferrer"&gt;JSON Schema&lt;/a&gt; as truth&lt;/li&gt;
&lt;li&gt;generated validators and clients off one spec&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Merge fails if:&lt;/strong&gt; code drifts from the declared contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  4) Semantic policy - beyond "it parses"
&lt;/h3&gt;

&lt;p&gt;Parser green is cheap. Policy is the rest: complexity, nesting, fan-out, banned APIs.&lt;/p&gt;

&lt;p&gt;Non-syntax observables count too: latency floors, ordering quirks, anything users can see in production. Those expectations harden like a documented field - even when the contract never promised them (same shape as implicit-performance expectations in Hyrum's Law - see Summary).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge fails if:&lt;/strong&gt; budgets or deny-lists trip.&lt;/p&gt;

&lt;h3&gt;
  
  
  5) Fitness - health and cost, not only logic
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.oreilly.com/library/view/building-evolutionary-architectures/9781491986356/" rel="noopener noreferrer"&gt;Building Evolutionary Architectures&lt;/a&gt; calls this kind of thing a fitness function: dependency growth, cycles, perf, memory, "how hard is revert". That is the bill for next quarter, not today's green build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge fails if:&lt;/strong&gt; fitness budgets regress.&lt;/p&gt;

&lt;h3&gt;
  
  
  6) Decision economy - intent is not optional
&lt;/h3&gt;

&lt;p&gt;Big anonymous diffs are debt. Small changes + link to ticket / ADR / explicit intent ("why now, what breaks if wrong, when removable").&lt;/p&gt;

&lt;p&gt;If there is no machine-linkable why for the what, treat the change as invalid.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Merge fails if:&lt;/strong&gt; trace missing.&lt;/p&gt;

&lt;h3&gt;
  
  
  7) Feedback semantics
&lt;/h3&gt;

&lt;p&gt;Verdict + reason codes + stable repro steps for the tool chain. Invest in error quality like you invest in features - bad messages waste everyone's time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another shift (architectural, not only procedural)
&lt;/h2&gt;

&lt;p&gt;Gates reject bad merges. Good. You still lose if volatility leaks outward and becomes the contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Principle
&lt;/h3&gt;

&lt;p&gt;Volatile parts must not be contracts. They live behind contracts - recursively:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;churny implementation behind a stable module surface&lt;/li&gt;
&lt;li&gt;churny module behind a stable service contract&lt;/li&gt;
&lt;li&gt;vendor quirks behind an internal adapter&lt;/li&gt;
&lt;li&gt;accidental shapes behind a semantic API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stability is not "clean core, messy edges". Every boundary that other teams or binaries see has to stabilize meaning - not mirror today's DB row, library type, or temporary workflow. The old split is still the reference: &lt;a href="https://doi.org/10.1145/361598.361623" rel="noopener noreferrer"&gt;Parnas on module criteria&lt;/a&gt;; for "shallow interface, deep module", &lt;a href="https://web.stanford.edu/~ouster/cgi-bin/aposd2ndEdExtract.pdf" rel="noopener noreferrer"&gt;Ousterhout&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When volume is high, the dangerous move is the same every time: an internal detail ships as a public shape (DTO as API, enum as protocol, error text as behavior). Assistants are especially good at surfacing whatever shape is easiest in the moment - which is exactly what you do not want on a public edge. Then the usual chain: observable -&amp;gt; depended -&amp;gt; expensive.&lt;/p&gt;

&lt;p&gt;Good architecture does not publish change. It absorbs it where the system can still afford to rewrite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The balance (do not over-rotate)
&lt;/h2&gt;

&lt;p&gt;"Hide everything behind interfaces" is how you buy a second job: adapters, mapping layers, ceremony, integration tax.&lt;/p&gt;

&lt;p&gt;The target is not maximal isolation. The target is governed coupling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;volatile internals&lt;/li&gt;
&lt;li&gt;stable semantics&lt;/li&gt;
&lt;li&gt;economical integration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Contracts must be cheap enough to compose. A boundary is good if it absorbs local churn without making cross-boundary change disproportionately expensive.&lt;/p&gt;

&lt;p&gt;Prefer a thin waist: small surface, explicit invariants, few operations, clear error semantics - not a universal wrapper over the universe.&lt;/p&gt;

&lt;h3&gt;
  
  
  DRY caveat
&lt;/h3&gt;

&lt;p&gt;Duplication inside a volatile zone is often cheaper than a shared abstraction that freezes churn too early.&lt;/p&gt;

&lt;p&gt;Law-shaped rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Don't share what is still changing.&lt;br&gt;
Hide it until the semantics stabilize.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Sanity checks on any seam
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Does it hide volatility, or just export the internal model?&lt;/li&gt;
&lt;li&gt;Can you swap implementation without changing the contract? (Seams and tests around them are still the practical toolkit in &lt;a href="https://www.oreilly.com/library/view/working-effectively-with/0131177052/" rel="noopener noreferrer"&gt;Feathers&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;What does a cross-cutting change cost across two or three of these seams?&lt;/li&gt;
&lt;li&gt;Did the adapter layer become the slowest and smartest part of the system?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not everything should be isolated. Isolate where volatility would otherwise escape and become public truth.&lt;/p&gt;

&lt;p&gt;The purpose of a boundary is not separation for its own sake. It is preserving cheap change for everyone outside.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rollout (without freezing the team)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Pick 5-10 non-negotiables: invariants + boundary rules.&lt;/li&gt;
&lt;li&gt;Encode as CI with explicit failure modes.&lt;/li&gt;
&lt;li&gt;Require intent links for non-trivial patches.&lt;/li&gt;
&lt;li&gt;Add fitness budgets (deps, complexity, perf, reversibility).&lt;/li&gt;
&lt;li&gt;Trend the meta: rejections by rule, repeat offenders, cost to delete recent work, deps touched per change.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What to optimize for
&lt;/h2&gt;

&lt;p&gt;Not "more lines merged". More reversible change.&lt;/p&gt;

&lt;p&gt;Execution got cheap; mistakes did not. They got easier to commit and harder to remove.&lt;/p&gt;

&lt;p&gt;So the question is not "how fast do we ship"? It is "do we keep the right to change this tomorrow"?&lt;/p&gt;

&lt;p&gt;"Read every line" is not a strategy at volume.&lt;/p&gt;

&lt;p&gt;What scales: invariants and contracts that reject invalid states before they become history.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>softwareengineering</category>
      <category>codequality</category>
      <category>scalability</category>
    </item>
    <item>
      <title>Code Is Not the Source of Truth. It's a Materialized View.</title>
      <dc:creator>Vasiliy Shilov</dc:creator>
      <pubDate>Wed, 18 Mar 2026 23:46:27 +0000</pubDate>
      <link>https://dev.to/uxter/code-is-not-the-source-of-truth-its-a-materialized-view-3hag</link>
      <guid>https://dev.to/uxter/code-is-not-the-source-of-truth-its-a-materialized-view-3hag</guid>
      <description>&lt;p&gt;A short piece about what happens to development when code stops being the center. More of a reflection: how we think, what we fix in place, and why speed is no longer the main constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Introduction&lt;/strong&gt; What this is about and why it matters now. The shift from "write faster" to "understand more clearly".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code Is Not the Center&lt;/strong&gt; Code as cache and as a projection of decisions. Source of truth is not files but invariants and intent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Intent and Invariants&lt;/strong&gt; What to fix before code and why. Decision first, then code - not the other way around.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two Modes&lt;/strong&gt; Flow and serialization: when something new is born, when clarity appears. How not to get stuck packaging the past.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Comprehension Debt&lt;/strong&gt; We generate understanding but don't know how to keep it as a system. And what to do about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Equilibrium&lt;/strong&gt; Architecture as a balance of forces: latency - throughput, flexibility - simplicity. Change = shift in space, invariants = boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One Formula&lt;/strong&gt; Meaning is fixed, execution is computed, the system lives through the evolution of decisions.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2 id="introduction"&gt;1. Introduction&lt;/h2&gt;

&lt;p&gt;For the last six months I've had the sense that I'm not so much writing code as trying to get a feel for the invariants of development itself.&lt;/p&gt;

&lt;p&gt;At first it looked like a local task: speed up development with AI without losing control. It seemed to be about tools - Cursor, models, pipelines.&lt;/p&gt;

&lt;p&gt;Very quickly it became clear: tools aren't the problem at all.&lt;/p&gt;

&lt;p&gt;The problem is undefined intent.&lt;/p&gt;

&lt;p&gt;AI doesn't break architecture. It just makes its weak spots visible faster than you can paper over them. You used to be able to write by hand for a week and not notice the mess in your head. Now - ten minutes, and you already have an architectural mess in the code.&lt;/p&gt;

&lt;p&gt;I stopped trying to "write faster". That's no longer the problem. AI simply removed it.&lt;/p&gt;

&lt;p&gt;The problem now is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how well you actually understand what you're doing&lt;/li&gt;
&lt;li&gt;how well you can say it&lt;/li&gt;
&lt;li&gt;whether you have invariants, not just "seems fine"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the most interesting observation: speed is no longer the constraint. The new constraint is clarity of thinking, sharpness of invariants, the ability to formalize intent.&lt;/p&gt;

&lt;p&gt;This booklet is about where that leads: not another framework, but a shift of gravity. Code moves to the background. What we fix before code and how we keep the system within the bounds of understanding moves to the center.&lt;/p&gt;

&lt;p&gt;In short: not "how to write better", but "how to make thinking a bit more engineering". And that turned out harder than any NestJS service.&lt;/p&gt;




&lt;h2 id="code-is-not-the-center"&gt;2. Code Is Not the Center&lt;/h2&gt;

&lt;p&gt;We've lived a long time with the idea that code is the system. That if the code exists - the system exists. If we lost the code - disaster.&lt;/p&gt;

&lt;p&gt;AI quietly broke that assumption. Not loudly, not declaratively - through practice.&lt;/p&gt;

&lt;p&gt;Code became cheap. So cheap that losing it is no longer a tragedy. Not because code got worse, but because a layer above it appeared that used to be blurry.&lt;/p&gt;

&lt;p&gt;Before: idea, discussions, code, workarounds, documentation (if you're lucky).&lt;/p&gt;

&lt;p&gt;Now something else is taking shape: idea, invariants, boundaries, decision, execution, code.&lt;/p&gt;

&lt;p&gt;That's where the main shift happens: code no longer explains the system, it only reflects it.&lt;/p&gt;




&lt;p&gt;Code is not the asset. The asset is invariants and decisions.&lt;/p&gt;

&lt;p&gt;Code is their projection. You can rebuild it.&lt;/p&gt;

&lt;p&gt;If you have intent, invariants, constraints, decision graph - code becomes computable. Not in a magical sense, but in this sense: computation used to be expensive, so we fixed the result (code). Now computation is cheap - we can fix the description and rebuild the result.&lt;/p&gt;

&lt;p&gt;A handy formula:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/cimpai/cimp-practices/blob/main/templates/CHANGE_PLAN.md" rel="noopener noreferrer"&gt;CHANGE_PLAN&lt;/a&gt; / DOSA / invariants&lt;/strong&gt; - source of truth
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;code&lt;/strong&gt; - materialized view
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;execution&lt;/strong&gt; - computing system state
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You're just applying to development what databases, functional systems, and distributed systems already do: schema and change log matter more than the current snapshot.&lt;/p&gt;

&lt;p&gt;One important anti-pattern: "I made it pretty but lost understanding" - that's a real anchor. So the model needs one more invariant: if you can't verify and understand the result - it's invalid, even if it's "computable".&lt;/p&gt;

&lt;p&gt;The reality of the system is slowly moving: it used to be "files + classes + functions", it's becoming "decisions + constraints + their evolution".&lt;/p&gt;




&lt;h2 id="intent-and-invariants"&gt;3. Intent and Invariants&lt;/h2&gt;

&lt;p&gt;Clean Architecture, DDD, Ports &amp;amp; Adapters - they define structure and boundaries. But the lifecycle of a decision - how it is captured, constrained, and evolved - remains implicit. The closest thing we have is &lt;a href="https://adr.github.io/" rel="noopener noreferrer"&gt;ADR&lt;/a&gt;. But &lt;a href="https://adr.github.io/" rel="noopener noreferrer"&gt;ADR&lt;/a&gt; turns decisions into documents. What we need is to turn decisions into system primitives.&lt;/p&gt;

&lt;p&gt;When you start explicitly fixing intent, invariants, constraints, kill criteria - you don't get documentation. You get a decision graph. And at some point it becomes obvious: code is not the center of the system. Code is a projection of decisions.&lt;/p&gt;

&lt;p&gt;Hence the next step. If decisions are primary, they can be versioned, checked, executed, tied to metrics. Then architecture isn't a set of layers, it's a system for managing decisions under uncertainty.&lt;/p&gt;




&lt;p&gt;In practice it looks like this. First you form the context of intent: talk to people, gather the picture. Then - a dump of thoughts, into a chat or on paper. Then structuring: not "make it pretty", but "split by intent, invariants, boundaries, steps". One plan file, one execution step at a time, a separate context per step. The model gets not "everything at once" but the minimal sufficient slice. And that gives both quality and economy: moving along meaning is cheaper than recovering meaning through search.&lt;/p&gt;

&lt;p&gt;The main observation: a separate chat - less context -&amp;gt; often better quality. LLM quality is proportional to the clarity of local context, not its size. A tree model fits: root -&amp;gt; intent and invariants, branches -&amp;gt; change plans, leaves -&amp;gt; execution steps. That's indexing by meaning, not by text.&lt;/p&gt;

&lt;p&gt;The human is responsible for meaning and boundaries. The system (AI + runtime) - for execution. AI shouldn't define the system, it should live inside the frame we set.&lt;/p&gt;




&lt;h2 id="two-modes"&gt;4. Two Modes&lt;/h2&gt;

&lt;p&gt;There are two modes we slip into when we think and do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Flow / exploration.&lt;/strong&gt; That's where something new is born. No clarity, just "feels like something's here", contradiction is allowed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serialization / fixation.&lt;/strong&gt; That's where clarity, words, boundaries appear. But almost nothing new appears there anymore.&lt;/p&gt;

&lt;p&gt;The problem isn't that the second mode is bad. The problem is that it's very sticky. It gives a sense of control, completeness - "I did good, I packaged it". And you can sit in it for hours, days, and at some point catch yourself: you haven't discovered anything in a long time, you're just neatly packing the past.&lt;/p&gt;

&lt;p&gt;The key skill isn't "write better", it's switching between modes on purpose. Almost like a toggle: now I'm exploring, now I'm fixing.&lt;/p&gt;

&lt;p&gt;A strict rule helps: if you're in flow - don't try to make it pretty and correct right away. At most - short markers, anchors, scraps of thought so you can come back later. And only in a separate state: ok, now let's turn this into a plan, an article, an architecture.&lt;/p&gt;

&lt;p&gt;And an important effect: if you do it that way, serialization stops "killing the flow" and starts feeding it. You return not to a void but to a space that's already a bit structured.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/cimpai/cimp" rel="noopener noreferrer"&gt;CIMP&lt;/a&gt; and similar things are basically an attempt to make even the fixed part not die but stay part of a living thinking process. Not a "memory dump", but a frame you can build the next loop on.&lt;/p&gt;




&lt;h2 id="comprehension-debt"&gt;5. Comprehension Debt&lt;/h2&gt;

&lt;p&gt;You talk with someone - and it's great. But if you close the chat, half the meaning just vanishes.&lt;/p&gt;

&lt;p&gt;We generate understanding but don't know how to keep it as a system. I call this &lt;strong&gt;comprehension debt&lt;/strong&gt; - like technical debt, only in understanding. The term has been around in various circles for a long time, and I didn't coin it, but this name fits me best.&lt;/p&gt;

&lt;p&gt;Any conversation with AI is ephemeral. It doesn't persist as a knowledge system. You kind of understood - but that understanding isn't anchored anywhere. Hence ideas like RAG, context graphs, structured artifacts. But even that's not enough: knowledge isn't text, it's the link between decisions, invariants, and consequences.&lt;/p&gt;

&lt;p&gt;In the new model, documentation stops being "extra work". It becomes the main interface for controlling the system. Not an afterthought, but what execution loses its anchor without.&lt;/p&gt;

&lt;p&gt;Formulating outward - articles, posts, plans - is a way to unload your head, check what matters, and make room for new things. Not a side effect but a natural extension of the same approach: structure lowers the cognitive cost.&lt;/p&gt;

&lt;p&gt;One more thought: if you can't hold the system as a model - it doesn't exist. Doesn't matter how many lines of code. So fixing meaning isn't bureaucracy, it's the condition for the system to exist in your head and in the heads of everyone who works with it.&lt;/p&gt;




&lt;h2 id="equilibrium"&gt;6. Equilibrium&lt;/h2&gt;

&lt;p&gt;Architecture works with a limited set of concepts and meanings. They're almost deterministic, what changes is mainly context. And there are concrete forces that are always being traded off.&lt;/p&gt;

&lt;p&gt;Technical: compute vs data, latency vs throughput, consistency and availability.&lt;/p&gt;

&lt;p&gt;Structural: flexibility vs simplicity, experimentation and research vs control.&lt;/p&gt;

&lt;p&gt;Architecture isn't a choice of "what's better", it's equilibrium. Every change isn't a "feature", it's a shift of the system in the space of these forces. And if you have invariants, you limit where you can move. If you don't - the system just spreads.&lt;/p&gt;

&lt;p&gt;It helps to think of it as vectors. The system is a point in the space of forces, a change is a shift of the vector, invariants are constraints on the allowed region. You start to see that system architecture and model architecture (the same vectors, matrices, parallel computation) aren't different worlds - they're the same math in different guises.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/uxter/stop-using-llms-for-everything-the-power-of-hybrid-architectures-45ee"&gt;Splitting into two layers&lt;/a&gt; keeps the zones from mixing: deterministic (invariants, policies, boundaries, checkable rules) and probabilistic (AI, inference, generation, search). AI should live inside constraints, not define them. Then the human is responsible for meaning and boundaries, the system for execution.&lt;/p&gt;

&lt;p&gt;In the end, development isn't writing code, it's managing the computation of decisions. And your workflow with plans, steps, and review stops looking like a hack and starts looking like a clean model: you generate intent, fix it, split into steps, execute one at a time, check, adjust. The main thing: you don't hold everything in your head at once. You build a system where moving along meaning is cheaper than recovering it.&lt;/p&gt;




&lt;h2 id="one-formula"&gt;7. One Formula&lt;/h2&gt;

&lt;p&gt;If you pack it all into one short formula:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Meaning is fixed. Execution is computed. The system lives through the evolution of decisions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You're no longer trying to "write correctly". You're trying to make it so that even if you forget - the system can be restored. That's already very close to how math, physics, and stable systems in general work.&lt;/p&gt;

&lt;p&gt;The shift is essentially:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;was:&lt;/strong&gt; code-centric engineering
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;becomes:&lt;/strong&gt; decision-centric engineering
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Like git for code - only for decisions. At some point I realized architecture isn't about diagrams. It's about taking a blurry thought and making it concrete enough to be checked, executed, and not lost a week later.&lt;/p&gt;

&lt;p&gt;The final picture: not "speed up development", but make it observable, explainable, controllable, and scalable without losing meaning. Thinking becomes executable. And that's much deeper than it seems at first.&lt;/p&gt;




&lt;p&gt;This idea doesn't stop here.&lt;/p&gt;

&lt;p&gt;If code is not the source of truth, then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what is?&lt;/li&gt;
&lt;li&gt;how is it structured?&lt;/li&gt;
&lt;li&gt;how does it evolve?&lt;/li&gt;
&lt;/ul&gt;

&lt;p id="next-parts"&gt;I'll explore this in the next short parts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DOSA (Decision-Oriented System Architecture)&lt;/strong&gt; - what happens when decisions become first-class system primitives, and architecture turns into a graph of evolving decisions rather than static structure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context-oriented engineering&lt;/strong&gt; - why smaller, structured context outperforms large prompts, and how to make context composable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feedback-centric engineering&lt;/strong&gt; - how systems close the loop between execution and understanding, and learn through decisions
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System evolution&lt;/strong&gt; - how systems become capable of change while preserving stability&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>architecture</category>
      <category>discuss</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Stop Using LLMs for Everything: The Power of Hybrid Architectures</title>
      <dc:creator>Vasiliy Shilov</dc:creator>
      <pubDate>Sun, 08 Mar 2026 19:40:10 +0000</pubDate>
      <link>https://dev.to/uxter/stop-using-llms-for-everything-the-power-of-hybrid-architectures-45ee</link>
      <guid>https://dev.to/uxter/stop-using-llms-for-everything-the-power-of-hybrid-architectures-45ee</guid>
      <description>&lt;p&gt;Over the past month my thinking about AI systems changed dramatically.&lt;/p&gt;

&lt;p&gt;Many teams are quietly making the same architectural mistake:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;They use LLMs for problems that should remain deterministic.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result is predictable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;higher latency&lt;/li&gt;
&lt;li&gt;higher cost&lt;/li&gt;
&lt;li&gt;lower reliability&lt;/li&gt;
&lt;li&gt;harder debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The irony?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Most intelligent systems don't need more AI. They need better architecture.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The common narrative today is simple:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intelligence = large probabilistic models.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This assumption quietly pushes many teams into a dangerous design mistake: using probabilistic models for problems that should remain deterministic.&lt;/p&gt;

&lt;p&gt;But when you start building systems that actually work reliably, a different picture appears.&lt;/p&gt;

&lt;p&gt;Most practical systems are not purely probabilistic — they are architectures combining deterministic and probabilistic computation.&lt;/p&gt;

&lt;p&gt;Understanding the difference between these two classes of computation turns out to be extremely important, not only for AI engineers but for system architects in general.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where This Idea Came From
&lt;/h2&gt;

&lt;p&gt;My perspective on this topic evolved through several stages.&lt;/p&gt;

&lt;p&gt;First, years of writing software the traditional way — carefully designing deterministic systems where behavior is predictable and constraints are explicit.&lt;/p&gt;

&lt;p&gt;Then the arrival of AI coding tools. Suddenly code generation became extremely cheap. Many tasks that used to require careful implementation could be produced instantly.&lt;/p&gt;

&lt;p&gt;At first this felt like pure acceleration. But over time it became clear that cheap execution has a side effect: architectural drift.&lt;/p&gt;

&lt;p&gt;This line of thinking started when I began exploring the hidden cost of cheap execution in AI-accelerated development (which I wrote about earlier in a LinkedIn post: "&lt;a href="https://www.linkedin.com/pulse/hidden-cost-cheap-execution-vasiliy-shilov-6o1qf/" rel="noopener noreferrer"&gt;The Hidden Cost of Cheap Execution&lt;/a&gt;").&lt;/p&gt;

&lt;p&gt;More recently I've been building tools that intentionally combine deterministic and probabilistic computation — applying probabilistic reasoning only where deterministic structure cannot reduce the problem space further.&lt;/p&gt;

&lt;p&gt;This article summarizes the core principle behind that approach.&lt;/p&gt;




&lt;h2&gt;
  
  
  Two Classes of Computation
&lt;/h2&gt;

&lt;p&gt;At a very high level, most computational tasks fall into two categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Deterministic computation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Probabilistic computation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;They solve fundamentally different kinds of problems.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;Deterministic&lt;/th&gt;
&lt;th&gt;Probabilistic&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Output&lt;/td&gt;
&lt;td&gt;fixed&lt;/td&gt;
&lt;td&gt;distribution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging&lt;/td&gt;
&lt;td&gt;straightforward&lt;/td&gt;
&lt;td&gt;statistical&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Failure&lt;/td&gt;
&lt;td&gt;explicit&lt;/td&gt;
&lt;td&gt;uncertain&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;cheap&lt;/td&gt;
&lt;td&gt;expensive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use case&lt;/td&gt;
&lt;td&gt;constraints&lt;/td&gt;
&lt;td&gt;ambiguity&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Deterministic Computation
&lt;/h2&gt;

&lt;p&gt;Deterministic computation is what classical software engineering is built on. Given the same input, the system always produces the same output.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;compilers&lt;/li&gt;
&lt;li&gt;parsers&lt;/li&gt;
&lt;li&gt;type checkers&lt;/li&gt;
&lt;li&gt;database queries&lt;/li&gt;
&lt;li&gt;validation rules&lt;/li&gt;
&lt;li&gt;cryptography&lt;/li&gt;
&lt;li&gt;routing logic&lt;/li&gt;
&lt;li&gt;protocol implementations&lt;/li&gt;
&lt;li&gt;regular expressions (parsing, validation, extraction)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In deterministic systems:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;output = f(input)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The function &lt;em&gt;f&lt;/em&gt; is explicit, stable, and predictable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;p&gt;Deterministic computation is extremely powerful when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rules are known&lt;/li&gt;
&lt;li&gt;constraints are strict&lt;/li&gt;
&lt;li&gt;correctness matters&lt;/li&gt;
&lt;li&gt;behavior must be explainable&lt;/li&gt;
&lt;li&gt;failure modes must be controlled&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Properties:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;predictable&lt;/li&gt;
&lt;li&gt;debuggable&lt;/li&gt;
&lt;li&gt;verifiable&lt;/li&gt;
&lt;li&gt;cheap to run&lt;/li&gt;
&lt;li&gt;safe for critical paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why the core infrastructure of the digital world — databases, compilers, operating systems — is deterministic. No surprises. You can reason about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;Deterministic systems struggle when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rules are unknown&lt;/li&gt;
&lt;li&gt;inputs are ambiguous&lt;/li&gt;
&lt;li&gt;the space of possibilities is huge&lt;/li&gt;
&lt;li&gt;knowledge must be compressed from data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;natural language interpretation&lt;/li&gt;
&lt;li&gt;image recognition&lt;/li&gt;
&lt;li&gt;semantic similarity&lt;/li&gt;
&lt;li&gt;reasoning under uncertainty&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These problems are hard to encode with explicit rules. That's where probability earns its place.&lt;/p&gt;




&lt;h2&gt;
  
  
  Probabilistic Computation
&lt;/h2&gt;

&lt;p&gt;Probabilistic systems operate differently. Instead of explicit rules, they model probability distributions.&lt;/p&gt;

&lt;p&gt;For example, a language model estimates:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P(next_token | context)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The system does not compute the answer through rules; it computes the most likely continuation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;language models&lt;/li&gt;
&lt;li&gt;speech recognition&lt;/li&gt;
&lt;li&gt;recommender systems&lt;/li&gt;
&lt;li&gt;ranking models&lt;/li&gt;
&lt;li&gt;anomaly detection&lt;/li&gt;
&lt;li&gt;computer vision models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Probabilistic systems are extremely powerful for problems where:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rules are unknown&lt;/li&gt;
&lt;li&gt;data is noisy&lt;/li&gt;
&lt;li&gt;patterns must be inferred&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Strengths
&lt;/h3&gt;

&lt;p&gt;Probabilistic systems are excellent at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pattern recognition&lt;/li&gt;
&lt;li&gt;generalization&lt;/li&gt;
&lt;li&gt;handling ambiguity&lt;/li&gt;
&lt;li&gt;synthesizing new combinations&lt;/li&gt;
&lt;li&gt;compressing large knowledge spaces&lt;/li&gt;
&lt;li&gt;when you don't have a spec, they're often the only option&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is why modern AI works at all. The catch: it doesn't tell you &lt;em&gt;where&lt;/em&gt; to use it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Limitations
&lt;/h3&gt;

&lt;p&gt;But probabilistic systems have fundamental weaknesses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;non-deterministic outputs&lt;/li&gt;
&lt;li&gt;hallucinations&lt;/li&gt;
&lt;li&gt;difficulty enforcing constraints&lt;/li&gt;
&lt;li&gt;limited explainability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;cost and latency&lt;/strong&gt; — model inference is expensive compared to deterministic logic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A regex, an if statement, or a database lookup executes in microseconds and costs essentially nothing. A model call costs money and introduces latency. At scale, this difference becomes a primary architectural constraint.&lt;/p&gt;

&lt;p&gt;If used incorrectly, they introduce uncertainty into places where certainty is required.&lt;/p&gt;




&lt;h2&gt;
  
  
  The False Dichotomy
&lt;/h2&gt;

&lt;p&gt;Many discussions today frame the problem incorrectly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should we replace deterministic systems with AI?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the wrong question. The real question is:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How should deterministic and probabilistic computation be composed?&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Where Deterministic Computation Wins
&lt;/h2&gt;

&lt;p&gt;Deterministic systems dominate when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the structure is known&lt;/li&gt;
&lt;li&gt;constraints exist&lt;/li&gt;
&lt;li&gt;invariants must be preserved&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Programming languages&lt;/strong&gt; — Compilers are deterministic for a reason. A probabilistic compiler would be catastrophic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Databases&lt;/strong&gt; — SQL engines are deterministic because queries must be correct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protocols&lt;/strong&gt; — Network protocols rely on deterministic state machines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Validation&lt;/strong&gt; — Formats like JSON, protobuf, and schema validation require exact correctness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regular expressions&lt;/strong&gt; — Same pattern and input always yield the same match. In hybrid systems they often do the first cut — extracting structure (dates, IDs, emails) from raw text before any LLM sees it. That reduces ambiguity and keeps the model away from tasks that don't need probability.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Where Probabilistic Computation Wins
&lt;/h2&gt;

&lt;p&gt;Probabilistic systems dominate when the problem is inherently ambiguous.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Examples:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Natural language&lt;/strong&gt; — Human language contains ambiguity everywhere.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval and ranking&lt;/strong&gt; — Choosing the most relevant document is rarely deterministic. Ever tried to make that 100% rule-based? It doesn't scale.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vision&lt;/strong&gt; — Images are noisy and high dimensional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Code synthesis&lt;/strong&gt; — Generating new code often requires combining patterns probabilistically.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Deterministic Risk Control
&lt;/h2&gt;

&lt;p&gt;Deterministic layers are where you enforce invariants and reduce risk. Probabilistic components don't get to override these rules.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Input validation&lt;/strong&gt; — length, charset, schema (e.g. JSON schema). Invalid input never reaches the model.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output validation&lt;/strong&gt; — allowlists of actions, formats, or categories; length limits; PII checks. The model may suggest something, but only allowed values are executed or stored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Regular expressions&lt;/strong&gt; — extract and validate structure (emails, IDs, tags) before the model; same for checking model output against expected patterns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit and idempotency&lt;/strong&gt; — deterministic request IDs and idempotency keys ensure that critical actions are logged and not duplicated, regardless of model non-determinism.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I've seen codebases that sent every user message straight to an LLM. The bill and the latency told the story.&lt;/p&gt;

&lt;p&gt;The rule of thumb: &lt;em&gt;anything that would cause legal, safety, or data-integrity issues must be enforced in deterministic code&lt;/em&gt;, not in prompt engineering or "smarter" models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example: deterministic extraction before any LLM call:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;extractStructuredParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;ticketIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="nl"&gt;hasUrgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;[\w&lt;/span&gt;&lt;span class="sr"&gt;.-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+@&lt;/span&gt;&lt;span class="se"&gt;[\w&lt;/span&gt;&lt;span class="sr"&gt;.-&lt;/span&gt;&lt;span class="se"&gt;]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;\.\w&lt;/span&gt;&lt;span class="sr"&gt;+/g&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;ticketRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/#&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/g&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;urgentRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b(&lt;/span&gt;&lt;span class="sr"&gt;urgent|asap|critical&lt;/span&gt;&lt;span class="se"&gt;)\b&lt;/span&gt;&lt;span class="sr"&gt;/i&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;{&lt;/span&gt;
    &lt;span class="na"&gt;emails&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailRegex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="na"&gt;ticketIds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ticketRegex&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;map&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="na"&gt;hasUrgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;urgentRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Same input =&amp;gt; same output. No model needed for this.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example: deterministic guardrails on model output&lt;/strong&gt; — only allowlisted actions are executed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ALLOWED_ACTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;view&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;edit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;submit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cancel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;safeExecute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;modelOutput&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&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;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;modelOutput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\s&lt;/span&gt;&lt;span class="sr"&gt;+/&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;// e.g. "submit form"&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ALLOWED_ACTIONS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;error: unknown action&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// never pass through raw model output&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;executeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Real Architecture: Hybrid Systems
&lt;/h2&gt;

&lt;p&gt;The most powerful systems are hybrid. Instead of replacing deterministic computation, probabilistic models should operate inside deterministic scaffolding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deterministic logic defines the boundaries. Probabilistic models explore inside those boundaries.&lt;/strong&gt; That is the metaphor worth keeping in mind.&lt;/p&gt;

&lt;p&gt;Conceptually, the flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          Problem Space

┌──────────────────────────────┐
│                              │
│   Deterministic Reduction    │
│  (rules, validation, index)  │
│                              │
└──────────────┬───────────────┘
               │
               ▼
      Residual Uncertainty
               │
               ▼
     Probabilistic Reasoning
        (LLM / ML models)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Good architecture reduces the problem space deterministically before applying probabilistic intelligence.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A typical pipeline in code looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;input
   │
   ▼
deterministic preprocessing
   │
   ▼
constraint reduction
   │
   ▼
retrieval / memory
   │
   ▼
probabilistic reasoning
   │
   ▼
deterministic validation
   │
   ▼
output
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In code, that often looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Hybrid: deterministic shell around probabilistic core&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;processUserRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Deterministic: normalize and validate input&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;10000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid length&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Deterministic: extract known structure (e.g. with regex)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;refs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/#&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="nf"&gt;map&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;// ticket IDs&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Probabilistic: only for the ambiguous part&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refs&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Deterministic: validate output shape and safety&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5000&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="nf"&gt;fallbackResponse&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="nx"&gt;response&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In other words:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Remove everything that can be solved deterministically.&lt;/li&gt;
&lt;li&gt;Narrow the search space.&lt;/li&gt;
&lt;li&gt;Retrieve known information.&lt;/li&gt;
&lt;li&gt;Use probabilistic reasoning only for the residual uncertainty.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ports and Adapters: Structure Decides
&lt;/h3&gt;

&lt;p&gt;The same pipeline fits naturally into a port-adapter (hexagonal) view. What matters is the &lt;strong&gt;structure&lt;/strong&gt; — the ports and the flow — not whether a given step is implemented deterministically or probabilistically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          ┌─────────────────────────────────────┐
          │         Application Core            │
          │  (orchestration, use cases, ports)  │
          └──────────────────┬──────────────────┘
                             │
     ┌───────────────────────┼────────────────────────┐
     │                       │                        │
     ▼                       ▼                        ▼
┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐ ┌─────────┐
│ Preproc │ │ Retrieve │ │ Reason │ │ Validate │ │  Output │
│  port   │ │  port    │ │  port  │ │  port    │ │  port   │
└────┬────┘ └────┬─────┘ └────┬───┘ └────┬─────┘ └────┬────┘
     │           │            │          │            │
     ▼           ▼            ▼          ▼            ▼
  adapter     adapter      adapter     adapter      adapter
  (determ.    (vector DB,  (LLM /      (schema,     (format,
  or LLM)     or rule)     or rules)   allowlist)   log)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core depends only on &lt;strong&gt;ports&lt;/strong&gt; (interfaces). Each adapter can be deterministic or probabilistic. You can replace a deterministic preprocessor with a probabilistic one (e.g. "normalize with an LLM") or the other way around — the architecture stays the same. &lt;strong&gt;Structure decides; implementations are pluggable.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Port: the core only depends on this contract&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ReasonerPort&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Adapter A: deterministic (rules, template)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RuleBasedReasoner&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ReasonerPort&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refs&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;applyTemplates&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// same input =&amp;gt; same output&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Adapter B: probabilistic (LLM)&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LLMReasoner&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;ReasonerPort&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refs&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;refs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;refs&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="c1"&gt;// same input =&amp;gt; may vary&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Application code is identical; swap the adapter to switch behaviour&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reasoner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ReasonerPort&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RuleBasedReasoner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// or new LLMReasoner()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So: the &lt;strong&gt;decision&lt;/strong&gt; of where to use deterministic vs probabilistic logic lives in the choice of adapters, not in the core. The core defines &lt;em&gt;what&lt;/em&gt; steps exist and in what order — that is what we mean by "architecture is the multiplier."&lt;/p&gt;




&lt;h2&gt;
  
  
  Residual Intelligence
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Residual Intelligence Principle
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Probabilistic models should solve only the residual uncertainty after deterministic reduction of the problem space.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good architecture does not ask AI to solve everything. It asks AI to solve only what cannot be solved deterministically. Get that wrong and you're paying for intelligence you don't need.&lt;/p&gt;

&lt;p&gt;This dramatically reduces complexity and leads to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cheaper systems&lt;/li&gt;
&lt;li&gt;more reliable outputs&lt;/li&gt;
&lt;li&gt;fewer hallucinations&lt;/li&gt;
&lt;li&gt;easier governance&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example: Code Completion
&lt;/h3&gt;

&lt;p&gt;Modern IDEs illustrate this hybrid approach well. Many completions do not require LLMs. They rely on deterministic information:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;syntax&lt;/li&gt;
&lt;li&gt;types&lt;/li&gt;
&lt;li&gt;symbol tables&lt;/li&gt;
&lt;li&gt;project index&lt;/li&gt;
&lt;li&gt;scope rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Only when the system cannot determine a clear continuation does it use probabilistic generation. This combination is far more efficient than using an LLM everywhere.&lt;/p&gt;




&lt;h2&gt;
  
  
  Extreme Cases
&lt;/h2&gt;

&lt;p&gt;Understanding the extremes is also instructive: pure deterministic systems suffer from rule explosion, pure probabilistic ones from uncontrolled uncertainty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pure deterministic systems&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Strengths:&lt;/em&gt; reliability, predictability, efficiency&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Weaknesses:&lt;/em&gt; brittleness, inability to generalize, enormous rule complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pure probabilistic systems&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;Strengths:&lt;/em&gt; flexibility, adaptability, pattern recognition&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Weaknesses:&lt;/em&gt; instability, hallucinations, lack of guarantees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most systems that "went full AI" learned that the hard way.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture Is the Multiplier
&lt;/h2&gt;

&lt;p&gt;The biggest performance gains rarely come from making probabilistic models bigger. They come from structuring the system correctly.&lt;/p&gt;

&lt;p&gt;A well-designed deterministic layer can reduce the search space by orders of magnitude, so the probabilistic layer works on a much smaller and easier problem — and that is where nonlinear efficiency gains appear. One good deterministic filter can shrink the problem tenfold before the model ever runs.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Different Way to Think About AI
&lt;/h2&gt;

&lt;p&gt;Instead of thinking about AI as a replacement for software engineering, we can think about it as a new computational layer.&lt;/p&gt;

&lt;p&gt;Not:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;software =&amp;gt; replaced by AI&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deterministic systems
      +
probabilistic models
      =
hybrid intelligent architectures
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The future of intelligent systems is likely not pure AI; it is architecture — the art of deciding which parts of the system must be deterministic, and where probability should be allowed to exist.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thought
&lt;/h2&gt;

&lt;p&gt;AI did not eliminate engineering. It exposed something deeper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Execution was never the hardest problem.&lt;/strong&gt; The real challenge has always been structuring the problem space so that expensive intelligence is used only where it is truly needed. That is the job of architecture.&lt;/p&gt;




&lt;h2&gt;
  
  
  One question
&lt;/h2&gt;

&lt;p&gt;If you removed all deterministic layers from your system and replaced them with LLM calls...&lt;/p&gt;

&lt;p&gt;would it become smarter — or just more expensive?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>systemdesign</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Clean Architecture in the Age of AI: Preventing Architectural Liquefaction</title>
      <dc:creator>Vasiliy Shilov</dc:creator>
      <pubDate>Mon, 02 Mar 2026 00:21:21 +0000</pubDate>
      <link>https://dev.to/uxter/clean-architecture-in-the-age-of-ai-preventing-architectural-liquefaction-5d8d</link>
      <guid>https://dev.to/uxter/clean-architecture-in-the-age-of-ai-preventing-architectural-liquefaction-5d8d</guid>
      <description>&lt;p&gt;AI has made execution cheap; models optimize locally, not for architecture. In many teams the side effect is not bad code or broken builds, but something more structural: &lt;strong&gt;architectural liquefaction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Architectural liquefaction&lt;/em&gt; is the progressive loss of structural boundaries under sustained probabilistic code generation and accelerated change cycles. It does not happen in one PR — layer boundaries soften, dependencies cross the wrong way, contracts drift, invariants weaken, "temporary" shortcuts pile up. Everything still works. Until the cost of change quietly multiplies. Without explicit constraints, entropy grows as we ship faster.&lt;/p&gt;

&lt;p&gt;Clean Architecture is often described as a layering discipline. But in the context of AI-assisted development, it may serve a different purpose: a &lt;em&gt;deterministic shell&lt;/em&gt; around probabilistic execution. Not dogma, not aesthetic preference — a stabilizing mechanism. When boundaries are explicit and dependency direction is enforced:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The solution space &lt;strong&gt;narrows&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Drift becomes &lt;strong&gt;detectable&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Structural violations &lt;strong&gt;surface earlier&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Local optimization cannot silently destroy global design.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The architecture becomes a &lt;strong&gt;control surface&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before AI, architectural violations required effort. A developer had to consciously decide to break a boundary.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Now&lt;/strong&gt;, violations can be generated in seconds.&lt;/p&gt;

&lt;p&gt;And because AI-generated code often "looks right", structural erosion is harder to notice. The real cost is not bad code in the moment; it's that the drift stays invisible until you hit a refactor that suddenly touches half the codebase. One more thing: the more “flexible” and underspecified your prompts and rules are, the faster liquefaction tends to happen — the model fills in the gaps in whatever direction is locally easiest.&lt;/p&gt;

&lt;p&gt;I once wrote down all our architectural principles — boundaries, dependency rules, what lives where — into a &lt;code&gt;docs/&lt;/code&gt; folder in plain Markdown, then wired them into Cursor as project rules so they get injected into every prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tree ./docs/
&lt;span class="nb"&gt;.&lt;/span&gt;
├── ARCHITECTURAL-STYLE-GUIDE.md
├── CLEAN-NEST-APP.md
├── architecture
│   ├── adapters.md
│   ├── core.md
│   ├── controllers.md
│   ├── events.md
│   ├── inter-module-communication.md
│   ├── modules.md
│   ├── structure.md
│   ├── testing.md
│   └── when-to-simplify.md
└── guides
    ├── cheat-sheet.md
    ├── common-patterns.md
    └── quick-start.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before that, Cursor would often put repository calls straight into controllers or leak infrastructure imports into the domain layer — it just followed the patterns it saw in the codebase. After the rules were in place, it started routing through use cases and keeping adapters out of core. Still not perfect: sometimes it over-engineers or picks the wrong abstraction. But the rate of cross-layer violations dropped sharply. The model had something to optimize &lt;em&gt;for&lt;/em&gt; instead of only optimizing for "code that runs".&lt;/p&gt;

&lt;p&gt;That is one data point. It fits the hypothesis: &lt;strong&gt;explicit boundaries plus enforcement reduce structural drift&lt;/strong&gt;, even when the code is AI-generated.&lt;/p&gt;

&lt;p&gt;To make this testable we'd need drift metrics (e.g. dependency violations, cross-layer calls), review cost over time, and refactor scope when fixing violations. The hypothesis would be falsified if teams with strict rules drift as much as others, or if review and refactor cost keep growing despite enforcement. I'm preparing concrete ways to define and track these — drift metrics and cost — for follow-up posts.&lt;/p&gt;

&lt;p&gt;Clean Architecture is usually framed as boundaries, inward dependencies, business logic isolated from the rest. True enough — but in an AI-heavy workflow the useful way to see it is: &lt;em&gt;probabilistic execution, deterministic governance&lt;/em&gt;. We are not removing uncertainty. We are putting a box around it so that the model's choices stay inside the box. The architecture becomes the box.&lt;/p&gt;

&lt;p&gt;If you are using AI heavily in development: are your boundaries getting stronger or weaker? Is the cost of keeping the structure in your head going up or down? I don't have a conclusion yet — only a hypothesis. AI has optimized execution; whether we've optimized stability, or are just producing entropy faster, is open. Obvious structures are often the first to dissolve when everything speeds up. In the next posts I'll look at other ways to keep things from liquefying.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>cleanarchitecture</category>
      <category>ai</category>
      <category>softwaredevelopment</category>
    </item>
  </channel>
</rss>
