<?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: Ernesto Herrera Salinas</title>
    <description>The latest articles on DEV Community by Ernesto Herrera Salinas (@ernestohs).</description>
    <link>https://dev.to/ernestohs</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F784815%2Fd5daef24-3e99-433f-819f-5b37dfac6558.png</url>
      <title>DEV Community: Ernesto Herrera Salinas</title>
      <link>https://dev.to/ernestohs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ernestohs"/>
    <language>en</language>
    <item>
      <title>Your Coding Agent Has Amnesia: Memory-Augmented Generation, Explained Through Codex</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Mon, 10 Aug 2026 13:09:13 +0000</pubDate>
      <link>https://dev.to/ernestohs/your-coding-agent-has-amnesia-memory-augmented-generation-explained-through-codex-3fpi</link>
      <guid>https://dev.to/ernestohs/your-coding-agent-has-amnesia-memory-augmented-generation-explained-through-codex-3fpi</guid>
      <description>&lt;h2&gt;
  
  
  The Monday Problem
&lt;/h2&gt;

&lt;p&gt;On Friday, you spent forty minutes teaching Codex about your project. You explained that the backend uses PostgreSQL, that the test suite must run with &lt;code&gt;make test-fast&lt;/code&gt; because the full suite takes twenty minutes, and that the payments module is fragile and should never be refactored without a ticket. Codex worked beautifully for the rest of the session.&lt;/p&gt;

&lt;p&gt;On Monday, you open a new session. Codex suggests refactoring the payments module. It runs the full test suite. It asks what database you use.&lt;/p&gt;

&lt;p&gt;Nothing broke. This is the design. Large language models are stateless functions: everything the model "knows" about your project lives in the context window of the current session, and when the session ends, that context is destroyed. The forty minutes of Friday context did not degrade or get misplaced. It never existed anywhere except in a buffer that no longer exists.&lt;/p&gt;

&lt;p&gt;This article is about the class of techniques built to fix that, known as Memory-Augmented Generation (MAG), and about how OpenAI's Codex implements it in practice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Context Window Is Not Enough
&lt;/h2&gt;

&lt;p&gt;The obvious objection: context windows are huge now, so why not just keep everything in context?&lt;/p&gt;

&lt;p&gt;Three reasons.&lt;/p&gt;

&lt;p&gt;First, context is per-session. A million-token window does not help you on Monday if Friday's session is gone. Window size solves a capacity problem, not a persistence problem.&lt;/p&gt;

&lt;p&gt;Second, long context degrades. Attention effectiveness falls with distance, a failure mode documented as the "lost in the middle" phenomenon: models reliably use information at the beginning and end of a long context but miss information buried in the middle (Liu et al., 2024, &lt;a href="https://arxiv.org/abs/2307.03172" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2307.03172&lt;/a&gt;). Stuffing your entire project history into context is not only expensive, it is unreliable.&lt;/p&gt;

&lt;p&gt;Third, context is undifferentiated. A transcript contains everything: the useful architectural decision and the fourteen failed attempts that preceded it. What you actually want to carry forward is a distilled fact ("we chose SQLAlchemy 2.0 style because of X"), not the raw log that produced it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MAG Is
&lt;/h2&gt;

&lt;p&gt;Memory-Augmented Generation extends an LLM with an external memory system that persists across sessions and is actively managed: written to, consolidated, retrieved from, and pruned. The term was formalized in the MemOS paper (Li et al., 2025, &lt;a href="https://arxiv.org/abs/2505.22101" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2505.22101&lt;/a&gt;), which argued that LLMs need memory as a first-class architectural concern rather than an afterthought, and distinguished three memory types: parametric memory (knowledge baked into weights), activation memory (the ephemeral runtime context), and plaintext memory (external, editable knowledge). Earlier work in the same lineage includes MemGPT (Packer et al., 2023, &lt;a href="https://arxiv.org/abs/2310.08560" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2310.08560&lt;/a&gt;), which treated the LLM like an operating system paging data between a small "main context" and larger external storage.&lt;/p&gt;

&lt;p&gt;The most useful way to understand MAG is by contrast with the pattern everyone already knows: Retrieval-Augmented Generation.&lt;/p&gt;

&lt;p&gt;RAG retrieves. MAG remembers. RAG pulls relevant chunks from a static external corpus at query time; the corpus does not learn anything from the interaction. MAG adds a write path and a lifecycle: the system decides what is worth keeping from an interaction, merges it with what it already knows, retrieves it later, and eventually forgets what stopped being useful.&lt;/p&gt;

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

&lt;p&gt;The loop on the right is the defining feature. A RAG system with a read-only vector database is not doing MAG, no matter how sophisticated the retrieval. MAG requires the write, consolidate, and forget stages. Pruning runs as a background sweep over the store rather than sitting inline in the loop, which matters later when we look at how Codex schedules it.&lt;/p&gt;

&lt;p&gt;One terminology caveat for the careful reader: MAG is an emerging term, not a settled standard like RAG. Much of the industry still says "agent memory," and there is a separate framework called MMAG (Mixed Memory-Augmented Generation, Zeppieri, 2025, &lt;a href="https://arxiv.org/abs/2512.01710" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2512.01710&lt;/a&gt;) that organizes agent memory into five cognitive layers. Do not confuse the two.&lt;/p&gt;

&lt;h2&gt;
  
  
  Case Study: How Codex Remembers
&lt;/h2&gt;

&lt;p&gt;Codex, OpenAI's coding agent, ships a two-layer memory model that maps almost perfectly onto the static-versus-lifecycle distinction above. Both layers are documented at &lt;a href="https://developers.openai.com/codex" rel="noopener noreferrer"&gt;https://developers.openai.com/codex&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: AGENTS.md, the static layer
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;AGENTS.md&lt;/code&gt; is a markdown instruction file that Codex reads at the start of every session. It follows a cross-tool open convention (&lt;a href="https://agents.md" rel="noopener noreferrer"&gt;https://agents.md&lt;/a&gt;) also used by Cursor, Aider, and others. Codex discovers these files hierarchically: a global one at &lt;code&gt;~/.codex/AGENTS.md&lt;/code&gt;, then every &lt;code&gt;AGENTS.md&lt;/code&gt; from the repository root down to the working directory, concatenated in path order.&lt;/p&gt;

&lt;p&gt;This layer is for stable facts: the test command, the deploy process, code style rules, which modules are fragile. It is source-controlled, team-shareable, and fully under human control.&lt;/p&gt;

&lt;p&gt;It is also, strictly speaking, not memory. It is configuration. A human writes it, a human maintains it, and it captures only what someone remembered to write down. The Redis quirk you discovered on Tuesday afternoon does not appear in &lt;code&gt;AGENTS.md&lt;/code&gt; unless you put it there. And there is a hard practical limit: the combined file content is capped at 32 KiB by default, and truncation past the cap is silent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: Memories, the generated layer
&lt;/h3&gt;

&lt;p&gt;The second layer is where Codex actually implements MAG. Codex summarizes its own prior sessions in the background and writes the results to &lt;code&gt;~/.codex/memories/&lt;/code&gt;, which subsequent sessions read. Note that this layer is off by default: you have to enable it in &lt;code&gt;~/.codex/config.toml&lt;/code&gt; (see the config reference in the official docs). The pipeline, per OpenAI's documentation and the analysis at &lt;a href="https://mem0.ai/blog/how-memory-works-in-codex-cli" rel="noopener noreferrer"&gt;https://mem0.ai/blog/how-memory-works-in-codex-cli&lt;/a&gt;, works like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7vjha4oe6ceh7ioip196.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7vjha4oe6ceh7ioip196.png" alt="How memory codex works" width="590" height="1090"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Several design choices are worth noticing because they generalize beyond Codex:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consolidation is asynchronous.&lt;/strong&gt; A session must be idle for hours before it becomes eligible. Memory formation happens offline, not inline with generation, which keeps the interactive loop fast. This mirrors a broader industry pattern of background "sleep-time" consolidation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two models, two jobs.&lt;/strong&gt; One model extracts candidate memories from a session; a second merges candidates into the existing store. Extraction and consolidation are different problems, and separating them lets each be tuned independently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forgetting is a feature.&lt;/strong&gt; Memories that go unrecalled for thirty days are pruned. This is the counterintuitive part of MAG design: an ever-growing memory store degrades retrieval quality and accumulates stale facts, so deliberate forgetting improves the system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Storage is plain markdown, and retrieval is grep.&lt;/strong&gt; No vector database. At session start, Codex reads a consolidated &lt;code&gt;memory_summary.md&lt;/code&gt; whole (third-party analysis of the open-source CLI puts a cap of roughly 5,000 tokens on this injection, a budget decision that parallels the 32 KiB AGENTS.md ceiling), then instructs the agent to grep over the long-form &lt;code&gt;MEMORY.md&lt;/code&gt; when it needs detail. This is a real engineering tradeoff: lexical retrieval is fast, predictable, and debuggable (you can &lt;code&gt;cat&lt;/code&gt; your agent's memory), but it cannot match a stored fact whose phrasing differs from the query. Embedding-based systems invert that tradeoff.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trying it yourself
&lt;/h3&gt;

&lt;p&gt;The experiment is simple, but two prerequisites will silently sink it if you skip them. First, Memories must be enabled in &lt;code&gt;~/.codex/config.toml&lt;/code&gt;; on a default install the feature is off and nothing will be written. Second, if your account is in the EEA, UK, or Switzerland, the Memories layer is not available at launch, and only the AGENTS.md layer applies.&lt;/p&gt;

&lt;p&gt;With that out of the way:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run a session and establish a project-specific fact conversationally, not via &lt;code&gt;AGENTS.md&lt;/code&gt;. Something distinctive enough to be unambiguous, like a made-up internal codename for a service.&lt;/li&gt;
&lt;li&gt;Close the session and wait past the idle window (six hours by default).&lt;/li&gt;
&lt;li&gt;Inspect &lt;code&gt;~/.codex/memories/&lt;/code&gt; and read &lt;code&gt;memory_summary.md&lt;/code&gt;. Did the fact survive extraction and consolidation, and in what form?&lt;/li&gt;
&lt;li&gt;Start a new session and ask a question that requires the fact. Check whether Codex recalls it unprompted, and whether it greps the long-form memory to do so.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The interesting result is not step 4 succeeding; it is comparing what you said in step 1 with what got written in step 3. The distance between the two is the extraction model's editorial judgment, and it is worth seeing with your own eyes before you trust it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where It Breaks
&lt;/h2&gt;

