<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Parker Jones Dev Blog - claude-code</title>
    <subtitle>Dev Blog of Parker Jones</subtitle>
    <link rel="self" type="application/atom+xml" href="https://parkerjones.dev/tags/claude-code/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://parkerjones.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-06-26T00:00:00+00:00</updated>
    <id>https://parkerjones.dev/tags/claude-code/atom.xml</id>
    <entry xml:lang="en">
        <title>RTK: Cutting My Claude Code Token Bill by 90% with a Rust Proxy</title>
        <published>2026-06-26T00:00:00+00:00</published>
        <updated>2026-06-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://parkerjones.dev/posts/rtk-token-killer/"/>
        <id>https://parkerjones.dev/posts/rtk-token-killer/</id>
        
        <content type="html" xml:base="https://parkerjones.dev/posts/rtk-token-killer/">&lt;p&gt;I&#x27;ve been running coding agents against real work for a while now, and the thing nobody warns you about is the &lt;em&gt;output tax&lt;&#x2F;em&gt;. Every time the agent runs &lt;code&gt;git status&lt;&#x2F;code&gt;, &lt;code&gt;cargo test&lt;&#x2F;code&gt;, or &lt;code&gt;aws lambda list-functions&lt;&#x2F;code&gt;, the entire wall of text it gets back is fed into the model&#x27;s context. You pay for those tokens once when they arrive — and then again on every single turn after, because that output sits in the context window and gets re-sent until the conversation ends.&lt;&#x2F;p&gt;
&lt;p&gt;Most of that text is noise. ANSI color codes. Timestamps. Table columns I don&#x27;t care about. JSON fields I&#x27;ll never read. So I built a small Rust tool to strip it out before it ever reaches the model. I call it &lt;strong&gt;RTK&lt;&#x2F;strong&gt; — Rust Token Killer.&lt;&#x2F;p&gt;
&lt;p&gt;Here&#x27;s what it&#x27;s done across my last few thousand commands:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;RTK Token Savings (Global Scope)
════════════════════════════════════════════════════════════
Total commands:    6283
Input tokens:      26.9M
Output tokens:     2.5M
Tokens saved:      24.5M (90.9%)
Total exec time:   462m34s (avg 4.4s)
Efficiency meter:  ██████████████████████░░ 90.9%
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Six thousand commands, &lt;strong&gt;24.5 million tokens saved — 90.9%&lt;&#x2F;strong&gt;. That&#x27;s not a microbenchmark; that&#x27;s my actual usage.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-hidden-cost-of-agent-tool-output&quot;&gt;The hidden cost of agent tool output&lt;&#x2F;h2&gt;
&lt;p&gt;When you use a chat model directly, you read its output and the cost stops there. When you put a model in an &lt;em&gt;agent loop&lt;&#x2F;em&gt;, the economics flip. The model runs a tool, the tool&#x27;s stdout&#x2F;stderr comes back as a tool result, and that result becomes part of the running transcript. Every subsequent turn re-sends the whole transcript. So a 4,000-token &lt;code&gt;cargo test&lt;&#x2F;code&gt; dump on turn 3 is still being paid for on turn 30.&lt;&#x2F;p&gt;
&lt;p&gt;The fix isn&#x27;t &quot;run fewer commands.&quot; Agents &lt;em&gt;should&lt;&#x2F;em&gt; poke at the system constantly — that&#x27;s what makes them useful. The fix is to make each command&#x27;s output carry only the information the model actually needs to make its next decision.&lt;&#x2F;p&gt;
&lt;p&gt;That&#x27;s a filtering problem, and filtering is exactly the kind of thing Rust is good at: fast, streaming, zero-allocation where it counts, and cheap enough that wrapping every command in the agent&#x27;s hot loop adds no friction. RTK averages 4.4s per command — and most of that is the underlying command itself, not the proxy.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;how-it-works-transparent-hook-based-rewriting&quot;&gt;How it works: transparent, hook-based rewriting&lt;&#x2F;h2&gt;
&lt;p&gt;The design goal was that I should never have to think about RTK. I don&#x27;t want to teach the agent to &quot;use rtk&quot; — I want every command it already runs to get filtered automatically.&lt;&#x2F;p&gt;
&lt;p&gt;So RTK plugs into Claude Code as a hook. When the agent decides to run &lt;code&gt;git status&lt;&#x2F;code&gt;, the hook rewrites it to &lt;code&gt;rtk git status&lt;&#x2F;code&gt; before execution. RTK runs the real command, reshapes the output, and hands back the lean version. The agent never knows the proxy is there, and the rewrite itself costs zero tokens.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;git status        →   rtk git status      (transparent, 0 tokens overhead)
cargo test        →   rtk cargo test
aws lambda list…  →   rtk aws lambda list-functions
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;There are a handful of meta-commands I do call directly:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;rtk gain              # token-savings analytics (the table above)
rtk gain --history    # per-command history with savings
rtk discover          # mine Claude Code history for missed opportunities
rtk proxy &amp;lt;cmd&amp;gt;       # run a command raw, no filtering (escape hatch)
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;h2 id=&quot;where-the-savings-actually-come-from&quot;&gt;Where the savings actually come from&lt;&#x2F;h2&gt;
&lt;p&gt;Not every command saves the same amount, and that&#x27;s the interesting part. Breaking down my top commands by impact:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;  #  Command                   Count   Saved    Avg%
 1.  rtk cargo test --work…        7   10.8M  100.0%
 2.  rtk aws lambda list-f…        9    3.1M   22.2%
 3.  rtk read                    602    2.7M   18.0%
 4.  rtk git push origin …         1    1.8M  100.0%
 5.  rtk cargo test --work…        1    1.8M  100.0%
 6.  rtk git push -u origi…        1    1.7M  100.0%
 7.  rtk aws cloudformatio…        2  324.0K   50.0%
 8.  rtk:toml ps -ef               6  298.1K   98.7%
 9.  rtk:toml ps aux               5  288.8K   98.5%
