<?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: just_an_electron</title>
    <description>The latest articles on DEV Community by just_an_electron (@just_an_electron).</description>
    <link>https://dev.to/just_an_electron</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%2F3986850%2F8a02cb1b-b285-4705-b7b5-3ebe01e681bd.jpg</url>
      <title>DEV Community: just_an_electron</title>
      <link>https://dev.to/just_an_electron</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/just_an_electron"/>
    <language>en</language>
    <item>
      <title>A self-updating knowledge base for my terminal AI assistant (Claude Code hooks)</title>
      <dc:creator>just_an_electron</dc:creator>
      <pubDate>Wed, 01 Jul 2026 17:45:03 +0000</pubDate>
      <link>https://dev.to/just_an_electron/a-self-updating-knowledge-base-for-my-terminal-ai-assistant-claude-code-hooks-28jb</link>
      <guid>https://dev.to/just_an_electron/a-self-updating-knowledge-base-for-my-terminal-ai-assistant-claude-code-hooks-28jb</guid>
      <description>&lt;p&gt;I spend most of my day in the terminal with an AI coding assistant. Every session I would solve something worth remembering: a tricky fix, a config gotcha, a small runbook. Then I would lose it. It lived in a scrollback buffer that vanished when I closed the tab. A month later I would re-solve the same thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea
&lt;/h2&gt;

&lt;p&gt;Make the assistant maintain its own notes. Three moving parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On every prompt, search a small Markdown knowledge base (KB) and inject the relevant entries, so the assistant answers with prior context instead of re-deriving it.&lt;/li&gt;
&lt;li&gt;When a session ends, capture anything worth keeping back into the KB.&lt;/li&gt;
&lt;li&gt;On session start, load the index so the assistant knows what exists.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Claude Code has &lt;strong&gt;hooks&lt;/strong&gt;: shell commands that fire on lifecycle events. Three of them cover the whole loop.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SessionStart"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/kb-load.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"UserPromptSubmit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/kb-search.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"SessionEnd"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/path/kb-enqueue.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything below is the useful stuff I learned wiring these up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Retrieval: UserPromptSubmit runs on every message
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;UserPromptSubmit&lt;/code&gt; fires for each prompt you send, receives the prompt text on stdin, and can inject context before the model runs. That is the hook that makes the KB get checked automatically, instead of leaving it to the model to decide whether to look.&lt;/p&gt;

&lt;p&gt;Three design rules: it must be cheap (grep, no LLM), lean (inject only the top matches, never dump whole files), and it must never block the prompt.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# NOTE: no `set -e`. A UserPromptSubmit hook that exits non-zero BLOCKS the prompt.&lt;/span&gt;
&lt;span class="c"&gt;# Every failure path must fall through to `exit 0` with no output.&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-uo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;PROMPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import sys,json;print(json.load(sys.stdin).get("prompt",""))'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROMPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

python3 - &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PROMPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;' || true
import sys, os, re, glob, json
kb, prompt = sys.argv[1], sys.argv[2]

# tokenize the prompt, drop stopwords/short tokens, keep ticket-ish IDs like abc-123
terms = [t for t in re.findall(r"[a-z][a-z0-9-]{2,}", prompt.lower()) if t not in STOP]
if not terms: sys.exit(0)

# rank every KB file by (distinct terms matched, then total matches)
scored = []
for f in glob.glob(os.path.join(kb, "**/*.md"), recursive=True):
    text = open(f, errors="ignore").read().lower()
    hits = sum(1 for t in terms if t in text)
    if hits: scored.append((hits, sum(text.count(t) for t in terms), f))
if not scored: sys.exit(0)
scored.sort(reverse=True)

# inject ONLY the top few: title + path. The model opens the file if it needs detail.
lines = ["# Relevant KB entries (consult before answering; open the file for detail):", ""]
for _, _, f in scored[:5]:
    lines.append(f"- {title_of(f)} [{os.path.relpath(f, kb)}]")