&lt;p&gt;An honest treatment of MAG requires the failure modes, because they are not solved problems, in Codex or anywhere else.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrong memories compound.&lt;/strong&gt; If the extraction model records a misleading conclusion in week one, the agent confidently applies it in week four. Codex does ship provenance in the narrow sense: memory entries can be traced back to the session files and line ranges they came from via citation blocks. What is missing is dispute semantics. There is no mechanism to mark a memory as contested or superseded; correction happens implicitly through consolidation, if it happens at all. Traceability tells you where a bad memory came from. It does not stop the agent from acting on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Staleness.&lt;/strong&gt; Code changes; memories do not automatically notice. The memory saying "deploys go through &lt;code&gt;make ship&lt;/code&gt;" survives the migration to a new deploy pipeline until it is either pruned by disuse or overwritten by a newer session. In the gap, the agent is confidently wrong, which is worse than ignorant.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No sharing, no sync.&lt;/strong&gt; Codex memories are local, per-user generated state. A second laptop starts cold. A new teammate inherits nothing from the team's accumulated agent context except what made it into the checked-in &lt;code&gt;AGENTS.md&lt;/code&gt;. This is exactly the gap that external memory layers (Mem0, and open-source projects like agentmemory) exist to fill via MCP, at the cost of adding a dependency and, for hosted options, sending your project context to a third party. Disclosure: Mem0 sells exactly this layer, so its analysis of Codex's gaps, cited above, should be read with that in mind. The gaps are real; the framing is a sales funnel.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy surface.&lt;/strong&gt; A system that automatically summarizes everything you do and writes it to disk is a system that can memorize secrets. Codex ships secret redaction in the pipeline, but redaction is pattern-matching, and pattern-matching misses things.&lt;/p&gt;

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

&lt;p&gt;MAG is the recognition that statelessness, the property that made LLMs simple to reason about, is the main obstacle to making them useful collaborators over time. Codex's implementation shows what the pattern buys, an agent that stops asking what database you use, and how early we still are: the failure modes above are open problems, not edge cases. RAG took roughly three years to go from the 2020 paper to standard practice. Memory looks to be on a similar trajectory, and those open problems are where the next few years of work will happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Li, Z. et al. (2025). MemOS: An Operating System for Memory-Augmented Generation (MAG) in Large Language Models. &lt;a href="https://arxiv.org/abs/2505.22101" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2505.22101&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Packer, C. et al. (2023). MemGPT: Towards LLMs as Operating Systems. &lt;a href="https://arxiv.org/abs/2310.08560" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2310.08560&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Liu, N. F. et al. (2024). Lost in the Middle: How Language Models Use Long Contexts. &lt;a href="https://arxiv.org/abs/2307.03172" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2307.03172&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Zeppieri, S. (2025). MMAG: Mixed Memory-Augmented Generation for Large Language Models Applications. &lt;a href="https://arxiv.org/abs/2512.01710" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2512.01710&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAI. Codex documentation: AGENTS.md guide, Memories, and config reference. &lt;a href="https://developers.openai.com/codex" rel="noopener noreferrer"&gt;https://developers.openai.com/codex&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AGENTS.md open specification. &lt;a href="https://agents.md" rel="noopener noreferrer"&gt;https://agents.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Sangshetti, H. (2026). Codex CLI Memory: How It Works. Mem0 blog. &lt;a href="https://mem0.ai/blog/how-memory-works-in-codex-cli" rel="noopener noreferrer"&gt;https://mem0.ai/blog/how-memory-works-in-codex-cli&lt;/a&gt; (third-party analysis by a vendor selling an external memory layer; used where official docs are thin, with that conflict of interest in mind)&lt;/li&gt;
&lt;li&gt;Codex Knowledge Base (2026). Codex CLI Memory Internals: Pipelines, Secret Sanitisation and Intelligent Forgetting. &lt;a href="https://codex.danielvaughan.com/2026/04/08/codex-cli-memory-internals/" rel="noopener noreferrer"&gt;https://codex.danielvaughan.com/2026/04/08/codex-cli-memory-internals/&lt;/a&gt; (third-party analysis of the open-source CLI; source for the citation-block provenance mechanism and the memory summary token cap)&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>The one seam, shown: Inline up close</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Thu, 30 Jul 2026 00:47:19 +0000</pubDate>
      <link>https://dev.to/ernestohs/the-one-seam-shown-inline-up-close-12np</link>
      <guid>https://dev.to/ernestohs/the-one-seam-shown-inline-up-close-12np</guid>
      <description>&lt;p&gt;In post 10 I closed the composition-versus-coherence question with one paragraph: I&lt;br&gt;
kept strict object-scoping, reserved an &lt;code&gt;Inline&lt;/code&gt; operator for later, and rejected&lt;br&gt;
automatic reach-down. That was true, and it was too fast. A reader told me the&lt;br&gt;
reserved operator was not clear from a sentence, which is fair. A design record that&lt;br&gt;
asserts a decision without showing it is not really a record. So here is the seam,&lt;br&gt;
worked out.&lt;/p&gt;

&lt;p&gt;First, the good news that made this only one seam and not ten: composition and&lt;br&gt;
coherence mostly do not collide. Composition substitutes complex &lt;em&gt;types&lt;/em&gt;; coherence&lt;br&gt;
binds scalar &lt;em&gt;facets&lt;/em&gt;; those are disjoint kinds of member. A collection gives one&lt;br&gt;
persona per element. Draw order falls out of the eager construction rule from post 7.&lt;br&gt;
And the resolver pipeline I pre-paid for back in post 4 turned out to be coherence's&lt;br&gt;
host. The two threads layer cleanly almost everywhere. Almost.&lt;/p&gt;
&lt;h2&gt;
  
  
  The discontinuity
&lt;/h2&gt;

&lt;p&gt;Coherence is object-scoped (post 5). That one rule has a consequence that only shows&lt;br&gt;
up once you have composition encouraging you to split a type across nested objects:&lt;br&gt;
moving a facet into a child changes whether it coheres.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Flat: Email is a Person facet, so it coheres with the name.&lt;/span&gt;
&lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;//   -&amp;gt; "Maria", "Gonzalez", "maria.gonzalez@..."&lt;/span&gt;

&lt;span class="c1"&gt;// Decomposed: Contact is its own scope. A lone Email there does not activate a&lt;/span&gt;
&lt;span class="c1"&gt;// persona (one corroborating member, no name anchor), so it is a plain, unrelated email.&lt;/span&gt;
&lt;span class="n"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Contact&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ContactInfo&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;//   -&amp;gt; "Maria", "Gonzalez", "rwilson@..."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same three fields, same intent, different result, decided entirely by which object&lt;br&gt;
they live on. That is the discontinuity.&lt;/p&gt;
&lt;h2&gt;
  
  
  The options
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A, strict: object-scoping stays. The decomposed email does not cohere.
   Maximally predictable. The gap is the already-deferred cross-entity work.

B, reach-down: a child with no entity of its own is absorbed into the parent scope.
   The email coheres. But "absorbed or not" now depends on hidden conditions.

C, Inline: an explicit operator that declares a child shares the parent's scope.
   Coheres when you say so, stays strict otherwise.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;I kept A as the v1.1 default and reserved C. The rest of this post is C, shown,&lt;br&gt;
because a reserved operator you cannot picture is not a real reservation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Inline, in three scenarios
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Inline&lt;/code&gt; declares that a nested child shares its parent's scope: its scalar facets&lt;br&gt;
lift into the parent's bundle. The child is still a real object; only its coherence&lt;br&gt;
scoping changes.&lt;/p&gt;

&lt;p&gt;Scenario one, the plain case. It pulls a value-object's fields into the persona:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;Lie&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Define&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Customer&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Inline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Contact&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;// Contact.Email/Phone join Customer's persona&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;//   FirstName="Maria"  LastName="Gonzalez"&lt;/span&gt;
&lt;span class="c1"&gt;//   Contact.Email="maria.gonzalez@..."   Contact.Phone= the Person's phone&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scenario two, where it changes &lt;em&gt;activation&lt;/em&gt;, not just correlation. A parent with only&lt;br&gt;
an &lt;code&gt;Email&lt;/code&gt; and a nested &lt;code&gt;{ First, Last }&lt;/code&gt; activate nothing on their own; inlined, they&lt;br&gt;
clear the gate together and become one person:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Account { Email, Name : NameParts { First, Last } }&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Inline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//   Name.First="David"  Name.Last="Okafor"&lt;/span&gt;
&lt;span class="c1"&gt;//   Email="david.okafor@..."   &amp;lt;- now the same Person; without Inline it was unrelated&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scenario three, the one that turns a silent wrong-look into a fix. Decompose an&lt;br&gt;
address across two blocks and each block coheres into a &lt;em&gt;different&lt;/em&gt; real place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Office { City, State, Postal : PostalBlock { PostalCode, Country } }&lt;/span&gt;
&lt;span class="c1"&gt;// default:  City/State = Austin, Texas   and   PostalCode/Country = a German ZIP + Germany&lt;/span&gt;
&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Inline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;o&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Postal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;//   one real row projects across both: Austin, Texas, 78701, United States&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each block was internally consistent and the object as a whole was nonsense. &lt;code&gt;Inline&lt;/code&gt;&lt;br&gt;
makes the two blocks one row.&lt;/p&gt;
&lt;h2&gt;
  
  
  What Inline does not do
&lt;/h2&gt;

&lt;p&gt;A reserved feature is only honest if its limits are stated with its powers. &lt;code&gt;Inline&lt;/code&gt;&lt;br&gt;
does not do cross-entity correlation: an inlined person and an inlined address are&lt;br&gt;
still independent bundles, so the person does not "live at" the address. It does not&lt;br&gt;
split a collapsed same-type scope (two people flat on one object is still the deferred&lt;br&gt;
case). And it loses to explicit rules, like every automatic-tier facet does (post 8).&lt;/p&gt;
&lt;h2&gt;
  
  
  Why reach-down lost
&lt;/h2&gt;