10.  rtk gh pr diff               18  110.7K   48.1%
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Three distinct categories show up here:&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Full suppression (100%).&lt;&#x2F;strong&gt; &lt;code&gt;cargo test --workspace&lt;&#x2F;code&gt; and &lt;code&gt;git push&lt;&#x2F;code&gt; produce enormous progress streams — compiler chatter, per-test lines, transfer counters — and the only thing the model needs is &lt;em&gt;did it pass&lt;&#x2F;em&gt; or &lt;em&gt;what failed&lt;&#x2F;em&gt;. When everything succeeds, the right answer is a one-line summary, and the raw 10.8M tokens of test output never enter context. That single command class is my biggest win by a mile.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Reshaping (98%).&lt;&#x2F;strong&gt; &lt;code&gt;ps -ef&lt;&#x2F;code&gt; and &lt;code&gt;ps aux&lt;&#x2F;code&gt; are wide, repetitive tables. Run them through RTK&#x27;s structured &lt;code&gt;:toml&lt;&#x2F;code&gt; output mode and you get a compact, parseable representation — 98%+ smaller, and &lt;em&gt;easier&lt;&#x2F;em&gt; for the model to reason about than a column-aligned ASCII table.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;Trimming (18–50%).&lt;&#x2F;strong&gt; &lt;code&gt;read&lt;&#x2F;code&gt;, &lt;code&gt;gh pr diff&lt;&#x2F;code&gt;, and &lt;code&gt;aws&lt;&#x2F;code&gt; calls don&#x27;t get gutted — the content matters — but there&#x27;s still 18–50% of pure formatting cruft to shave. Note &lt;code&gt;read&lt;&#x2F;code&gt; ran &lt;strong&gt;602 times&lt;&#x2F;strong&gt;: small per-call savings on a high-frequency command adds up to 2.7M tokens.&lt;&#x2F;p&gt;
&lt;p&gt;The lesson I keep relearning: optimize the things you do constantly (&lt;code&gt;read&lt;&#x2F;code&gt;) &lt;em&gt;and&lt;&#x2F;em&gt; the things that dump enormous one-shot payloads (&lt;code&gt;cargo test&lt;&#x2F;code&gt;). The middle is where most of the volume hides.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;structured-output-with-toml-mode&quot;&gt;Structured output with &lt;code&gt;:toml&lt;&#x2F;code&gt; mode&lt;&#x2F;h2&gt;
&lt;p&gt;The &lt;code&gt;:toml&lt;&#x2F;code&gt; variants above aren&#x27;t a gimmick. ASCII tables are optimized for &lt;em&gt;human eyes&lt;&#x2F;em&gt; — alignment, separators, headers repeated for readability. A model doesn&#x27;t need any of that; it needs keys and values. Emitting &lt;code&gt;ps aux&lt;&#x2F;code&gt; as compact TOML drops the token count by ~98% &lt;strong&gt;and&lt;&#x2F;strong&gt; removes the ambiguity of parsing whitespace-aligned columns. Cheaper and more reliable at the same time is a rare trade to win.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;finding-what-you-re-missing-rtk-discover&quot;&gt;Finding what you&#x27;re missing: &lt;code&gt;rtk discover&lt;&#x2F;code&gt;&lt;&#x2F;h2&gt;
&lt;p&gt;The one I reach for when I want to tune things is &lt;code&gt;rtk discover&lt;&#x2F;code&gt;. It reads back through my Claude Code history and flags commands that burned tokens but aren&#x27;t being proxied yet — the long tail of &quot;oh, I run &lt;em&gt;that&lt;&#x2F;em&gt; a lot.&quot; It turns token optimization into a feedback loop instead of a guessing game: ship a filter, run for a week, ask &lt;code&gt;discover&lt;&#x2F;code&gt; what&#x27;s still expensive, repeat.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;when-not-to-filter&quot;&gt;When &lt;em&gt;not&lt;&#x2F;em&gt; to filter&lt;&#x2F;h2&gt;
&lt;p&gt;Filtering is lossy by definition, and sometimes I genuinely need the raw bytes — debugging a tool whose exact output format is the thing I&#x27;m investigating, or chasing a bug &lt;em&gt;in a filter itself&lt;&#x2F;em&gt;. That&#x27;s what &lt;code&gt;rtk proxy &amp;lt;cmd&amp;gt;&lt;&#x2F;code&gt; is for: run it completely untouched. Having a clean escape hatch is what makes aggressive default filtering safe. If the trim ever hides something I needed, I&#x27;m one command away from the truth.&lt;&#x2F;p&gt;
&lt;p&gt;One footgun worth flagging: there&#x27;s a name collision out there. If &lt;code&gt;rtk gain&lt;&#x2F;code&gt; erroring with &quot;command not found,&quot; you&#x27;ve probably got &lt;code&gt;reachingforthejack&#x2F;rtk&lt;&#x2F;code&gt; (a Rust Type Kit) on your &lt;code&gt;PATH&lt;&#x2F;code&gt; instead. &lt;code&gt;which rtk&lt;&#x2F;code&gt; sorts it out.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-i-learned&quot;&gt;What I learned&lt;&#x2F;h2&gt;
&lt;p&gt;I went in thinking of this as a cost optimization, and it is — but the bigger payoff turned out to be &lt;strong&gt;context window hygiene&lt;&#x2F;strong&gt;. Tokens are cheap-ish; &lt;em&gt;context is scarce&lt;&#x2F;em&gt;. Every line of noise I strip is a line that isn&#x27;t crowding out something the model actually needs to remember three turns from now. The 90% I&#x27;m not paying for is nice. The 90% that isn&#x27;t diluting the agent&#x27;s attention is the real win.&lt;&#x2F;p&gt;
&lt;p&gt;Next up: smarter, per-command filter profiles (a &lt;code&gt;cargo test&lt;&#x2F;code&gt; filter shouldn&#x27;t look anything like an &lt;code&gt;aws&lt;&#x2F;code&gt; filter), and packaging RTK so it&#x27;s a one-line install for anyone else running agents against a noisy CLI. If that&#x27;s you, the output tax is real and you&#x27;re almost certainly paying it. It&#x27;s very killable.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;— Parker Jones, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;parkerjones.dev&quot;&gt;parkerjones.dev&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Building a Second Brain for Engineers: an LLM Capture-and-Synthesis Pipeline</title>
        <published>2026-06-26T00:00:00+00:00</published>
        <updated>2026-06-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://parkerjones.dev/posts/second-brain-llm-pipeline/"/>
        <id>https://parkerjones.dev/posts/second-brain-llm-pipeline/</id>
        
        <content type="html" xml:base="https://parkerjones.dev/posts/second-brain-llm-pipeline/">&lt;p&gt;As an engineer, the context you need to do your job is scattered across a dozen systems: decisions made in chat threads, the &lt;em&gt;why&lt;&#x2F;em&gt; behind a change buried in a pull request, design rationale in a wiki page, a commitment made in a meeting nobody wrote down. Most of it decays. Six months later you&#x27;re re-deriving a decision you already made because the only record was a Slack thread that scrolled into oblivion.&lt;&#x2F;p&gt;
&lt;p&gt;I built a pipeline to fix that — an LLM-assisted system that &lt;em&gt;captures&lt;&#x2F;em&gt; raw material from those systems and &lt;em&gt;synthesizes&lt;&#x2F;em&gt; it into a durable, searchable knowledge base. This post is the design, generalized. I&#x27;m deliberately keeping it tool- and employer-neutral; the value is in the architecture, which ports to any stack.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-three-layer-model&quot;&gt;The three-layer model&lt;&#x2F;h2&gt;
&lt;p&gt;The system has three layers, and keeping them separate is the whole game:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;  Layer 1: sources&#x2F;    raw captures, one file per artifact, lightly structured
  Layer 2: wiki&#x2F;       synthesized pages — the compressed, durable knowledge
  Layer 3: schema      the protocol: conventions, frontmatter, ingest rules
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sources&lt;&#x2F;strong&gt; are raw. A captured chat thread, a PR snapshot, a meeting transcript — verbatim, with metadata, written to a file. Cheap and lossless.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The wiki&lt;&#x2F;strong&gt; is synthesized. A synthesis agent reads new sources and folds them into durable pages: a glossary entry, an incident postmortem, a reusable pattern. The wiki&#x27;s job is &lt;em&gt;compression&lt;&#x2F;em&gt;, not mirroring.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;The schema&lt;&#x2F;strong&gt; is the contract both layers obey — filename conventions, frontmatter shape, the ingest protocol. It changes rarely and deliberately.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The insight that made this work: &lt;strong&gt;capture and synthesis are different problems with different failure modes, so decouple them.&lt;&#x2F;strong&gt; Capture must be fast and reliable (you&#x27;re stealing a moment to save something). Synthesis can be slow and batched (it runs later, reads everything new, thinks hard). Couple them and a slow synthesis step blocks you from capturing at all. Decouple them and capture is instant; synthesis happens on its own cadence and reads whatever has accumulated.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-to-capture-per-source&quot;&gt;What to capture, per source&lt;&#x2F;h2&gt;
&lt;p&gt;Every source type — chat, issue tracker, code host, docs&#x2F;wiki, meeting transcripts, ad-hoc paste-ins — gets the same five-part treatment:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Capture-worthy signals.&lt;&#x2F;strong&gt; What&#x27;s actually worth keeping. For a chat platform: threads where a decision is announced (&quot;let&#x27;s go with…&quot;, &quot;approved&quot;, &quot;ship it&quot;), threads where you&#x27;re mentioned and haven&#x27;t replied, high-engagement threads in channels you watch.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Noise to skip.&lt;&#x2F;strong&gt; Bot notifications. Your own status pings. Reaction-only activity. The stuff that would bury the signal.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Output shape.&lt;&#x2F;strong&gt; The frontmatter and filename the capture writes — so the synthesis layer can consume it without guessing.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Cadence.&lt;&#x2F;strong&gt; Daily sweep? On-demand? Weekly?&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Quirks.&lt;&#x2F;strong&gt; The source-specific gotchas (pagination, permalink expiry, HTML-to-markdown fidelity).&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;A captured issue-tracker ticket, for instance, lands as structured frontmatter plus a body:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;yaml&quot;&gt;---
source: issue-tracker
key: PROJ-1234
captured: 2026-06-26T08:00:00-04:00
status: Released
last_event: 2026-06-25T16:22:00-04:00
---
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That &lt;code&gt;last_event&lt;&#x2F;code&gt; field is the high-water mark: on the next sweep, the capturer fetches only events newer than it, instead of re-pulling the whole ticket.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;capture-mechanisms-and-the-trap-everyone-hits&quot;&gt;Capture mechanisms — and the trap everyone hits&lt;&#x2F;h2&gt;
&lt;p&gt;There are three mechanism families, and choosing the right one per source is most of the design:&lt;&#x2F;p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Mechanism&lt;&#x2F;th&gt;&lt;th&gt;Best for&lt;&#x2F;th&gt;&lt;th&gt;Trade-off&lt;&#x2F;th&gt;&lt;&#x2F;tr&gt;&lt;&#x2F;thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Skills&lt;&#x2F;strong&gt; (slash commands)&lt;&#x2F;td&gt;&lt;td&gt;On-demand, mid-conversation capture&lt;&#x2F;td&gt;&lt;td&gt;You have to remember to invoke them&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Hooks&lt;&#x2F;strong&gt; (event-driven)&lt;&#x2F;td&gt;&lt;td&gt;Local events you control&lt;&#x2F;td&gt;&lt;td&gt;Narrower than the name suggests — see below&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Scheduled tasks&lt;&#x2F;strong&gt; (periodic sweeps)&lt;&#x2F;td&gt;&lt;td&gt;External-system polling&lt;&#x2F;td&gt;&lt;td&gt;Latency up to one cadence; needs dedup state&lt;&#x2F;td&gt;&lt;&#x2F;tr&gt;
&lt;&#x2F;tbody&gt;&lt;&#x2F;table&gt;
&lt;p&gt;Here&#x27;s the trap, and it&#x27;s worth stating loudly because it cost me an afternoon: &lt;strong&gt;AI-agent hooks fire on &lt;em&gt;conversation&lt;&#x2F;em&gt; events&lt;&#x2F;strong&gt; — the agent stopped, a tool is about to run, the user submitted a prompt — &lt;strong&gt;not on external events.&lt;&#x2F;strong&gt; There is no &quot;hook on PR opened&quot; or &quot;hook on Slack message.&quot; If you want event-driven capture from an external system, the real path is something outside the agent&#x27;s hook system entirely: a git &lt;code&gt;post-merge&lt;&#x2F;code&gt; hook on your own machine, or a CI action that writes to a synced directory. Conflating &quot;agent hook&quot; with &quot;webhook&quot; sends you building something that can&#x27;t exist.&lt;&#x2F;p&gt;
&lt;p&gt;So the actual recommendation per source is mostly: &lt;strong&gt;scheduled sweep for external systems&lt;&#x2F;strong&gt; (poll daily, dedup against state), &lt;strong&gt;on-demand skill for user-initiated capture&lt;&#x2F;strong&gt; (&lt;code&gt;&#x2F;capture &amp;lt;url&amp;gt;&lt;&#x2F;code&gt; when you decide something matters), and hooks only in the narrow spots where a &lt;em&gt;local&lt;&#x2F;em&gt; event is the trigger.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;dedup-and-freshness-via-filename-conventions&quot;&gt;Dedup and freshness via filename conventions&lt;&#x2F;h2&gt;
&lt;p&gt;State is the enemy of reliability, so I push dedup into filenames instead of a database. Two shapes:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time-stamped capture&lt;&#x2F;strong&gt; — &lt;code&gt;&amp;lt;source&amp;gt;-&amp;lt;id&amp;gt;-&amp;lt;YYYY-MM-DD&amp;gt;.md&lt;&#x2F;code&gt; — for artifacts with no stable identity (a daily chat digest, an ad-hoc article). Each capture is its own file.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Stable-entity capture&lt;&#x2F;strong&gt; — &lt;code&gt;&amp;lt;source&amp;gt;-&amp;lt;id&amp;gt;.md&lt;&#x2F;code&gt; — for things with a canonical identity, recaptured &lt;em&gt;in place&lt;&#x2F;em&gt; (a ticket, a PR, a wiki page).&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;Recapture semantics depend on the source&#x27;s own model:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Append-only, newest-first&lt;&#x2F;strong&gt; for sources with event timelines (tickets, PRs): each recapture prepends a dated section; old content stays.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Versioned-replace with diff preservation&lt;&#x2F;strong&gt; for edit-replace sources (wiki pages): replace the body, but keep the prior version under a &lt;code&gt;## Previous version&lt;&#x2F;code&gt; heading so the diff isn&#x27;t lost.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The filename &lt;em&gt;is&lt;&#x2F;em&gt; the dedup key. Two captures of the same ticket can never land under two different names — which, before I imposed this, is exactly what happened.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;when-does-a-raw-source-become-a-wiki-page&quot;&gt;When does a raw source become a wiki page?&lt;&#x2F;h2&gt;
&lt;p&gt;Not everything earns synthesis. A source gets promoted to its own durable page when &lt;strong&gt;any&lt;&#x2F;strong&gt; of these holds:&lt;&#x2F;p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Recurrence&lt;&#x2F;strong&gt; — the concept shows up 3+ times across captures and notes. Recurring relevance means compression pays off.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Decision-of-record&lt;&#x2F;strong&gt; — it documents a choice that&#x27;ll be referenced later (an architecture decision, an incident root cause). Decisions get a page on first capture; the value is findability.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Reusable pattern&lt;&#x2F;strong&gt; — it describes a technique that applies beyond its origin. A pattern extracted from one incident belongs in the wiki because it&#x27;ll apply to the next one.&lt;&#x2F;li&gt;
&lt;&#x2F;ol&gt;
&lt;p&gt;Everything else stays raw. A one-off thread that resolved itself, a ticket shipped without revisiting — those live in &lt;code&gt;sources&#x2F;&lt;&#x2F;code&gt; as a searchable archive but never clutter the synthesized layer. &lt;strong&gt;Synthesis over enumeration:&lt;&#x2F;strong&gt; one wiki page can cite ten sources. The wiki compresses; it doesn&#x27;t mirror.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-one-rule-you-can-t-skip-sensitive-content&quot;&gt;The one rule you can&#x27;t skip: sensitive content&lt;&#x2F;h2&gt;
&lt;p&gt;The moment you point automated capture at chat and meetings, you&#x27;re one bad sweep away from archiving someone&#x27;s DM, an HR conversation, or customer-confidential material. The rule has to be &lt;strong&gt;default-deny for the sensitive class&lt;&#x2F;strong&gt;: skip private channels, skip DMs, require explicit confirmation before persisting a meeting transcript. It&#x27;s safer to under-capture and manually add than to over-capture and have to scrub. Design this in from line one, not after the first incident.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-this-is-worth-the-ceremony&quot;&gt;Why this is worth the ceremony&lt;&#x2F;h2&gt;
&lt;p&gt;&quot;Why this much machinery for personal notes?&quot; is a fair question, and the honest answer is: because the alternative — capturing by hand, when you remember, in whatever tool is open — doesn&#x27;t scale past a few weeks of good intentions. The pipeline&#x27;s entire purpose is to make &lt;em&gt;capture cheaper than not capturing&lt;&#x2F;em&gt;, and to make synthesis happen whether or not you feel like it that day.&lt;&#x2F;p&gt;
&lt;p&gt;The tools are interchangeable — your chat platform, your agent runner, your note format. The architecture is the durable part: &lt;strong&gt;raw capture decoupled from batched synthesis, dedup encoded in filenames, a promotion rule that keeps the synthesized layer small, and default-deny on anything sensitive.&lt;&#x2F;strong&gt; Build that, and the context you need stops decaying.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;— Parker Jones, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;parkerjones.dev&quot;&gt;parkerjones.dev&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
    <entry xml:lang="en">
        <title>Shipping Claude Skills with Nix: a Reproducible Agent Toolkit Across My Fleet</title>
        <published>2026-06-26T00:00:00+00:00</published>
        <updated>2026-06-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://parkerjones.dev/posts/skills-with-nix/"/>
        <id>https://parkerjones.dev/posts/skills-with-nix/</id>
        
        <content type="html" xml:base="https://parkerjones.dev/posts/skills-with-nix/">&lt;p&gt;A coding agent is only as good as the procedures you hand it. Out of the box, a model improvises every task from scratch; give it a &lt;em&gt;skill&lt;&#x2F;em&gt; — a written procedure for &quot;do TDD,&quot; &quot;diagnose a hard bug,&quot; &quot;turn this into issues&quot; — and it stops guessing and starts following a method you trust. I&#x27;ve built up a collection of these, and once you have more than a handful the real problem isn&#x27;t writing them, it&#x27;s &lt;strong&gt;distribution&lt;&#x2F;strong&gt;: getting the same skills onto every machine I work from, version-pinned, without copy-pasting Markdown around.&lt;&#x2F;p&gt;
&lt;p&gt;I solved that with Nix. Here&#x27;s the setup.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-a-skill-is&quot;&gt;What a skill is&lt;&#x2F;h2&gt;
&lt;p&gt;My skills live in a repo (a fork of &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;mattpocock&#x2F;skills&quot;&gt;Matt Pocock&#x27;s &lt;code&gt;skills&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, credit where it&#x27;s due), organized into buckets — &lt;code&gt;engineering&lt;&#x2F;code&gt;, &lt;code&gt;qa&lt;&#x2F;code&gt;, &lt;code&gt;productivity&lt;&#x2F;code&gt;, &lt;code&gt;personal&lt;&#x2F;code&gt;, &lt;code&gt;misc&lt;&#x2F;code&gt;. Each skill is a directory with a &lt;code&gt;SKILL.md&lt;&#x2F;code&gt;: YAML frontmatter naming it and describing &lt;em&gt;when&lt;&#x2F;em&gt; to use it, then the procedure itself.&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;markdown&quot;&gt;---
name: diagnose
description: Disciplined diagnosis loop for hard bugs and performance
  regressions. Reproduce → minimise → hypothesise → instrument → fix →
  regression-test. Use when user says &amp;quot;diagnose this&amp;quot; &#x2F; &amp;quot;debug this&amp;quot;...
---

# Diagnose
...
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The &lt;code&gt;description&lt;&#x2F;code&gt; is load-bearing — it&#x27;s what the agent matches against to decide whether the skill is relevant. A few I reach for constantly: &lt;code&gt;tdd&lt;&#x2F;code&gt; (red-green-refactor), &lt;code&gt;diagnose&lt;&#x2F;code&gt; (the loop above), &lt;code&gt;to-prd&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;to-issues&lt;&#x2F;code&gt; &#x2F; &lt;code&gt;triage&lt;&#x2F;code&gt; (turning a vague ask into tracked work), and &lt;code&gt;write-a-skill&lt;&#x2F;code&gt; (the skill that writes more skills). Small, composable, model-agnostic. No framework owning my process — just procedures I can read, edit, and trust.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;three-ways-to-install-them&quot;&gt;Three ways to install them&lt;&#x2F;h2&gt;
&lt;p&gt;The repo supports three distribution paths, in increasing order of how much I actually rely on them.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;npx&lt;&#x2F;code&gt;, for anyone.&lt;&#x2F;strong&gt; The zero-commitment path:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;npx skills@latest add parallaxisjones&#x2F;skills
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Pick the skills and agents you want, and you&#x27;re set. Great for trying them on a machine that isn&#x27;t mine.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;2. A symlink script, for a local clone.&lt;&#x2F;strong&gt; If I&#x27;ve cloned the repo, &lt;code&gt;link-skills.sh&lt;&#x2F;code&gt; symlinks every &lt;code&gt;SKILL.md&lt;&#x2F;code&gt; into &lt;code&gt;~&#x2F;.claude&#x2F;skills&#x2F;&lt;&#x2F;code&gt; so the CLI picks them up directly. It&#x27;s idempotent — re-run after pulling — and it specifically guards against the footgun where &lt;code&gt;~&#x2F;.claude&#x2F;skills&lt;&#x2F;code&gt; is &lt;em&gt;itself&lt;&#x2F;em&gt; a symlink back into the repo, which would write per-skill symlinks into my own working copy:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;bash&quot;&gt;if [ -L &amp;quot;$DEST&amp;quot; ]; then
  resolved=&amp;quot;$(readlink -f &amp;quot;$DEST&amp;quot;)&amp;quot;
  case &amp;quot;$resolved&amp;quot; in
    &amp;quot;$REPO&amp;quot;&#x2F;*) echo &amp;quot;refusing to pollute the repo&amp;quot;; exit 1 ;;
  esac
fi
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That defensive check is the kind of thing you write &lt;em&gt;after&lt;&#x2F;em&gt; the first time a script eats its own tail.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;strong&gt;3. Nix, for my actual fleet.&lt;&#x2F;strong&gt; This is the one that matters. My skills repo is a flake input in my &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-one-flake&#x2F;&quot;&gt;system config&lt;&#x2F;a&gt;, and a home-manager module materializes them declaratively on every machine.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-nix-wiring&quot;&gt;The Nix wiring&lt;&#x2F;h2&gt;
&lt;p&gt;Two inputs do the work — my skills repo, and &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;Kyure-A&#x2F;agent-skills-nix&quot;&gt;&lt;code&gt;agent-skills-nix&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;, a home-manager module that knows how to turn a skills repo into materialized files:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;nix&quot;&gt;# flake.nix
inputs = {
  agent-skills-nix = {
    url = &amp;quot;github:Kyure-A&#x2F;agent-skills-nix&amp;quot;;
    inputs.nixpkgs.follows = &amp;quot;nixpkgs&amp;quot;;
    inputs.home-manager.follows = &amp;quot;home-manager&amp;quot;;
  };
  my-skills.url = &amp;quot;github:parallaxisjones&#x2F;skills&amp;quot;;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Then in home-manager I point the module at my repo, filter to the buckets I want live, and enable everything:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;nix&quot;&gt;agent-skills = {
  enable = true;
  sources.mine = {
    input  = &amp;quot;my-skills&amp;quot;;
    subdir = &amp;quot;skills&amp;quot;;
    filter.nameRegex = &amp;quot;^(engineering|misc|personal|productivity)&#x2F;.*&amp;quot;;
  };
  skills.enableAll = true;
  targets.claude.enable = true;
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;That&#x27;s the whole thing. On &lt;code&gt;nixos-rebuild switch&lt;&#x2F;code&gt; (or &lt;code&gt;darwin-rebuild&lt;&#x2F;code&gt; on the Mac), every &lt;code&gt;SKILL.md&lt;&#x2F;code&gt; matching the regex gets written into Claude&#x27;s skills directory. The &lt;code&gt;deprecated&#x2F;&lt;&#x2F;code&gt; bucket is excluded by the filter, so retiring a skill is a one-line regex change, not a manual delete on five machines.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-bother-what-nix-actually-buys-here&quot;&gt;Why bother — what Nix actually buys here&lt;&#x2F;h2&gt;
&lt;p&gt;You could argue the symlink script is simpler, and for one machine it is. The fleet is where Nix earns it:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pinning.&lt;&#x2F;strong&gt; &lt;code&gt;flake.lock&lt;&#x2F;code&gt; records the exact skills commit each machine is on. &quot;Which version of my &lt;code&gt;triage&lt;&#x2F;code&gt; skill is the laptop running?&quot; has an answer, not a shrug.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Parity from scratch.&lt;&#x2F;strong&gt; A fresh machine reaches full skill parity as a side effect of building its system config. There&#x27;s no separate &quot;and don&#x27;t forget to install your skills&quot; step — it&#x27;s the same &lt;code&gt;switch&lt;&#x2F;code&gt; that installs my shell and my packages.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;Atomic rollback.&lt;&#x2F;strong&gt; If a skill edit makes the agent behave worse, rolling back the generation rolls back the skills with it.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;There&#x27;s a framing I lean on for deciding what to manage this way — think of three zones. &lt;em&gt;Zone 1&lt;&#x2F;em&gt; is stable, declarative config (the home-manager module enabling skills). &lt;em&gt;Zone 3&lt;&#x2F;em&gt; is runtime state that should never touch Nix (per-conversation agent memory). Skills sit in &lt;strong&gt;Zone 2&lt;&#x2F;strong&gt;: authored content, edited as plain files in their repo, but &lt;em&gt;materialized&lt;&#x2F;em&gt; onto each machine by Nix. Nix owns where they land and which version; I own what they say.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-honest-caveat&quot;&gt;The honest caveat&lt;&#x2F;h2&gt;
&lt;p&gt;Nix doesn&#x27;t validate skill &lt;em&gt;content&lt;&#x2F;em&gt; — a &lt;code&gt;SKILL.md&lt;&#x2F;code&gt; with a bad &lt;code&gt;description&lt;&#x2F;code&gt; will deploy just as reliably as a good one. This pipeline guarantees distribution and pinning, not quality. The quality comes from treating skills like code: review them, iterate on the descriptions when the agent picks the wrong one, and delete the ones that stop earning their place (that&#x27;s what &lt;code&gt;deprecated&#x2F;&lt;&#x2F;code&gt; is for).&lt;&#x2F;p&gt;
&lt;p&gt;But that&#x27;s the right division of labor. Writing a good procedure is human work. Making sure that procedure is &lt;em&gt;identically present on every machine I touch&lt;&#x2F;em&gt; is exactly the kind of toil Nix exists to kill. Treat your agent&#x27;s skills as part of your declarative system, not as dotfiles you sync by hand.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;— Parker Jones, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;parkerjones.dev&quot;&gt;parkerjones.dev&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