print(json.dumps({"hookSpecificOutput":
    {"hookEventName": "UserPromptSubmit", "additionalContext": "&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;".join(lines)}}))
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The point is not that grep is clever. It is that the hook does the searching once, ranks the results, and hands the model the specific files to read. The model stops guessing which files or tools to check, because the relevant entry is already in front of it. Scanning a few dozen small Markdown files per prompt takes under 100ms, and the win is injecting only the top five, not the whole KB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capture: the mistake, and the fix
&lt;/h2&gt;

&lt;p&gt;My first version captured on the &lt;code&gt;Stop&lt;/code&gt; hook, which fires every time the assistant finishes a reply. The capture spawns a headless AI call (&lt;code&gt;claude -p ...&lt;/code&gt;) to read the transcript and write KB entries. It worked, and then the assistant got painfully slow. Every turn ended with "running stop hook" for minutes.&lt;/p&gt;

&lt;p&gt;Here is why. &lt;code&gt;Stop&lt;/code&gt; runs synchronously, once per turn. A 30-turn session fired the headless capture about 30 times, each one re-reading a bigger transcript. I was re-capturing the same conversation over and over.&lt;/p&gt;

&lt;p&gt;The unit I actually wanted was the &lt;strong&gt;session&lt;/strong&gt; (start until &lt;code&gt;/clear&lt;/code&gt;), captured &lt;strong&gt;once&lt;/strong&gt;. That is &lt;code&gt;SessionEnd&lt;/code&gt;, which fires on &lt;code&gt;/clear&lt;/code&gt; and on exit. So I moved the capture there, and hit the real lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;SessionEnd&lt;/code&gt; cannot run a long or backgrounded job. It is documented as non-blocking (side effects only), and a process you background from it is not guaranteed to survive. The session is terminating, and its child processes can be killed with it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So a multi-minute headless capture launched from &lt;code&gt;SessionEnd&lt;/code&gt; gets cut off partway.&lt;/p&gt;