&lt;p&gt;The deciding argument is the same spine the whole series runs on. &lt;code&gt;Inline&lt;/code&gt; is a line&lt;br&gt;
in your definition, so &lt;code&gt;Explain&lt;/code&gt; can name it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Office.Postal.PostalCode -&amp;gt; Address.PostalCode   [coherent entity]  (inlined from Postal)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reach-down would produce the same coherent values with no declaration anywhere to&lt;br&gt;
point at. The report could only say "absorbed," and you would have nothing in your&lt;br&gt;
own code that explains why. A coherent value you cannot trace is exactly the kind of&lt;br&gt;
magic the rigor lane exists to avoid.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Show, do not assert: a reserved operator you cannot picture is not a reservation, it&lt;br&gt;
is a promissory note. And when two features meet, prefer the seam you can explain over&lt;br&gt;
the convenience you cannot. &lt;code&gt;Inline&lt;/code&gt; ships later, alongside the cross-entity work it&lt;br&gt;
belongs with, but the default it sits on top of, strict and legible, is the v1.1&lt;br&gt;
decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;This second pass through the design added exactly one decision and a lot of&lt;br&gt;
consolidation. The last post is about that: the ledger of everything I have promised&lt;br&gt;
but not shipped.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Skill Opt: Training the Skill File Instead of the Model</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Mon, 27 Jul 2026 13:32:44 +0000</pubDate>
      <link>https://dev.to/ernestohs/skill-opt-training-the-skill-file-instead-of-the-model-4oa2</link>
      <guid>https://dev.to/ernestohs/skill-opt-training-the-skill-file-instead-of-the-model-4oa2</guid>
      <description>&lt;p&gt;I started with a weird question:&lt;/p&gt;

&lt;p&gt;What if I cannot train the model, but I can train the instructions around the model?&lt;/p&gt;

&lt;p&gt;Not fine-tuning. Not LoRA. Not changing weights. I mean taking a closed model, wrapping it with a &lt;code&gt;SKILL.md&lt;/code&gt; file, running benchmarks against it, editing the skill, measuring again, and repeating the loop until the benchmark improves.&lt;/p&gt;

&lt;p&gt;Basically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sentences become parameters.&lt;/li&gt;
&lt;li&gt;Edits become optimization steps.&lt;/li&gt;
&lt;li&gt;Benchmark score becomes the loss signal.&lt;/li&gt;
&lt;li&gt;The model stays frozen.&lt;/li&gt;
&lt;li&gt;The skill gets trained.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That idea is not as strange as it sounds. It sits at the intersection of Agent Skills, automatic prompt optimization, textual gradients, eval-driven development, and black-box optimization.&lt;/p&gt;

&lt;p&gt;I would call this idea &lt;strong&gt;Skill Opt&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The model is closed, but the behavior is not
&lt;/h2&gt;

&lt;p&gt;A closed model does not give you access to weights, gradients, optimizer state, or training data. You cannot backpropagate through it in the classical machine-learning sense.&lt;/p&gt;

&lt;p&gt;But modern AI systems are not only the model. They are the model plus instructions, tools, retrieval, examples, policies, workflows, and context.&lt;/p&gt;

&lt;p&gt;Agent Skills make this explicit. The Agent Skills specification defines a skill as a directory that contains, at minimum, a &lt;code&gt;SKILL.md&lt;/code&gt; file. That file contains YAML frontmatter plus Markdown instructions. The required frontmatter fields are &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;description&lt;/code&gt;, and the Markdown body contains the operational instructions the agent should follow. Skills can also include scripts, references, assets, and other supporting files.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;SKILL.md&lt;/code&gt; is not just documentation. It is executable behavior in text form. The agent reads it, decides when it applies, and uses it to guide actions.&lt;/p&gt;

&lt;p&gt;That means the model is frozen, but the system behavior is still tunable.&lt;/p&gt;

&lt;p&gt;That is the core of Skill Opt.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;SKILL.md&lt;/code&gt; as a parameter surface
&lt;/h2&gt;

&lt;p&gt;In normal training, parameters are numbers. You compute a loss, estimate a gradient, update the numbers, and run again.&lt;/p&gt;

&lt;p&gt;In Skill Opt, the parameters are not numbers. They are pieces of text:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Machine learning concept&lt;/th&gt;
&lt;th&gt;Skill Opt equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Model weights&lt;/td&gt;
&lt;td&gt;Sentences, examples, constraints, workflows, and scripts in &lt;code&gt;SKILL.md&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Forward pass&lt;/td&gt;
&lt;td&gt;Run the agent against an eval prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prediction&lt;/td&gt;
&lt;td&gt;The artifact, answer, code, file, or action produced by the agent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Loss&lt;/td&gt;
&lt;td&gt;Failed assertions, low benchmark score, wrong tool usage, wasted tokens, wrong skill triggering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gradient&lt;/td&gt;
&lt;td&gt;Natural-language critique explaining what went wrong&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Optimizer step&lt;/td&gt;
&lt;td&gt;Edit the skill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Epoch&lt;/td&gt;
&lt;td&gt;One full benchmark pass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation set&lt;/td&gt;
&lt;td&gt;Prompts not used while editing the skill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Overfitting&lt;/td&gt;
&lt;td&gt;A skill that passes your exact tests but fails on real prompts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The Agent Skills docs already recommend a version of this loop for descriptions. They suggest building labeled trigger queries, running them multiple times because model behavior is nondeterministic, measuring trigger rate, splitting train and validation sets, revising only from train failures, and selecting the best version by validation pass rate.&lt;/p&gt;

&lt;p&gt;For output quality, they recommend structured eval cases with realistic prompts, expected outputs, optional files, baseline comparisons, assertions, grading evidence, aggregate pass rates, token counts, timing, human review, and iteration.&lt;/p&gt;

&lt;p&gt;That is already optimization. Skill Opt just makes the analogy explicit and extends it from the &lt;code&gt;description&lt;/code&gt; field to the whole skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  The research already points in this direction
&lt;/h2&gt;

&lt;p&gt;The closest research family is automatic prompt optimization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic Prompt Engineer&lt;/strong&gt;, or APE, treats instructions as programs. It asks an LLM to generate candidate instructions, evaluates them with a score function, and keeps the best candidates. The paper reports that automatically generated instructions outperform a prior LLM baseline and are better than or comparable to human-written instructions on 19 of 24 tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Automatic Prompt Optimization with “Gradient Descent” and Beam Search&lt;/strong&gt; gets even closer to the Skill Opt metaphor. It uses minibatches of examples to produce natural-language “gradients” that criticize the current prompt, then edits the prompt in the opposite semantic direction of that critique. The method is guided by beam search and bandit selection, and the paper reports improvements of up to 31 percent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;OPRO&lt;/strong&gt;, or Optimization by PROmpting, treats the LLM itself as the optimizer. It gives the model previous candidate solutions and their scores, then asks it to generate better candidates. In prompt optimization experiments, OPRO found prompts that outperformed human-designed prompts on GSM8K and Big-Bench Hard tasks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TextGrad&lt;/strong&gt; takes the analogy even further. Instead of numerical gradients, it uses natural-language feedback from LLMs to improve components of compound AI systems. The authors frame this as automatic “differentiation” via text and report improvements across question answering, coding, reasoning prompts, molecule optimization, and radiotherapy treatment planning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DSPy&lt;/strong&gt; abstracts language-model pipelines as text transformation graphs and uses a compiler to optimize them against a metric. This is important because Skill Opt is not only prompt editing. It is closer to optimizing a small language-model program made of instructions, examples, scripts, and resources.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MIPRO&lt;/strong&gt; optimizes instructions and demonstrations for multi-stage language-model programs without access to module-level labels or gradients. That maps well to skills because a good skill often includes multiple steps where only the final artifact is scored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GEPA&lt;/strong&gt; uses natural-language reflection and Pareto-style evolution to improve prompts in compound AI systems. This is highly relevant because skill quality is multi-objective: correctness, cost, latency, safety, and maintainability can all matter at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PromptBreeder&lt;/strong&gt; evolves task prompts and also evolves the mutation prompts that generate new task prompts. That is very close to the idea of an optimizer improving not only the skill, but also the way skill edits are proposed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Self-Refine&lt;/strong&gt; and &lt;strong&gt;Reflexion&lt;/strong&gt; show that LLM systems can improve outputs or future behavior through iterative feedback without changing model weights. Self-Refine uses the same model to generate feedback and revise its own output, while Reflexion stores verbal lessons learned in memory to improve future trials.&lt;/p&gt;

&lt;p&gt;Together, these papers point to the same conclusion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even when model weights are fixed, text around the model can be searched, scored, criticized, mutated, selected, and improved.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Skill Opt applies that pattern to &lt;code&gt;SKILL.md&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Skill Opt loop
&lt;/h2&gt;

&lt;p&gt;A practical Skill Opt loop would look 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;skill_version = SKILL.md v1

repeat:
  run benchmark prompts with skill_version
  collect outputs, tool calls, token usage, duration, and failures
  grade with assertions, scripts, LLM judges, and human review
  summarize failures as natural-language gradients
  propose candidate edits to SKILL.md
  run candidates on train evals
  promote the best candidate by validation score
  save the winning version
until validation score stops improving
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical part is that the benchmark must be external to the optimizer. If you edit the skill using all test cases, you are not training. You are memorizing.&lt;/p&gt;

&lt;p&gt;The Agent Skills docs explicitly warn about this for description optimization: split the query set into train and validation, use only train failures to guide changes, and select the best version by validation pass rate.&lt;/p&gt;

&lt;p&gt;The same rule should apply to the entire skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  What exactly gets optimized?
&lt;/h2&gt;

&lt;p&gt;A skill has several trainable surfaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The description
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;description&lt;/code&gt; field is the trigger. If it is too vague, the skill does not activate. If it is too broad, it activates when it should not.&lt;/p&gt;

&lt;p&gt;The Agent Skills docs say the description is the primary mechanism agents use to decide whether to load a skill for a task.&lt;/p&gt;

&lt;p&gt;So description optimization is like optimizing the router.&lt;/p&gt;

&lt;p&gt;A weak description:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Helps with reports.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A trained description:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;Use this skill when the user asks to turn research notes, links,&lt;/span&gt;
  &lt;span class="s"&gt;benchmark findings, or technical analysis into a structured engineering&lt;/span&gt;
  &lt;span class="s"&gt;article. Apply it for DEV-style posts, architecture writeups,&lt;/span&gt;
  &lt;span class="s"&gt;implementation retrospectives, and source-backed technical explainers.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is not just better writing. It changes when the skill enters the context.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The body instructions
&lt;/h3&gt;

&lt;p&gt;The body of &lt;code&gt;SKILL.md&lt;/code&gt; is the behavioral contract. It can define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Steps to follow.&lt;/li&gt;
&lt;li&gt;Order of operations.&lt;/li&gt;
&lt;li&gt;Quality bars.&lt;/li&gt;
&lt;li&gt;Edge cases.&lt;/li&gt;
&lt;li&gt;Formatting rules.&lt;/li&gt;
&lt;li&gt;Forbidden shortcuts.&lt;/li&gt;
&lt;li&gt;Validation steps.&lt;/li&gt;
&lt;li&gt;Examples.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Agent Skills specification recommends step-by-step instructions, examples of inputs and outputs, and common edge cases in the Markdown body.&lt;/p&gt;

&lt;p&gt;Optimizing this section is like optimizing the policy the agent follows after activation.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Examples
&lt;/h3&gt;

&lt;p&gt;Examples are high leverage. They show the model what good output looks like.&lt;/p&gt;

&lt;p&gt;In Skill Opt, examples are not decoration. They are training-like demonstrations embedded in the runtime context.&lt;/p&gt;

&lt;p&gt;You can add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Positive examples.&lt;/li&gt;
&lt;li&gt;Negative examples.&lt;/li&gt;
&lt;li&gt;Edge cases.&lt;/li&gt;
&lt;li&gt;Before and after transformations.&lt;/li&gt;
&lt;li&gt;“Do not do this” examples.&lt;/li&gt;
&lt;li&gt;Good and bad final artifacts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then you measure whether those examples improve benchmark performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. References
&lt;/h3&gt;

&lt;p&gt;References are long-form knowledge. They should not all live inside &lt;code&gt;SKILL.md&lt;/code&gt;, because loading too much context can waste tokens and confuse the agent.&lt;/p&gt;

&lt;p&gt;The Agent Skills specification recommends using focused reference files and loading them on demand. It also recommends keeping the main &lt;code&gt;SKILL.md&lt;/code&gt; under 500 lines and moving detailed reference material to separate files.&lt;/p&gt;

&lt;p&gt;Skill Opt should measure whether moving content into references improves quality, cost, or reliability.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Scripts
&lt;/h3&gt;

&lt;p&gt;Scripts turn fragile natural-language instructions into deterministic operations.&lt;/p&gt;

&lt;p&gt;The Agent Skills specification allows optional &lt;code&gt;scripts/&lt;/code&gt; directories for executable code, and the evaluation guide recommends verification scripts for checks like valid JSON, correct row counts, file existence, and expected dimensions.&lt;/p&gt;

&lt;p&gt;So Skill Opt should not only ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Can better wording fix this?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It should also ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Should this instruction be code instead of text?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The benchmark is the loss
&lt;/h2&gt;

&lt;p&gt;A skill without evals is just a belief.&lt;/p&gt;

&lt;p&gt;To optimize a skill, you need a benchmark suite. At minimum, each eval should include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A realistic user prompt.&lt;/li&gt;
&lt;li&gt;Expected output.&lt;/li&gt;
&lt;li&gt;Optional input files.&lt;/li&gt;
&lt;li&gt;Assertions.&lt;/li&gt;
&lt;li&gt;A baseline run without the skill or with the previous skill.&lt;/li&gt;
&lt;li&gt;A score.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Agent Skills evaluation guide recommends this exact style of eval-driven workflow: realistic prompts, expected outputs, optional files, with-skill and without-skill comparisons, assertions, grading evidence, aggregate pass rates, token counts, timing, and human review.&lt;/p&gt;

&lt;p&gt;That gives you several possible loss signals:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Failed assertions.&lt;/li&gt;
&lt;li&gt;Lower human preference score.&lt;/li&gt;
&lt;li&gt;Worse LLM judge score.&lt;/li&gt;
&lt;li&gt;More tokens for the same quality.&lt;/li&gt;
&lt;li&gt;Longer runtime.&lt;/li&gt;
&lt;li&gt;Wrong files created.&lt;/li&gt;
&lt;li&gt;Wrong tool used.&lt;/li&gt;
&lt;li&gt;Skill failed to trigger.&lt;/li&gt;
&lt;li&gt;Skill triggered when it should not.&lt;/li&gt;
&lt;li&gt;Unsafe or over-permissive behavior.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good optimizer does not chase only one number. This is why GEPA’s Pareto-style approach is interesting: it treats optimization as a search across multiple objectives rather than a single scalar score.&lt;/p&gt;

&lt;h2&gt;
  
  
  The “gradient” is critique
&lt;/h2&gt;

&lt;p&gt;The gradient in Skill Opt is not a vector. It is a diagnosis.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Failure pattern:
The agent produced a correct article structure, but it repeatedly forgot to include sources after technical claims.

Suggested semantic update:
Add a mandatory citation rule under the "Source handling" section. Require citations at the paragraph level for all factual claims derived from research, and require a final source list.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That critique becomes an edit.&lt;/p&gt;

&lt;p&gt;This is the spirit of textual-gradient methods, but there is an important caveat: the gradient metaphor is useful, not literal. A 2025 paper argues that textual gradients often improve prompts, but the gradient analogy does not fully explain their behavior.&lt;/p&gt;

&lt;p&gt;That warning matters.&lt;/p&gt;

&lt;p&gt;Skill Opt should not pretend text optimization is mathematically equivalent to gradient descent. It should borrow the discipline of optimization without pretending the math is the same.&lt;/p&gt;

&lt;h2&gt;
  
  
  Skills are becoming software artifacts
&lt;/h2&gt;

&lt;p&gt;The newest research makes Skill Opt feel less like a hack and more like an emerging engineering discipline.&lt;/p&gt;

&lt;p&gt;A 2026 survey describes agent skills as composable packages of instructions, code, and resources that agents load on demand to extend capability without retraining.&lt;/p&gt;

&lt;p&gt;A 2026 data-driven analysis of 40,285 public skills describes skills as reusable, program-like modules that define triggering conditions, procedural logic, and tool interactions. It also identifies safety risks, including skills that enable state-changing or system-level actions.&lt;/p&gt;

&lt;p&gt;SkillsBench evaluates whether skills actually help. It reports that curated skills raise average pass rate by 16.2 percentage points across tested tasks, but effects vary by domain and some tasks show negative deltas. That is an important finding: skills can help, but they can also hurt.&lt;/p&gt;

&lt;p&gt;A July 2026 paper studies how AI agent skills are written, adapted, and maintained. It treats skills as engineered artifacts whose content and evolution shape agent behavior. That framing is exactly what Skill Opt needs.&lt;/p&gt;

&lt;p&gt;EvoSkills proposes self-evolving skill packages using a generator and verifier loop. It reports higher pass rates than several baselines on SkillsBench across Claude Code, Codex, and other LLMs.&lt;/p&gt;

&lt;p&gt;AgentSkillOS looks at selection, orchestration, and benchmarking of skills at ecosystem scale, using capability trees, retrieval, DAG-based orchestration, and pairwise evaluation.&lt;/p&gt;

&lt;p&gt;SkCC proposes compiling skills into a strongly typed intermediate representation to improve portability and security across agent frameworks. It reports pass-rate improvements, security-trigger results, and token savings.&lt;/p&gt;

&lt;p&gt;All of this points in the same direction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Skills are not just notes for the model. They are software artifacts that deserve tests, versioning, benchmarks, security review, and optimization.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The security problem: optimizing text can optimize bad behavior too
&lt;/h2&gt;

&lt;p&gt;There is a dark side.&lt;/p&gt;

&lt;p&gt;If &lt;code&gt;SKILL.md&lt;/code&gt; is operational text, then malicious or careless text can change agent behavior.&lt;/p&gt;

&lt;p&gt;A 2026 paper on semantic supply-chain attacks argues that &lt;code&gt;SKILL.md&lt;/code&gt; is not passive documentation. It can influence discovery, selection, and governance. The paper reports that short textual triggers can improve adversarial skill visibility, description framing can bias agents toward adversarial variants, and semantic evasion can help malicious skills avoid blocking.&lt;/p&gt;

&lt;p&gt;Another paper argues that Agent Skills enable realistic and simple prompt-injection attacks, including malicious instructions hidden inside long skill files or referenced scripts.&lt;/p&gt;

&lt;p&gt;A 2026 paper on dynamic malicious skills shows that malicious instructions embedded in natural-language documentation, including &lt;code&gt;SKILL.md&lt;/code&gt;, can induce an agent to dynamically inject malicious logic during execution.&lt;/p&gt;

&lt;p&gt;This matters for Skill Opt because optimization pressure can accidentally reward unsafe behavior. If the only benchmark is “complete the task,” the skill may learn to be pushier, skip confirmation, overuse tools, or hide uncertainty.&lt;/p&gt;

&lt;p&gt;So the loss function must include safety:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do not optimize only for pass rate.&lt;/li&gt;
&lt;li&gt;Include permission boundaries.&lt;/li&gt;
&lt;li&gt;Include negative tests.&lt;/li&gt;
&lt;li&gt;Include prompt-injection tests.&lt;/li&gt;
&lt;li&gt;Include tool-use constraints.&lt;/li&gt;
&lt;li&gt;Include human review for high-risk actions.&lt;/li&gt;
&lt;li&gt;Version-control every skill edit.&lt;/li&gt;
&lt;li&gt;Treat third-party skills like code, not like harmless documentation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A skill optimizer without safety evals is just a bug generator with good intentions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Skill Opt is, and what it is not
&lt;/h2&gt;

&lt;p&gt;Skill Opt is not model training.&lt;/p&gt;

&lt;p&gt;It does not make the base model smarter in the weight-level sense. The model has not learned permanently. If you remove the skill, the behavior disappears.&lt;/p&gt;

&lt;p&gt;But Skill Opt can make the system smarter in the engineering sense. The agent behaves better because the context, instructions, examples, scripts, activation rules, and validation loops are better.&lt;/p&gt;

&lt;p&gt;That distinction is important.&lt;/p&gt;

&lt;p&gt;We are not training the model.&lt;/p&gt;

&lt;p&gt;We are training the interface between the model and the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;Most teams cannot train frontier models. They cannot see the weights, modify the architecture, or run gradient descent over the model itself.&lt;/p&gt;

&lt;p&gt;But they can write skills.&lt;/p&gt;

&lt;p&gt;They can benchmark skills.&lt;/p&gt;

&lt;p&gt;They can edit skills.&lt;/p&gt;

&lt;p&gt;They can version skills.&lt;/p&gt;

&lt;p&gt;They can compare skills.&lt;/p&gt;

&lt;p&gt;They can build evals around skills.&lt;/p&gt;

&lt;p&gt;That makes &lt;code&gt;SKILL.md&lt;/code&gt; one of the most practical optimization surfaces available to people using closed AI systems.&lt;/p&gt;