&lt;p&gt;The fix is to split it across two hooks.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;SessionEnd&lt;/code&gt; does something trivial and instant: it enqueues the ended transcript's path.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;KB_CAPTURE&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0          &lt;span class="c"&gt;# recursion guard (see below)&lt;/span&gt;
&lt;span class="nv"&gt;T&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; | python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import sys,json;print(json.load(sys.stdin).get("transcript_path",""))'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$T&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$T&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$T&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KB&lt;/span&gt;&lt;span class="s2"&gt;/tools/queue.txt"&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SessionStart&lt;/code&gt; (of the next session) drains the queue and runs the capture in the background. That is safe here because this session stays alive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;QUEUE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KB&lt;/span&gt;&lt;span class="s2"&gt;/tools/queue.txt"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nv"&gt;LOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KB&lt;/span&gt;&lt;span class="s2"&gt;/tools/.lock"&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUEUE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;   &lt;span class="c"&gt;# mkdir is an atomic single-drain lock&lt;/span&gt;
  &lt;span class="nb"&gt;mv&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUEUE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUEUE&lt;/span&gt;&lt;span class="s2"&gt;.wip"&lt;/span&gt;                               &lt;span class="c"&gt;# claim atomically so new enqueues are not lost&lt;/span&gt;
  &lt;span class="o"&gt;(&lt;/span&gt; &lt;span class="nb"&gt;sort&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUEUE&lt;/span&gt;&lt;span class="s2"&gt;.wip"&lt;/span&gt; | &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; t&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
        &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nv"&gt;KB_BG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;KB_TRANSCRIPT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$t&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KB&lt;/span&gt;&lt;span class="s2"&gt;/tools/kb-capture.sh"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$KB&lt;/span&gt;&lt;span class="s2"&gt;/log"&lt;/span&gt; 2&amp;gt;&amp;amp;1
      &lt;span class="k"&gt;done
      &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$QUEUE&lt;/span&gt;&lt;span class="s2"&gt;.wip"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;rmdir&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1 &amp;amp;        &lt;span class="c"&gt;# backgrounded off a LIVE session, so it survives&lt;/span&gt;
  &lt;span class="nb"&gt;disown &lt;/span&gt;2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true
&lt;/span&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Net effect: one capture per session, off the critical path. The only trade-off is that the write lands at the start of the next session (a few seconds later) rather than the instant you &lt;code&gt;/clear&lt;/code&gt;, which is the one place a long job is guaranteed to survive.&lt;/p&gt;

&lt;p&gt;Two smaller notes. macOS has no &lt;code&gt;setsid&lt;/code&gt;, so detach with &lt;code&gt;nohup ... &amp;amp;&lt;/code&gt; plus &lt;code&gt;disown&lt;/code&gt; (a bash builtin) rather than &lt;code&gt;setsid&lt;/code&gt;. And read stdin before backgrounding, because the detached copy has none.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that made it cheap and safe
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. A cheaper model for the capture.&lt;/strong&gt; It is a summarize-and-write task, so it does not need your most expensive model. Pin it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KB_CAPTURE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;capture-prompt.md&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--model&lt;/span&gt; &amp;lt;cheap-fast-model&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--allowedTools&lt;/span&gt; &lt;span class="s2"&gt;"Read,Edit,Write,Grep,Glob"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Roughly 5x cheaper, and it finishes fast, so queued captures clear quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A pre-filter before spending a token.&lt;/strong&gt; Most sessions are not worth capturing. A quick grep gates the AI call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qiE&lt;/span&gt; &lt;span class="s1"&gt;'root cause|next step|blocked on|draft|fix|&amp;lt;ticket-pattern&amp;gt;'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TRANSCRIPT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. A recursion guard, and set it first.&lt;/strong&gt; The headless &lt;code&gt;claude&lt;/code&gt; you spawn inherits your environment, including the hook registration. So it fires its own hooks, which spawn another headless &lt;code&gt;claude&lt;/code&gt;, and so on without end. One env var breaks the loop: put &lt;code&gt;[ -n "${KB_CAPTURE:-}" ] &amp;amp;&amp;amp; exit 0&lt;/code&gt; at the top of every hook, and set &lt;code&gt;KB_CAPTURE=1&lt;/code&gt; when you spawn the headless call. Without it, one real session fans out into an unbounded tree of AI calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  The economics
&lt;/h2&gt;

&lt;p&gt;Per prompt: a sub-100ms grep plus a small context injection of about five entries, and only when there is a match. Per session: one cheap capture, and only if the session actually learned something (the pre-filter plus a "did anything change" check before writing).&lt;/p&gt;

&lt;p&gt;That is the trade. You pay a tiny, bounded cost to cache knowledge, so future sessions skip the expensive part: re-reading a large codebase, re-querying tools, re-investigating a problem you already solved. One avoided re-derivation pays for a lot of captures.&lt;/p&gt;

&lt;h2&gt;
  
  
  The lessons, condensed
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;UserPromptSubmit&lt;/code&gt; is where you inject retrieval. It is the only hook that runs on every prompt and can add context before the model.&lt;/li&gt;
&lt;li&gt;Anything on &lt;code&gt;Stop&lt;/code&gt; runs on the critical path of every turn. Keep it instant.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SessionEnd&lt;/code&gt; cannot run long or background work. Enqueue there, and do the heavy lifting on the next &lt;code&gt;SessionStart&lt;/code&gt;, where the process survives.&lt;/li&gt;
&lt;li&gt;Guard against recursion before you ever run a hook that spawns the assistant.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>claudecode</category>
    </item>
    <item>
      <title>Voice mode in Claude Code (VS Code, macOS)</title>
      <dc:creator>just_an_electron</dc:creator>
      <pubDate>Thu, 25 Jun 2026 08:03:48 +0000</pubDate>
      <link>https://dev.to/just_an_electron/voice-mode-in-claude-code-vs-code-macos-3cjj</link>
      <guid>https://dev.to/just_an_electron/voice-mode-in-claude-code-vs-code-macos-3cjj</guid>
      <description>&lt;p&gt;Claude Code has a built-in voice mode: hold space, talk, it transcribes into your prompt. Here's how to get it running in the &lt;strong&gt;VS Code integrated terminal&lt;/strong&gt; on macOS — including the one step that breaks it for everyone.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Install the backend
&lt;/h2&gt;

&lt;p&gt;Voice mode records via &lt;code&gt;sox&lt;/code&gt;. No sox, no audio.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;sox
sox &lt;span class="nt"&gt;--version&lt;/span&gt;   &lt;span class="c"&gt;# sanity check&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Turn it on
&lt;/h2&gt;

&lt;p&gt;In the Claude Code prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/voice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hold &lt;strong&gt;space&lt;/strong&gt; to record, release to stop.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Grant the mic — to VS Code, not the terminal
&lt;/h2&gt;

&lt;p&gt;Claude Code runs inside VS Code, so macOS attributes the mic request to &lt;strong&gt;Visual Studio Code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Open &lt;strong&gt;System Settings → Privacy &amp;amp; Security → Microphone&lt;/strong&gt;. If VS Code isn't listed, it never requested access yet — and there's no "+" to add it manually. Force the prompt:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;⌘Q to fully quit VS Code&lt;/strong&gt; (closing the window keeps the process alive — not enough).&lt;/li&gt;
&lt;li&gt;Reopen it.&lt;/li&gt;
&lt;li&gt;Trigger a recording. macOS pops &lt;em&gt;"Visual Studio Code would like to access the microphone"&lt;/em&gt; → &lt;strong&gt;Allow&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;macOS only re-reads mic permissions at app launch. A VS Code instance that was already running when you flipped the toggle keeps getting fed silence until you restart it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  4. Verify you're capturing real audio
&lt;/h2&gt;

&lt;p&gt;The nasty failure: recording "works" but is pure silence (permission denied → all zeros).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;rec &lt;span class="nt"&gt;-q&lt;/span&gt; /tmp/mictest.wav trim 0 2          &lt;span class="c"&gt;# records 2s — say something&lt;/span&gt;
sox /tmp/mictest.wav &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nb"&gt;stat &lt;/span&gt;2&amp;gt;&amp;amp;1 | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"Maximum amplitude"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0.000000&lt;/code&gt; → still silent. Recheck the toggle, restart VS Code.&lt;/li&gt;
&lt;li&gt;non-zero (e.g. &lt;code&gt;0.17&lt;/code&gt;) → working. RMS around &lt;code&gt;0.01&lt;/code&gt; is a normal talking level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gotchas
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Symptom&lt;/th&gt;
&lt;th&gt;Fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"No audio detected"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;brew install sox&lt;/code&gt; + grant mic permission&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amplitude &lt;code&gt;0.000000&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Enable VS Code in Microphone settings, then &lt;strong&gt;⌘Q + reopen&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Toggle off with &lt;code&gt;/voice&lt;/code&gt;. Change dictation language with &lt;code&gt;/config&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>vscode</category>
      <category>macos</category>
      <category>productivity</category>
    </item>
    <item>
      <title>One Agent Per Repo: An Orchestration Pattern for Multi-Repo AI Coding</title>
      <dc:creator>just_an_electron</dc:creator>
      <pubDate>Tue, 16 Jun 2026 08:19:02 +0000</pubDate>
      <link>https://dev.to/just_an_electron/one-agent-per-repo-an-orchestration-pattern-for-multi-repo-ai-coding-49ba</link>
      <guid>https://dev.to/just_an_electron/one-agent-per-repo-an-orchestration-pattern-for-multi-repo-ai-coding-49ba</guid>
      <description>&lt;p&gt;Your AI coding assistant is brilliant inside a single repo. Drop it into a DevOps workspace of a dozen and watch it fall apart.&lt;/p&gt;

&lt;p&gt;It skims half of one repo and half of another, forgets which ones are read-only, blends their conventions, and spends most of its context window just trying to remember the rules. You ask one cross-cutting question and get a confident, half-wrong answer stitched together from the wrong files. The harder problem isn't the model, it's that you handed one brain a dozen jobs at once.&lt;/p&gt;

&lt;p&gt;There's a fix, and it mirrors how good teams already split the work: &lt;strong&gt;give each repository its own specialised agent, and let one orchestrator route the work and merge the answers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's it. The rest of this post is the &lt;em&gt;why&lt;/em&gt; and the &lt;em&gt;how&lt;/em&gt;: the one constraint that quietly dictates the whole design, copy-pasteable starter agents you can drop in today, and the failure modes to avoid. Examples use &lt;a href="https://docs.anthropic.com/en/docs/claude-code" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; subagents, but the idea ports to any agent runtime with per-agent prompts and tool scoping.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One AI agent per repo, each with a scoped prompt and &lt;strong&gt;scoped tools&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A single &lt;strong&gt;orchestrator (the main session) routes work&lt;/strong&gt; and merges answers. It can't be a separate "router agent," because subagents can't spawn subagents.&lt;/li&gt;
&lt;li&gt;Make read-only repos read-only &lt;strong&gt;by removing their write tools&lt;/strong&gt;, not by asking nicely.&lt;/li&gt;
&lt;li&gt;Cross-repo questions &lt;strong&gt;fan out to several agents in parallel&lt;/strong&gt;, each in a clean context.&lt;/li&gt;
&lt;li&gt;It's a few Markdown files. No framework required.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;Picture a system that's grown into several independent repos, checked out side by side, a layout most DevOps and platform teams will recognise:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Repo&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Edit policy&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;backend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Application services you debug against, source of truth for behavior and stack traces&lt;/td&gt;
&lt;td&gt;read only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;frontend&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Web client / UI you debug against&lt;/td&gt;
&lt;td&gt;read only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;infrastructure&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Terraform / IaC you own&lt;/td&gt;
&lt;td&gt;edit, confirm first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;deploy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Kubernetes manifests / Helm charts you own&lt;/td&gt;
&lt;td&gt;edit, confirm first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;docs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Runbooks and documentation&lt;/td&gt;
&lt;td&gt;edit via pull request&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a DevOps view of the world: &lt;code&gt;infrastructure&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt; are yours to change, but they touch production, so an agent should never apply a change without showing you the diff and getting a yes. &lt;code&gt;backend&lt;/code&gt; and &lt;code&gt;frontend&lt;/code&gt; belong to other teams; you read them to debug, never to edit. &lt;code&gt;docs&lt;/code&gt; you change freely, but through a pull request. Three tiers, and each one becomes a different agent.&lt;/p&gt;

&lt;p&gt;Point one AI session at all of it and you hit three walls.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context dilution.&lt;/strong&gt; Loading every repo's conventions, layout, and edit policy into one context window crowds out the actual problem, and the model starts leaking rules from one repo into another ("it's fine to edit here" bleeding into a read-only repo).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serial investigation.&lt;/strong&gt; A question like &lt;em&gt;"is this a code bug or an environment misconfiguration?"&lt;/em&gt; spans two repos. One session reads the first, then the second, then struggles to hold both in mind at once.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weak guardrails.&lt;/strong&gt; When "this repo is read-only" lives only in a prose instructions file, it's a polite request the model can forget under load, not an enforced boundary. On a shared workspace, that's how the wrong repo gets edited.&lt;/p&gt;

&lt;p&gt;The thesis: one specialised agent per repo, and a single orchestrator that routes work to the relevant ones, in parallel, and merges their findings.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pattern has a name: orchestrator-worker
&lt;/h2&gt;

&lt;p&gt;It mirrors how you'd run a team. You wouldn't make one engineer own the backend, the Helm charts, and the docs all at once, holding every rule in their head. You'd give each area an owner and put someone in charge of routing the work. That's the &lt;strong&gt;orchestrator-worker&lt;/strong&gt; pattern (also called manager-worker, or conductor), and it's well-trodden ground. The recurring lessons:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One agent per repo means a focused context window per repo.&lt;/strong&gt; The backend agent reads only backend conventions; the deploy agent only deploy conventions. The orchestrator coordinates but holds none of the detail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A multi-repo layout is where this shines.&lt;/strong&gt; Independent repos mean agents in different repos can't create file conflicts, unlike a monorepo where parallel edits collide.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managers hold the plan; workers execute&lt;/strong&gt;, one repo, one task.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is new. There's already plenty written on multi-agent orchestration and the orchestrator-worker pattern, and a quick search will turn up more than enough. What follows is my take on making it actually work across a real multi-repo workspace.&lt;/p&gt;




&lt;h2&gt;
  
  
  The one constraint that shapes everything
&lt;/h2&gt;

&lt;p&gt;Internalise this before drawing any boxes, because it kills the most intuitive design:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Subagents cannot spawn other subagents.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The tempting model is a three-tier pipeline: a &lt;em&gt;router&lt;/em&gt; agent dispatches to &lt;em&gt;repo&lt;/em&gt; agents, which report up to a separate &lt;em&gt;answer&lt;/em&gt; agent. That's two levels of delegation, and the second level isn't allowed.&lt;/p&gt;

&lt;p&gt;So the orchestrator can't be a subagent. &lt;strong&gt;It must be the main conversation itself.&lt;/strong&gt; The session you talk to &lt;em&gt;is&lt;/em&gt; the conductor: it reads your request, decides which repo agents to invoke (the one and only level of subagents), collects their reports, and synthesises the answer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;        YOU
         |  (your request)
         v
  +--------------------------------------------+
  |   ORCHESTRATOR  =  the main session         |
  |   - reads the request                       |
  |   - routes to the right repo workers        |
  |   - fans out in parallel when needed         |
  |   - merges their reports                     |
  |   - replies to you                           |
  +--------------------------------------------+
       |          |          |            |
       v          v          v            v
  [backend]   [deploy]   [infra]      [docs]     &amp;lt;- one worker per repo
   agent       agent      agent        agent        (cannot spawn more)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every choice below lives inside this constraint.&lt;/p&gt;




&lt;h2&gt;
  
  
  The building block: one agent definition per repo
&lt;/h2&gt;

&lt;p&gt;In Claude Code, each agent is a small Markdown file with YAML frontmatter, dropped in &lt;code&gt;.claude/agents/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend-expert&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;Application services you debug against: the source of truth for behavior and stack&lt;/span&gt;
  &lt;span class="s"&gt;traces. Use when an issue involves a traceback, wrong output, or "where does X happen&lt;/span&gt;
  &lt;span class="s"&gt;in the code". Read only.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob, Bash&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are the expert for the backend repository.
&lt;span class="p"&gt;-&lt;/span&gt; Scope: application services, the code paths behind a stack trace.
&lt;span class="p"&gt;-&lt;/span&gt; Access: READ ONLY. Describe fixes precisely (file:line + the change); never apply them.
&lt;span class="p"&gt;-&lt;/span&gt; Report back: a conclusion first, then file:line references and root cause. Findings,
  not raw file dumps.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three frontmatter fields carry the design:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Note&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Routing signal&lt;/strong&gt; the orchestrator matches requests against&lt;/td&gt;
&lt;td&gt;Write it as &lt;em&gt;"use when ..."&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tools&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Hard guardrail&lt;/strong&gt;: omit &lt;code&gt;Edit&lt;/code&gt;/&lt;code&gt;Write&lt;/code&gt; and the agent is physically read-only&lt;/td&gt;
&lt;td&gt;The capability is &lt;em&gt;absent&lt;/em&gt;, not discouraged&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Cost/quality dial&lt;/strong&gt; per agent&lt;/td&gt;
&lt;td&gt;Heavy model for reasoning, cheap for lookups&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The orchestrator itself has no file; it's the main loop. Its routing comes from each agent's &lt;code&gt;description&lt;/code&gt; plus a short &lt;strong&gt;triage guide&lt;/strong&gt; in your top-level instructions file (&lt;code&gt;CLAUDE.md&lt;/code&gt;) mapping issue types to repos. Match one agent and delegate. Spans repos, and it fans out in parallel and reconciles.&lt;/p&gt;




&lt;h2&gt;
  
  
  Copy-pasteable starter agents
&lt;/h2&gt;

&lt;p&gt;The read-only example above (&lt;code&gt;backend-expert&lt;/code&gt;) is one tier: it simply has no write tools. As a DevOps engineer you also own a couple of repos, so here are the two editable tiers. The key difference from a read-only agent is in &lt;code&gt;tools&lt;/code&gt; and in the policy line of the prompt.&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  deploy-expert.md (edit, confirm first)
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deploy-expert&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;Kubernetes manifests and Helm charts you own: env config, resource limits, what's set&lt;/span&gt;
  &lt;span class="s"&gt;per environment. Use for deployment questions and to make deployment changes.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob, Bash, Edit, Write&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are the expert for the deploy repository. You may edit it, but it touches production.
NEVER apply a change without first showing the human the exact diff and getting an explicit
yes. Propose first; edit only on confirmation; never push, apply, or roll out on your own.
Report conclusion-first with file:line references.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;(The &lt;code&gt;infrastructure&lt;/code&gt; repo gets the same shape: edit allowed, but confirm-before-apply,&lt;br&gt;
because Terraform changes touch production too.)&lt;br&gt;
&lt;/p&gt;

&lt;br&gt;
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;
  docs-editor.md (edit via PR)
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docs-editor&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="s"&gt;Runbooks and customer-facing docs. Use to find an existing runbook/known fix, or to&lt;/span&gt;
  &lt;span class="s"&gt;draft/edit docs. EDITS ALLOWED but only via pull request: never push to the default&lt;/span&gt;
  &lt;span class="s"&gt;branch directly.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob, Bash, Edit, Write&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sonnet&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You are the expert for the docs repository. Edits are allowed, but ALL changes go through
a pull request, never a push to the default branch. Prefer drafting; let the orchestrator
handle branch/PR creation. Follow existing structure and tone. Report conclusion-first.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;p&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Turning edit policies into real boundaries
&lt;/h2&gt;

&lt;p&gt;This is the part teams under-appreciate. A single monolithic agent holds write capability everywhere and relies entirely on &lt;em&gt;remembering&lt;/em&gt; which directories are off-limits. In the per-repo model the boundary is structural, and it comes in three tiers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read-only (hard wall).&lt;/strong&gt; The repos you only debug against, like &lt;code&gt;backend&lt;/code&gt; and &lt;code&gt;frontend&lt;/code&gt;, get an agent with no &lt;code&gt;Edit&lt;/code&gt;/&lt;code&gt;Write&lt;/code&gt; tools at all. It can't modify them even if you ask, because the capability is absent. No memory required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit, confirm first (production repos).&lt;/strong&gt; The repos you own that touch production, like &lt;code&gt;infrastructure&lt;/code&gt; and &lt;code&gt;deploy&lt;/code&gt;, get write tools, but the prompt forbids applying anything without showing you the diff and getting a yes, and forbids pushing or rolling out unattended.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edit via PR (low-risk repos).&lt;/strong&gt; Repos like &lt;code&gt;docs&lt;/code&gt;: write freely, but land changes through a pull request rather than a direct push.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Belt and suspenders. The most dangerous failure mode of AI on a shared workspace, quietly editing or deploying the wrong thing, is designed out: a read-only agent &lt;em&gt;can't&lt;/em&gt;, and a production agent &lt;em&gt;won't without a yes&lt;/em&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  A worked example, end to end
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Request:&lt;/strong&gt; &lt;em&gt;"A service crashes on startup in production. Is it the application code or the deployment config?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The orchestrator routes.&lt;/strong&gt; The request matches two agents, so it fans out in parallel:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;orchestrator
   |--&amp;gt; backend-expert : "trace the startup crash; is the code actually broken?"
   `--&amp;gt; deploy-expert  : "could the deployment config cause this? what's set in prod?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Workers investigate independently, each in a clean context.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;backend-expert&lt;/code&gt; traces startup and finds the service reads a required env var and exits cleanly if it's missing. The code is correct; it's failing fast by design.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;deploy-expert&lt;/code&gt; finds the prod Helm values never set that env var (it's only in the staging values). Root cause: a deployment-config gap, not the code.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. The orchestrator merges and answers you:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;It's a deployment-config gap, not a code bug. The service requires env var &lt;code&gt;X&lt;/code&gt; at startup (backend, file:line), but the prod Helm values don't set it; only staging does (deploy, file:line). I can add it to the prod values for you, here's the one-line diff. Apply it?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And there's the payoff of the tiers. &lt;code&gt;backend&lt;/code&gt; was read-only, so it could only explain. &lt;code&gt;deploy&lt;/code&gt; is yours to edit, so the same agent that found the gap can fix it, but it stops and shows you the diff first because that repo touches production. You get the fix in one motion, without ever handing an agent unattended write access to prod.&lt;/p&gt;

&lt;p&gt;A single session would have read both repos serially and juggled both rule sets at once. The split keeps each worker sharp and runs the investigations concurrently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Everyday flows:&lt;/strong&gt; single-repo lookups go to one agent; "write this up" goes to the docs agent (which lands it as a PR); and you can always force a route: &lt;em&gt;"ask the deploy-expert what env vars the prod values set for service Y."&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Operating, tuning, and pitfalls
&lt;/h2&gt;

&lt;p&gt;A few things that make or break it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Routing lives in &lt;code&gt;description&lt;/code&gt;.&lt;/strong&gt; Misrouting means a vague description; sharpen it, and keep them from overlapping or routing turns into a coin flip.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Each agent describes its own repo&lt;/strong&gt;, not the whole system, and you dial &lt;code&gt;model&lt;/code&gt; per agent (strong for reasoning, cheap for lookups).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don't grant write tools "just in case".&lt;/strong&gt; That quietly throws away the biggest safety win. Default to read-only.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workers don't share memory.&lt;/strong&gt; They don't see each other's state, so the orchestrator passes along anything that needs to cross over.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Limitations, and the heavier alternative
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No deep nesting.&lt;/strong&gt; One level of workers only; restructure "a worker needs workers" as orchestrator &lt;em&gt;phases&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No shared memory between workers.&lt;/strong&gt; Reconciliation is the orchestrator's job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing is heuristic&lt;/strong&gt;, not a hard dispatch table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For &lt;strong&gt;deterministic, large-scale fan-out&lt;/strong&gt;, like "audit every repo for X" or a migration across all of them, reach for a &lt;strong&gt;scripted workflow&lt;/strong&gt;: code that orchestrates agents with explicit loops, parallel stages, and structured outputs. Rule of thumb: interactive subagents for debugging and exploration; a scripted workflow when you need repeatable, massively parallel passes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adopt it in five minutes
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Create &lt;code&gt;.claude/agents/&lt;/code&gt; in your workspace root.&lt;/li&gt;
&lt;li&gt;Add one agent file per repo (start from the snippets above). Repos you only debug: omit &lt;code&gt;Edit&lt;/code&gt;/&lt;code&gt;Write&lt;/code&gt;. Repos you own that touch production: add them, plus a confirm-before-apply policy. Low-risk repos like docs: add them, plus a land-via-PR policy.&lt;/li&gt;
&lt;li&gt;Add a short triage guide to your top-level &lt;code&gt;CLAUDE.md&lt;/code&gt; mapping issue types to repos.&lt;/li&gt;
&lt;li&gt;Write each &lt;code&gt;description&lt;/code&gt; as &lt;em&gt;"use when ..."&lt;/em&gt;, keeping them disjoint.&lt;/li&gt;
&lt;li&gt;Ask a cross-repo question and watch the orchestrator delegate.&lt;/li&gt;
&lt;/ol&gt;




&lt;p&gt;I've been running this across a multi-repo workspace and it's changed how I debug. One generalist drowning in every repo loses to a small team of specialists with a coordinator. True of people, true of agents. Give each repo an owner, put one session in charge, and make the agent that touches production ask before it acts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How are you structuring this?&lt;/strong&gt; One agent per repo, one per domain, or something else? Drop it in the comments. 👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