&lt;p&gt;The big idea is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A closed model does not mean a closed system.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If the model is frozen, train the skill.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Agent Skills Overview&lt;br&gt;
URL: &lt;a href="https://agentskills.io/" rel="noopener noreferrer"&gt;https://agentskills.io/&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Agent Skills Specification&lt;br&gt;
URL: &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;https://agentskills.io/specification&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Agent Skills: Optimizing Skill Descriptions&lt;br&gt;
URL: &lt;a href="https://agentskills.io/skill-creation/optimizing-descriptions" rel="noopener noreferrer"&gt;https://agentskills.io/skill-creation/optimizing-descriptions&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Agent Skills: Evaluating Skill Output Quality&lt;br&gt;
URL: &lt;a href="https://agentskills.io/skill-creation/evaluating-skills" rel="noopener noreferrer"&gt;https://agentskills.io/skill-creation/evaluating-skills&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Zhou et al., “Large Language Models Are Human-Level Prompt Engineers”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2211.01910" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2211.01910&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pryzant et al., “Automatic Prompt Optimization with ‘Gradient Descent’ and Beam Search”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2305.03495" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2305.03495&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yang et al., “Large Language Models as Optimizers”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2309.03409" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2309.03409&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Yuksekgonul et al., “TextGrad: Automatic ‘Differentiation’ via Text”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2406.07496" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2406.07496&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Khattab et al., “DSPy: Compiling Declarative Language Model Calls into Self-Improving Pipelines”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2310.03714" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2310.03714&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Opsahl-Ong et al., “Optimizing Instructions and Demonstrations for Multi-Stage Language Model Programs”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2406.11695" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2406.11695&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Agrawal et al., “GEPA: Reflective Prompt Evolution Can Outperform Reinforcement Learning”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2507.19457" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2507.19457&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fernando et al., “Promptbreeder: Self-Referential Self-Improvement Via Prompt Evolution”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2309.16797" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2309.16797&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Madaan et al., “Self-Refine: Iterative Refinement with Self-Feedback”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2303.17651" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2303.17651&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Shinn et al., “Reflexion: Language Agents with Verbal Reinforcement Learning”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2303.11366" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2303.11366&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wang et al., “Voyager: An Open-Ended Embodied Agent with Large Language Models”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2305.16291" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2305.16291&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Melcer et al., “Textual Gradients are a Flawed Metaphor for Automatic Prompt Optimization”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2512.13598" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2512.13598&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ling et al., “Agent Skills: A Data-Driven Analysis of Claude Skills for Extending Large Language Model Functionality”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2602.08004" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2602.08004&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Li et al., “SkillsBench: Benchmarking How Well Agent Skills Work Across Diverse Tasks”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2602.12670" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2602.12670&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Xu and Yan, “Agent Skills for Large Language Models: Architecture, Acquisition, Security, and the Path Forward”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2602.12430" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2602.12430&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gao et al., “From Registry to Repository: How AI Agent Skills Are Written, Adapted, and Maintained”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2607.00911" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2607.00911&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Zhang et al., “CoEvoSkills: Self-Evolving Agent Skills via Co-Evolutionary Verification”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2604.01687" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2604.01687&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Li et al., “Organizing, Orchestrating, and Benchmarking Agent Skills at Ecosystem Scale”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2603.02176" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2603.02176&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ouyang et al., “SkCC: Portable and Secure Skill Compilation for Cross-Framework LLM Agents”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2605.03353" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2605.03353&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Saha et al., “Under the Hood of SKILL.md: Semantic Supply-chain Attacks on AI Agent Skill Registry”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2605.11418" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2605.11418&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Schmotz et al., “Agent Skills Enable a New Class of Realistic and Trivially Simple Prompt Injections”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2510.26328" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2510.26328&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chen et al., “Dynamic Malicious Skills in Agentic AI”&lt;br&gt;
URL: &lt;a href="https://arxiv.org/abs/2606.16287" rel="noopener noreferrer"&gt;https://arxiv.org/abs/2606.16287&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ai</category>
    </item>
    <item>
      <title>Decided is not done: taking stock before adding more</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Fri, 24 Jul 2026 18:48:20 +0000</pubDate>
      <link>https://dev.to/ernestohs/decided-is-not-done-taking-stock-before-adding-more-4c7</link>
      <guid>https://dev.to/ernestohs/decided-is-not-done-taking-stock-before-adding-more-4c7</guid>
      <description>&lt;p&gt;The first session ended at post 10. The design did not. I came back to it and, before writing a single new decision, asked the least glamorous question a solo project can ask itself: how far along is this, really, and what would it take to call it ready for someone else to review in depth?&lt;/p&gt;

&lt;p&gt;The answer was more useful than I expected, because it forced a distinction I had been blurring: a &lt;em&gt;settled mechanism&lt;/em&gt; is not a &lt;em&gt;hardened design&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fork: promote now, or hold and harden
&lt;/h2&gt;

&lt;p&gt;Composition was already reconciled into the binding docs. Coherence had seventeen recorded decisions covering the whole load-bearing core: binding, determinism, precedence, persona content, gender, explainability, detection, activation, the explicit accessor, and a second entity proving the abstraction generalizes. It was tempting to call that reviewable and promote it too.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A: promote coherence into the binding docs now.
   17 decisions, self-consistent, composition already went. Looks done.

B: hold. The mechanism is settled, but the seams between features are not.
   Harden first, promote second.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I took B. The tell was that I could not yet answer a reviewer's most obvious question, "what happens when a composed child is itself a person," without pointing at an open fork. A design you cannot stress at the seams is decided, not done.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "hardened" actually means
&lt;/h2&gt;

&lt;p&gt;The value of taking stock was turning a vague "almost there" into a concrete,&lt;br&gt;
finite list. Three passes stand between the current state and an in-depth review:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Cross-feature interaction pass.
   Where correctness bugs hide once two features exist. Composition x coherence is
   done (next post). Uniqueness, null-probability, and locale remain.

2. Surface-enumeration pass.
   Collect every public member the design has accumulated into one list to accept
   or cut. Public surface is locked, so this is the gate that matters most.

3. Consistency re-read.
   Read all the decisions straight through for contradictions and stale
   cross-references, the kind that creep in when you decide one fork at a time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after those does coherence get promoted into the binding docs. The interaction&lt;br&gt;
pass goes first on purpose: it is the one most likely to send an earlier decision&lt;br&gt;
back for revision, and there is no point freezing a surface that a later pass might&lt;br&gt;
reopen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is allowed to be slow
&lt;/h2&gt;

&lt;p&gt;This is the brief doing its job again. Solo, no deadline, quality over speed is&lt;br&gt;
exactly the license to harden before review instead of shipping a plausible-looking&lt;br&gt;
design and patching it under pressure later. It is also why I keep refusing to frame&lt;br&gt;
this work as a countdown to code. "Ready to build" is a finish line that a design in&lt;br&gt;
this phase does not have and should not pretend to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Before you add the next feature, find out whether the last ten are actually done or&lt;br&gt;
merely decided. The useful output of taking stock is not a percentage, it is a short&lt;br&gt;
list of named hardening passes that make the gap between "I made these decisions" and&lt;br&gt;
"someone could review this" explicit and finite.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;First pass on the list is the cross-feature interactions, and the sharpest one is&lt;br&gt;
where the two big v1.1 threads touch. Post 10 asserted that boundary in a single&lt;br&gt;
paragraph. A reader, reasonably, asked me to actually show it. Next post: the seam,&lt;br&gt;
worked out in full.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Proving it generalizes: Person to Address</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Tue, 21 Jul 2026 23:25:57 +0000</pubDate>
      <link>https://dev.to/ernestohs/proving-it-generalizes-person-to-address-1kp9</link>
      <guid>https://dev.to/ernestohs/proving-it-generalizes-person-to-address-1kp9</guid>
      <description>&lt;p&gt;A persona engine that only does &lt;code&gt;Person&lt;/code&gt; is a &lt;code&gt;Person&lt;/code&gt; feature wearing the costume of an abstraction. The whole bet behind making coherence a descriptor-driven stage was that a second entity would be nearly free. &lt;code&gt;Address&lt;/code&gt; was the test, and it is where the first session closes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second instance was nearly free
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;Address&lt;/code&gt; coherence means &lt;code&gt;City&lt;/code&gt;, &lt;code&gt;State&lt;/code&gt;, &lt;code&gt;PostalCode&lt;/code&gt;, and &lt;code&gt;Country&lt;/code&gt; agree. It reuses the exact detection and activation machinery from &lt;code&gt;Person&lt;/code&gt;: a second descriptor that claims the existing address generators from the catalog, gated by the same corroboration-count activation. The only structural difference is that &lt;code&gt;Address&lt;/code&gt; has no name anchor (there is no "FirstName of an address"), so its activation gate is pure corroboration count rather than anchor-plus-count. One genuine difference, and the framework absorbed it. That is the signal an abstraction is real.&lt;/p&gt;

&lt;h2&gt;
  
  
  One honest difference: how the bundle draws
&lt;/h2&gt;

&lt;p&gt;The interesting part is where &lt;code&gt;Person&lt;/code&gt; and &lt;code&gt;Address&lt;/code&gt; diverge, because it taught me what the descriptor actually abstracts.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Person:  correlated draws. Draw a gender, then a name that matches it, then ...
Address: row-projection.   You CANNOT synthesize a coherent address field by field
         (a random City plus a random State plus a random ZIP is nonsense).
         So draw ONE real (country, state, city, postal, lat, lng) tuple, and let
         the facets project from that row. Street and countryCode derive.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A descriptor does not just list facets. It owns "how to draw the bundle." For &lt;code&gt;Person&lt;/code&gt; that is a sequence of correlated draws; for &lt;code&gt;Address&lt;/code&gt; it is a single row lookup; the facets project either way. Same stage, same activation, two completely different generation strategies behind one abstraction.&lt;/p&gt;

&lt;h2&gt;
  
  
  A set of entities, not one persona
&lt;/h2&gt;

&lt;p&gt;Generalizing also forced a small reframe. One object can host a &lt;code&gt;Person&lt;/code&gt; and an &lt;code&gt;Address&lt;/code&gt; at the same time, so the activation pre-pass resolves a &lt;em&gt;set&lt;/em&gt; of entities per object, each drawing in registration order. Nested entities fall out of object-scoping for free: a nested &lt;code&gt;Address&lt;/code&gt; is its own scope and coheres on its&lt;br&gt;
own. (Two addresses flat on one object is still the deferred two-people case.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the data comes from
&lt;/h2&gt;

&lt;p&gt;Filling the address row table is a brief-shaped decision. Consistent with owning the rigor lane, core ships a small set of curated, real &lt;code&gt;en&lt;/code&gt; tuples, where coherence is guaranteed because the data is real. Breadth comes through data packs, not core, and synthesis is used only where ground truth is impractical (street&lt;br&gt;
numbers are always synthesized). Rigor in core, breadth at the edges.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where composition and coherence meet
&lt;/h2&gt;

&lt;p&gt;The last thing I checked was whether the two big v1.1 threads collide. Mostly they do not: composition substitutes complex types, coherence binds scalars, disjoint kinds, and the resolver pipeline I pre-paid for is coherence's host. The one genuine fork is a decomposition discontinuity: because coherence is object-scoped,&lt;br&gt;
moving a facet into a nested object changes whether it coheres with its old siblings. I kept strict object-scoping as the v1.1 default (predictable and explainable) and reserved an explicit &lt;code&gt;Inline&lt;/code&gt; operator to ship later alongside cross-entity correlation. Automatic reach-down lost, because cross-boundary magic is exactly the kind of thing that cannot be explained.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;An abstraction earns its keep when the second instance is nearly free and the third is obvious. And when two systems meet, prefer the boundary you can explain over the convenience you cannot. That single rule, local and diagnosable over non-local and silent, turned out to be the spine of nearly every decision in this&lt;br&gt;
series.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this leaves the first session
&lt;/h2&gt;

&lt;p&gt;Coherence is worked through its load-bearing core and stays open on purpose: &lt;code&gt;Company&lt;/code&gt; and temporal entities, cross-entity correlation (the person who lives at the address), and the catalog and dataset work all still have surface left. None of it is built. It is design, made deliberately and held revisitable, which is exactly where I want a library that intends to be the rigorous one to start.&lt;/p&gt;

&lt;p&gt;Thanks for reading along this first pass. The whole point of building in public is that the reasoning is the product as much as the code is.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>A believable person, and making it honest</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Sat, 18 Jul 2026 00:25:51 +0000</pubDate>
      <link>https://dev.to/ernestohs/a-believable-person-and-making-it-honest-1ab4</link>
      <guid>https://dev.to/ernestohs/a-believable-person-and-making-it-honest-1ab4</guid>
      <description>&lt;p&gt;Two things were still missing before the persona was presentable. It had to be fully believable, not half-coherent, and it had to be honest about what it had done. Three decisions covered both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correlate fully, or not at all
&lt;/h2&gt;

&lt;p&gt;The first fork was how deep the correlation goes. Name-coherence only (first, last, full, email, username agree) is cheaper. But half-coherence is its own kind of broken: a &lt;code&gt;FirstName&lt;/code&gt; of "Maria" next to a &lt;code&gt;Salutation&lt;/code&gt; of "Mr.," or an &lt;code&gt;Age&lt;/code&gt; that contradicts the &lt;code&gt;BirthDate&lt;/code&gt;, looks as wrong as fully random data, and the seed/demo reader is exactly the person who notices.&lt;/p&gt;

&lt;p&gt;So the persona is a full bundle. Some facets are drawn, the rest derive with no extra randomness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;drawn:   gender, firstName (conditioned on gender), lastName,
         birthDate (adult-aged, off the operation's reference time),
         phone (locale format), emailDomain
derived: fullName, initials, email, username, salutation (from gender),
         age (from birthDate + reference time)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is not free: it adds a dataset dependency, first names tagged by gender. I accepted that, because the alternative is data that fails the two-second believability test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gender, without shipping a gender enum
&lt;/h2&gt;

&lt;p&gt;The persona needs an internal gender axis to pick a coherent name and salutation.&lt;br&gt;
That raised a question I wanted to handle carefully: does Munchausen now ship a public gender enum? I decided no.&lt;/p&gt;

&lt;p&gt;The fork was whether to leave a model's own gender field alone (and risk it contradicting the name) or bind it. I bind it, by adapting to whatever type the model already declares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Account&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Gender&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                 &lt;span class="c1"&gt;// string -&amp;gt; "Female"&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Profile&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;Sex&lt;/span&gt; &lt;span class="n"&gt;Sex&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Sex&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Female&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Male&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Unspecified&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
                                                                    &lt;span class="c1"&gt;// enum -&amp;gt; Sex.Female&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Contact&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                                &lt;span class="c1"&gt;// no gender field&lt;/span&gt;
                                                                    &lt;span class="c1"&gt;// axis still drives the name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A string member gets &lt;code&gt;"Female"&lt;/code&gt; or &lt;code&gt;"Male"&lt;/code&gt;. An enum member gets the value whose name matches the internal axis, falling back to ordinary inference when nothing matches. Munchausen never defines the categories itself; it mirrors the type the model declares, and the explicit &lt;code&gt;d.Person.Gender&lt;/code&gt; is a plain string for the same&lt;br&gt;
reason. The internal-axis-only option lost because it leaves the gender field free to contradict the name, which is the exact incoherence I was removing, just moved one field over.&lt;/p&gt;
&lt;h2&gt;
  
  
  Make it inspectable
&lt;/h2&gt;

&lt;p&gt;Explainability is a differentiator on the rigor axis, so coherence cannot be a silent behavior. Each persona-bound member reports a new source, &lt;code&gt;InferenceSource.CoherentEntity&lt;/code&gt;, and &lt;code&gt;Explain()&lt;/code&gt; shows it plainly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Contact.FirstName  -&amp;gt; Person.First     [coherent entity]
Contact.LastName   -&amp;gt; Person.Last      [coherent entity]
Contact.Email      -&amp;gt; Person.Email     [coherent entity]   (fulfills [EmailAddress])
Contact.Gender     -&amp;gt; Person.Gender    [coherent entity]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see which members share one identity (they all say &lt;code&gt;Person.*&lt;/code&gt;), and the compose decision from the last post shows up inline on &lt;code&gt;Email&lt;/code&gt;. The same report is what makes the two-people collapse diagnosable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Correlate fully or not at all, because partial coherence reads as broken. Adapt to the types your users declare instead of legislating your own. And never let an automatic behavior be opaque: inference you can read is inference you can trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;A persona engine that only does &lt;code&gt;Person&lt;/code&gt; is just a &lt;code&gt;Person&lt;/code&gt; feature with extra steps. The real test of the abstraction was a second entity. Next post: proving it generalizes, from &lt;code&gt;Person&lt;/code&gt; to &lt;code&gt;Address&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Composing with attributes, not fighting them</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Fri, 17 Jul 2026 01:49:08 +0000</pubDate>
      <link>https://dev.to/ernestohs/composing-with-attributes-not-fighting-them-2gda</link>
      <guid>https://dev.to/ernestohs/composing-with-attributes-not-fighting-them-2gda</guid>
      <description>&lt;p&gt;Every inference feature eventually has to answer one question: who wins when two of us want the same member? For coherence, the sharp version of that question is attributes. A model that annotates its email is both telling the validator something and, now, overlapping with the persona.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Contact&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// persona facet&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;   &lt;span class="c1"&gt;// persona facet&lt;/span&gt;

    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;EmailAddress&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;                           &lt;span class="c1"&gt;// an attribute, on a persona facet&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The persona wants &lt;code&gt;Email&lt;/code&gt; to be &lt;code&gt;maria.garcia@...&lt;/code&gt;, coherent with the name. The &lt;code&gt;[EmailAddress]&lt;/code&gt; attribute wants "an email." A registered custom provider might want it too. So where does the persona sit in the precedence chain?&lt;/p&gt;

&lt;h2&gt;
  
  
  Three options
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A, conservative: persona below attributes.
   [EmailAddress] selects a generic email generator -&amp;gt; "k9x@host.net"
   Valid, but NOT coherent. Annotating your model quietly loses coherence.

B, aggressive: persona above providers and attributes.
   Persona's coherent email wins -&amp;gt; "maria.garcia@ex.com"
   Coherent, but it silently overrides a provider you deliberately registered.

C, compose: persona at the automatic tier.
   It FULFILLS a select attribute coherently ([EmailAddress] is satisfied by a
   coherent, valid email), is NARROWED by a constraint attribute ([StringLength]),
   beats plain semantic/type, and loses to explicit rules and providers.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The choice
&lt;/h2&gt;

&lt;p&gt;C, compose. It is the only one that maintains coherence across annotated models, which is the common shape for seed data, while still letting an explicit provider win, which is what a user most clearly meant.&lt;/p&gt;

&lt;p&gt;What made C feel principled rather than like a special case is that it rests on a distinction the API already had: semantic attributes &lt;em&gt;select&lt;/em&gt; a generator, constraint attributes &lt;em&gt;narrow&lt;/em&gt; values. &lt;code&gt;[EmailAddress]&lt;/code&gt; selects "email," and a coherent email is an email, so the persona fulfills it. &lt;code&gt;[StringLength]&lt;/code&gt; narrows, so the persona facet gets narrowed to fit. The persona did not need a new tier bolted on top of the precedence list. It slotted into a model that was already there.&lt;/p&gt;

&lt;p&gt;The conservative option lost because it punishes you for annotating your model, which is exactly the kind of surprise that erodes trust. The aggressive option lost because "automatic inference silently overrode the provider I registered" is a worse surprise. Compose avoids both: the explicit thing always wins, the automatic thing fills in coherently, and the attribute gets honored either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Precedence is where features negotiate, and the best outcome is usually compose, not override. Before you add a new priority tier, look for the distinction your system already makes (here, select versus narrow) and slot the new feature into it. A feature that fits the existing model is easier to learn and far easier to&lt;br&gt;
trust than one that fights it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;The persona can now bind, draw deterministically, and negotiate precedence. Two things remain before it is presentable: it has to be fully believable, and it has to be honest about what it did. Next post: a believable person, and making it honest.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>The PRNG draw-tape: eager versus lazy</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Wed, 15 Jul 2026 01:24:53 +0000</pubDate>
      <link>https://dev.to/ernestohs/the-prng-draw-tape-eager-versus-lazy-1408</link>
      <guid>https://dev.to/ernestohs/the-prng-draw-tape-eager-versus-lazy-1408</guid>
      <description>&lt;p&gt;Munchausen makes one absolute promise: same seed, same data, forever, on any machine. A persona is "generated once," which raises a question that sounds like an implementation detail and is actually part of the contract: once &lt;em&gt;where&lt;/em&gt; in the random stream? Get this wrong and a harmless code change silently changes everyone&lt;br&gt;
who upgrades.&lt;/p&gt;

&lt;p&gt;Picture the operation's randomness as a tape of draws, &lt;code&gt;d0, d1, d2, ...&lt;/code&gt;, consumed in order. The persona bundle eats a fixed block of them (gender, then a matching first name, then a last name), and any independent member, say &lt;code&gt;Age&lt;/code&gt;, draws on its own. The fork is where the bundle's block lands.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// persona facet&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;    &lt;span class="n"&gt;Age&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// independent&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// persona facet&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;     &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// persona facet (derived from name)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The two options, made concrete
&lt;/h2&gt;

&lt;p&gt;Both are equally coherent. The difference is stability when the model changes.&lt;br&gt;
Watch what happens when &lt;code&gt;Age&lt;/code&gt; moves to the top of the type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A, eager at construction: draw the bundle as a fixed block before any member.
   bundle[d0,d1,d2] -&amp;gt; Age=d3, FirstName/LastName/Email read cached facets
   Move Age to the top? Bundle is still first, person-members consume nothing,
   so NOTHING changes. Age stays 34, the name stays Maria Garcia.

B, lazy at first persona-member: draw the bundle at that member's slot.
   Age=d0 -&amp;gt; FirstName triggers bundle[d1,d2,d3]
   Move Age to the top and the whole tape slides: Age draws d0 instead of d3,
   the bundle shifts, every value changes.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The choice
&lt;/h2&gt;

&lt;p&gt;Eager, at construction. The deciding property is stability. Under eager, the persona draws as one block before member population, and the bound members are invisible to the stream, so reordering fields or adding a new facet does not move the values you already have. That matters most for the seed/demo job, where people edit their models constantly. Eager also fits the eager-Build philosophy of the whole library: the entity is drawn with the object, at a single point a golden test can pin.&lt;/p&gt;

&lt;p&gt;It costs one small, well-defined thing: person-members no longer draw at their own position in member order, which is a deliberate departure from the per-member rule the rest of the library follows. I decided that is a fair price for reorder invariance on exactly the fields people most want to stay put.&lt;/p&gt;

&lt;p&gt;There is one lazy corner, and I want to name it rather than hide it. The explicit &lt;code&gt;d.Person&lt;/code&gt; accessor, read on an object that never activated a persona, materializes one on first access. That single path is order-dependent, because it draws when the delegate runs. It is the documented exception that proves the eager rule, and an&lt;br&gt;
opt-in to force it eager is reserved for later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;In a deterministic system, "when you draw" is as much a part of the contract as "what you draw." When you choose, optimize for the changes your users will actually make. Reordering fields and adding a property are routine, so the draw rule that survives both is the right one, even at the cost of a small inconsistency elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;A persona does not generate in isolation. It has to negotiate with the rest of the inference pipeline, and the sharpest negotiation is with validation attributes.&lt;br&gt;
Next post: composing with attributes, not fighting them.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Binding members to a persona without magic</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Sun, 12 Jul 2026 02:34:17 +0000</pubDate>
      <link>https://dev.to/ernestohs/binding-members-to-a-persona-without-magic-foj</link>
      <guid>https://dev.to/ernestohs/binding-members-to-a-persona-without-magic-foj</guid>
      <description>&lt;p&gt;Automatic coherence sounds like magic, and magic in a library is a liability.&lt;br&gt;
The entire job in this part of the design was making it trustworthy: it has to fire when it should, stay silent when it should not, and be explainable when you ask. Several decisions, one theme.&lt;/p&gt;
&lt;h2&gt;
  
  
  Binding: automatic, with an explicit escape
&lt;/h2&gt;

&lt;p&gt;First, how does a member get attached to its object's persona? I shipped two paths on purpose.&lt;/p&gt;

&lt;p&gt;The primary path is automatic. Semantic inference, which already recognizes &lt;code&gt;FirstName&lt;/code&gt; and &lt;code&gt;Email&lt;/code&gt;, binds those members to the object's persona, so &lt;code&gt;Lie&amp;lt;Customer&amp;gt;.Generate()&lt;/code&gt; comes out coherent with zero configuration. That is the seed/demo win, and it has to be the default because it is the only thing that reaches the headline one-liner.&lt;/p&gt;

&lt;p&gt;The escape path is explicit: a &lt;code&gt;d.Person&lt;/code&gt; accessor you can read inside a &lt;code&gt;With&lt;/code&gt; or &lt;code&gt;Derive&lt;/code&gt; rule when you want precision. It is a complement, not a replacement, and an explicit rule always overrides the automatic binding.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where it lives: a stage, not a patch
&lt;/h2&gt;

&lt;p&gt;Coherence is not bolted onto the semantic matcher. It is its own resolver stage on the ordered pipeline I pre-paid for earlier in the series, driven by an entity descriptor that maps roles to facets. That choice is what allows the same machinery to later serve &lt;code&gt;Address&lt;/code&gt; and &lt;code&gt;Company&lt;/code&gt; by adding a descriptor instead of rewriting the matcher. It also gives &lt;code&gt;Explain()&lt;/code&gt; a real, first-class thing to report.&lt;/p&gt;
&lt;h2&gt;
  
  
  Detection: borrow the truth you already have
&lt;/h2&gt;

&lt;p&gt;Now the part that decides whether the whole feature is trustworthy. How does the engine know &lt;code&gt;FirstName&lt;/code&gt; is a person facet and &lt;code&gt;Name&lt;/code&gt; on a &lt;code&gt;Product&lt;/code&gt; is not?&lt;/p&gt;

&lt;p&gt;The tempting move is to give the persona its own table of names it owns. I did not do that, because then two tables can drift. Instead, the persona piggybacks the existing semantic candidate table: a member binds to a facet when the semantic candidate it already wins is one the persona claims. One source of truth, no drift. And the existing model-hint disambiguation comes along for free:&lt;br&gt;
&lt;code&gt;Product.Name&lt;/code&gt; already resolves to a product-name candidate, never a person one, so coherence inherits that false-positive avoidance without writing a line of new&lt;br&gt;
guarding.&lt;/p&gt;
&lt;h2&gt;
  
  
  Activation: gate on evidence
&lt;/h2&gt;

&lt;p&gt;The last question is: when does an object get a persona at all? Always-on would be reckless. So activation reuses the inference mode the library already exposes (&lt;code&gt;Conservative&lt;/code&gt;, &lt;code&gt;Balanced&lt;/code&gt;, &lt;code&gt;Aggressive&lt;/code&gt;) as an evidence dial, and counts corroborating role members:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Conservative: needs a name anchor (FirstName/LastName/FullName)
Balanced:     a name anchor, or two correlated members
Aggressive:   one matched member is enough
Disabled:     never
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is a build-time pre-pass, not a per-member check, and it self-limits the two-people collapse from the last post: prefixed, ambiguous members score low, so the mistaken merge only happens under &lt;code&gt;Aggressive&lt;/code&gt;, where you asked for reach.&lt;/p&gt;

&lt;p&gt;(Two smaller decisions ride along here: the explicit &lt;code&gt;d.Person&lt;/code&gt; is a fixed snapshot of properties, not a generator, so reads inside a rule line up with the&lt;br&gt;
auto-bound members; and reading it on an object with no activated persona materializes one lazily. That lazy draw is the one order-dependent corner in an otherwise eager design, and it is documented as such.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Automatic behavior earns trust by reusing the system's existing truth and gating on evidence, not by inventing a parallel guesser that can drift or misfire. Magic&lt;br&gt;
you can explain is just a feature. Magic you cannot is a support ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;I have been hand-waving one contractual detail: when exactly does a persona draw its random values from the stream? In a deterministic library, that is not a detail at all. Next post: the PRNG draw-tape, eager versus lazy.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Fake data that is not obviously fake</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Thu, 09 Jul 2026 00:38:35 +0000</pubDate>
      <link>https://dev.to/ernestohs/fake-data-that-is-not-obviously-fake-2pck</link>
      <guid>https://dev.to/ernestohs/fake-data-that-is-not-obviously-fake-2pck</guid>
      <description>&lt;p&gt;Back in the audit, I flagged a tension I had created for myself: I called seed and&lt;br&gt;
demo data a co-equal job, then pointed the whole roadmap at composition, which&lt;br&gt;
barely serves it. This post is where I start paying that off. It begins with an&lt;br&gt;
ugly little object.&lt;/p&gt;

&lt;p&gt;Here is what zero-config inference produces today, for a customer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  FirstName: "Maria",
  LastName:  "Garcia",
  FullName:  "John Smith",          // unrelated to First/Last
  Email:     "k7zp@example.net",    // unrelated to the name
  Username:  "blue-tiger-441"       // unrelated again
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a unit test, nobody cares. Any plausible string is fine. But for seed data in&lt;br&gt;
a demo database, this looks broken. A human reads it in two seconds and knows it&lt;br&gt;
is fake. And this is precisely the job composition cannot help with, because&lt;br&gt;
composition is about structure (which definition builds a nested object), and this&lt;br&gt;
is about correlation between flat scalar fields. Different problem, different&lt;br&gt;
machinery.&lt;/p&gt;
&lt;h2&gt;
  
  
  The idea: a persona
&lt;/h2&gt;

&lt;p&gt;The fix is an idea Bogus already has, called &lt;code&gt;Person&lt;/code&gt;: a coherent bundle generated&lt;br&gt;
once, where the fields agree because they all come from the same underlying&lt;br&gt;
identity. Munchausen's version has to be deterministic and inferred rather than&lt;br&gt;
hand-wired, but the core is the same. There is a hidden &lt;code&gt;Person&lt;/code&gt; for the object,&lt;br&gt;
and &lt;code&gt;FirstName&lt;/code&gt;, &lt;code&gt;Email&lt;/code&gt;, and &lt;code&gt;FullName&lt;/code&gt; are facets of it, not independent&lt;br&gt;
guesses.&lt;/p&gt;
&lt;h2&gt;
  
  
  The load-bearing fork: what is one identity?
&lt;/h2&gt;

&lt;p&gt;The first real decision is the one that sets the mental model for everything after&lt;br&gt;
it: in an object graph, which members share a single persona?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Customer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;FirstName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;LastName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;SalesRep&lt;/span&gt; &lt;span class="n"&gt;Rep&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SalesRep&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A: object-scoped. Persona boundary equals object boundary.
   Customer.FirstName/LastName/Email -&amp;gt; Customer's persona (one identity)
   Rep.Name/Rep.Email                -&amp;gt; Rep's own persona  (a different identity)

B: role-scoped. Group by a member-name prefix, so two people flat on one object
   stay distinct (CustomerName vs SalesRepName).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose object-scoped. It is predictable, it needs no name-parsing heuristics, and&lt;br&gt;
the boundary is something the user can already see in their own type. A nested&lt;br&gt;
object simply gets its own identity, which is almost always what you want.&lt;/p&gt;

&lt;p&gt;Object-scoping has a known sharp edge. If someone flattens two people onto one&lt;br&gt;
type, &lt;code&gt;Order { CustomerName, SalesRepName }&lt;/code&gt;, object-scoping makes them the same&lt;br&gt;
person. That is wrong, and I did not pretend otherwise. I deferred role grouping&lt;br&gt;
as a later refinement and made the collapse diagnosable through &lt;code&gt;Explain()&lt;/code&gt;, which&lt;br&gt;
will happily report "four members bound to one Person" so you can see it.&lt;/p&gt;

&lt;p&gt;The reason I was comfortable deferring it: object-scoping already beats AutoBogus&lt;br&gt;
for the common case, a single identity per object, which is most of the win. The&lt;br&gt;
flat-two-people shape is a denormalized DTO that no tool handles automatically&lt;br&gt;
today. So the principled default ships now, and the differentiator can come later&lt;br&gt;
without repainting the model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Coherence is not "better random values." It is a shared source of truth that&lt;br&gt;
several fields read from. And the first decision in any system like that is the&lt;br&gt;
boundary question: what counts as one identity? Pick the boundary your users can&lt;br&gt;
already see, and be honest about the case it gets wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;A persona is only useful if members actually attach to it, and the attaching has&lt;br&gt;
to be trustworthy rather than magic. Next post: binding members to a persona&lt;br&gt;
without magic, including how the engine decides a field is a person at all.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Paying for the future on purpose</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Tue, 07 Jul 2026 19:13:24 +0000</pubDate>
      <link>https://dev.to/ernestohs/paying-for-the-future-on-purpose-epj</link>
      <guid>https://dev.to/ernestohs/paying-for-the-future-on-purpose-epj</guid>
      <description>&lt;p&gt;There is a category of design decision where the cheap option for the version you are shipping quietly mortgages the version after it. With a solo project and no deadline, I kept choosing to pay now. Here are the two clearest  ases, and the principle that connected them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pre-pay one: structure over shortcuts
&lt;/h2&gt;

&lt;p&gt;Composition adds rules to members. A collection member, for instance, might use a child definition for its elements, hold between two and five of them, and be null&lt;br&gt;
ten percent of the time. v1.1 only needs the first of those. v1.2 wants all three stacked on one member. So how should a member hold its rules?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A: one rule per member, conflicts unless whitelisted.
   Stacking N features needs an O(N^2) compatibility matrix.

B: a member is a record of independent facets (element, size, null-probability).
   Stacking is free; only two rules of the SAME facet conflict.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Option A is less code for v1.1 today. Option B introduces a facet record now, for a feature that only uses one facet, which feels like over-building. I took B&lt;br&gt;
anyway. The same logic applied one layer up: resolution became an ordered pipeline of stages rather than a stack of hardcoded &lt;code&gt;if&lt;/code&gt; branches, so v1.2 can insert a stage by position instead of rewriting the branch.&lt;/p&gt;

&lt;p&gt;The reason is the brief, not taste. Feature growth is an explicit goal, and there is no deadline. The design doc itself had concluded that the cheap options are the&lt;br&gt;
only thing that would force v1.2 to reopen composition. When you can see the roadmap that clearly and you are not racing a clock, paying the structural cost&lt;br&gt;
once is strictly better than paying interest on it every release. (This pipeline, it turns out, becomes the host for the entire coherence feature later in the series. The pre-pay compounded in my favor faster than I expected.)&lt;/p&gt;
&lt;h2&gt;
  
  
  Pre-pay two: a default that fails loudly
&lt;/h2&gt;

&lt;p&gt;The second case is about composition boundaries. If you compose a reusable customer definition into an order, and that customer is silent about how money is generated, should a money rule you set on the order reach inside the customer?&lt;/p&gt;

&lt;p&gt;Two pure answers, and the choice is really about how each one fails.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hermetic: the rule seals at the boundary. The customer generates its own way.
ambient:  the rule flows in. The customer's money becomes yours.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ambient is convenient right up until it is not. The failure mode is that a distant ancestor silently mutates a fixture you wrote and tested, and you find out by&lt;br&gt;
auditing the whole chain. Hermetic fails the other way: your rule under-reaches, you see the wrong value at your own call site, and the fix is local. Local and diagnosable beats non-local and silent. So the default is hermetic: a composed definition is a sealed contract.&lt;/p&gt;

&lt;p&gt;But hermetic under-reaches on purpose, and sometimes you really do mean "every money value in this whole graph is in euros, no exceptions." For that I reserved&lt;br&gt;
an escape valve, &lt;code&gt;UseDeep&lt;/code&gt;, that a consumer writes explicitly and that always wins, penetrating any boundary. The fork there was whether reach should be the&lt;br&gt;
consumer's call (&lt;code&gt;UseDeep&lt;/code&gt;) or the author's  (&lt;code&gt;Sealed&lt;/code&gt;/&lt;code&gt;Open&lt;/code&gt; on the definition). I took a hybrid: sealed by default so authors get safe contracts for free, plus a consumer &lt;code&gt;UseDeep&lt;/code&gt; that overrides anything. Two concepts, but each is in the hands of the person who knows the intent. &lt;code&gt;UseDeep&lt;/code&gt; is greppable and visible at the call&lt;br&gt;
site, which keeps even the ambient-style power loud rather than silent. Its shape is reserved now and ships in a later point release, so the default can never paint&lt;br&gt;
it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;When you can see the feature roadmap and you are not under deadline, pay your structural debt down before it compounds. The brief is what tells you whether you&lt;br&gt;
can afford to. And when you pick a default, choose the one whose failure mode is local and visible, not the one whose convenience hides the bug several layers up.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;That is the composition thread settled. Now the threads switch. The next question is not how objects compose, it is whether the data inside them looks real at all.&lt;/p&gt;

&lt;p&gt;Next post: fake data that is not obviously fake.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Reconciling a public API that drifted</title>
      <dc:creator>Ernesto Herrera Salinas</dc:creator>
      <pubDate>Sun, 05 Jul 2026 21:11:47 +0000</pubDate>
      <link>https://dev.to/ernestohs/reconciling-a-public-api-that-drifted-1652</link>
      <guid>https://dev.to/ernestohs/reconciling-a-public-api-that-drifted-1652</guid>
      <description>&lt;p&gt;The audit found that my two binding documents disagreed about the v1.1 composition API. Closing that gap was not paperwork. A public method is a promise you can almost never take back, so reconciling the surface meant making real choices and living with them.&lt;/p&gt;

&lt;p&gt;Some quick context on what composition is. It lets a definition control how the nested objects inside a generated graph are produced: "every &lt;code&gt;Address&lt;/code&gt; in this order graph uses this address definition," or "this specific member uses that child definition." The surface in flux covered type-scoped, member-scoped, and element-scoped binding, plus substituting a concrete type for an abstract member.&lt;/p&gt;

&lt;p&gt;Three forks had to be settled.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fork one: the name
&lt;/h2&gt;

&lt;p&gt;The element-scoped method was &lt;code&gt;UseEach&lt;/code&gt; in the binding doc and &lt;code&gt;UseElements&lt;/code&gt; in the design doc. Tiny, but public, so it gets a real decision. I kept &lt;code&gt;UseEach&lt;/code&gt;. It was the name in the top-authority document already, and it reads cleanly next to&lt;br&gt;
its siblings: &lt;code&gt;Use&lt;/code&gt; for a type, &lt;code&gt;UseEach&lt;/code&gt; for the elements of a collection. Not every fork is deep. Some are just "pick one on purpose and stop relitigating it."&lt;/p&gt;
&lt;h2&gt;
  
  
  Fork two: the shape
&lt;/h2&gt;

&lt;p&gt;This one mattered. The binding doc had a single-generic member binding plus a separate &lt;code&gt;UseAs&lt;/code&gt; method for substituting a concrete type for an abstraction. The design doc had folded both into one two-generic method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// before: two verbs&lt;/span&gt;
&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TChild&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;UseAs&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TAbstraction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TImplementation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;UseAs&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TAbstraction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TImplementation&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// after: one verb, abstract-capable&lt;/span&gt;
&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TMember&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TConcrete&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;expr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;TConcrete&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TMember&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I took the folded shape. The generic constraint carries abstract substitution for free: for a concrete member, &lt;code&gt;TMember&lt;/code&gt; and &lt;code&gt;TConcrete&lt;/code&gt; infer to the same type, so existing calls are unchanged, and for an interface-typed member the second generic&lt;br&gt;
supplies the concrete definition. One verb instead of three, and the type system does the work that a separate method used to. &lt;code&gt;UseAs&lt;/code&gt; disappeared.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fork three: how much to ship
&lt;/h2&gt;

&lt;p&gt;The folded &lt;code&gt;Use&lt;/code&gt; handles abstract members one at a time. The binding doc also promised a type-level version: "every &lt;code&gt;IParty&lt;/code&gt; in this graph is a &lt;code&gt;Person&lt;/code&gt;," one registration, graph-wide. I deferred it.&lt;/p&gt;

&lt;p&gt;The reasons were concrete. The compiler cannot infer both type arguments for the type-level form, so every call would spell them out. It needs a conflict rule for two concretes registered against one abstraction. And it quietly nudges users toward expecting random polymorphic selection, which is a different feature&lt;br&gt;
entirely. It is cheap to add later and expensive to walk back, so it waits. In v1.1, abstract substitution is member-scoped and element-scoped only.&lt;/p&gt;

&lt;p&gt;I also added one new diagnostic, &lt;code&gt;LIE012&lt;/code&gt;, for an unsupported composition target (binding to a collection shape the library cannot materialize), since the existing diagnostic registry had no fitting code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the why into the contract
&lt;/h2&gt;

&lt;p&gt;The part I am most glad I did: I recorded all of this as a "Revision 2.2 amendment" inside &lt;code&gt;API_DESIGN.md&lt;/code&gt; itself. Not in a side note, not in a commit message, in the binding document, next to the surface it changed. The amendment says what folded into what, what was deferred and why, and that the type-level form is an additive follow-up.&lt;/p&gt;

&lt;p&gt;The contract and the design doc finally describe the same API, and the next person to read either one will find the reasoning attached to the decision instead of having to reconstruct it, or worse, rediscover the conflict I just closed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;A public API is a promise, and drift between your contract and your design is a quiet way to break it. Reconcile deliberately, prefer the change that is additive and lets the type system carry the load, defer the expensive-to-reverse pieces, and write the why into the contract so it does not drift again.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;With the surface honest, the deeper composition decisions were waiting, the ones that were not about taste at all. They were decided by the brief: solo, no deadline, quality over speed. Next post, paying for the future on purpose.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
