<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel>
  <title>jchimp.tech</title>
  <link>https://jchimp.tech/</link>
  <atom:link href="https://jchimp.tech/rss.xml" rel="self" type="application/rss+xml"/>
  <description>Lab notes from a SysAdmin / Developer / Homelab</description>
  <item>
    <title>It's a Function, in a Loop, in a Loop</title>
    <link>https://jchimp.tech/blog/its-a-function-in-a-loop-in-a-loop/</link>
    <guid>https://jchimp.tech/blog/its-a-function-in-a-loop-in-a-loop/</guid>
    <pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate>
    <description>A language model has no memory and runs nothing. Streaming text, a chat that follows the thread, an agent that reads your files — all of it is the same stateless function call, wrapped in loops somebody else wrote.</description>
    <content:encoded><![CDATA[<p>A language model is a function. You hand it a list of numbers. It hands you back a score for every number that could come next.</p>
<p>That's the whole thing. It does not remember you. It does not run anything. It does not decide when to stop talking. Every capability you associate with "AI" is somebody wrapping that one function call in a loop and doing the interesting work in the wrapper.</p>
<p>Text streaming out a word at a time. A chat that follows the thread across twenty turns. An agent that reads your files, runs your tests, and reports back. Same call. Different loops.</p>
<p>Once you see the loops, the magic doesn't disappear — it moves. The mysterious part stops being "how does it talk to me" and becomes "how did those numbers get so good," which is a much better thing to be confused about.</p>
<p>I'm going to build this up in three layers, and there's no math in any of them. If you want to see the function itself — the actual matrix multiplies, the attention mechanism, a repo you can clone — that's the companion piece. Start here first, though. The loops are what makes that article make sense, and almost nobody explains them before diving into the linear algebra.</p>
<h2 id="the-function-scores-every-option-it-never-picks-one">The function scores every option. It never picks one.<a class="headerlink" href="#the-function-scores-every-option-it-never-picks-one" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>A weather forecast says 70% rain. It doesn't own an umbrella, and it doesn't decide whether you bring one. Same deal here: feed a model the text so far, and it hands back probabilities for the next token — never a token.</p>
<p>Something else has to pick. That's a decoding algorithm, and it lives outside the model: always grab the top score and you get a model that repeats itself into a rut. Roll a weighted die instead and you get something that reads like writing. Same probabilities, different code doing the choosing.</p>
<p><img loading="lazy" alt="Figure 1" src="/blog/its-a-function-in-a-loop-in-a-loop/fig-01-scores-not-text.svg"></p>
<p><strong>Fig. 1</strong> is the line the rest of this article hangs off. Left side is the model — numbers in, numbers out. Right side is code somebody wrote. So "the AI decided to…" is shorthand. It didn't decide. It published odds, and the decoding loop picked.</p>
<p>One more thing worth pinning down: the model is a fixed function. Same text in, same probabilities out, every single call, forever. Whatever "memory" of a conversation it seems to have is just old text getting stuffed back into the next call — the model itself keeps nothing between them.</p>
<h2 id="a-paragraph-is-just-the-same-call-looped">A paragraph is just the same call, looped.<a class="headerlink" href="#a-paragraph-is-just-the-same-call-looped" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>The model scores one position. You want a paragraph, so you call the same function again — same signature, same forecast, one more token further along.</p>
<div class="language-python highlight"><pre><span></span><code><span class="n">tokens</span> <span class="o">=</span> <span class="n">encode</span><span class="p">(</span><span class="n">your_prompt</span><span class="p">)</span>

<span class="n">repeat</span> <span class="n">until</span> <span class="n">you</span><span class="s1">'ve had enough:</span>
    <span class="n">scores</span> <span class="o">=</span> <span class="n">model</span><span class="p">(</span><span class="n">tokens</span><span class="p">)</span>      <span class="c1"># the one function call</span>
    <span class="n">pick</span>   <span class="o">=</span> <span class="n">weighted_die</span><span class="p">(</span><span class="n">scores</span><span class="p">)</span>
    <span class="n">tokens</span> <span class="o">=</span> <span class="n">tokens</span> <span class="o">+</span> <span class="p">[</span><span class="n">pick</span><span class="p">]</span>    <span class="c1"># glue it on the end</span>
</code></pre></div>
<p>Four lines, and one of them is the model. That's generation. Score, pick, append, go again — feeding the output back in as the next input, over and over, until you hit a stop token or a cap you set.</p>
<p>Two things fall out of that loop immediately.</p>
<p><strong>Streaming isn't a UI trick.</strong> Tokens show up one at a time in the chat window because they're <em>produced</em> one at a time. The loop can't run ahead of itself — token 40 literally cannot be computed until token 39 exists, because token 39 is part of the input that produces it. That typewriter effect isn't a designer being cute. It's the loop, exposed.</p>
<p><strong>The context window is a hard limit, not a fading memory.</strong> The model can only accept a fixed number of tokens — a real number, baked into its architecture. Feed it one more than that and it doesn't get vague or fuzzy. It crashes. So the loop crops: before every call, it slices off the oldest tokens to fit.</p>
<p>That's worth saying plainly, because the fading-memory metaphor is everywhere and it's wrong. Nothing decays. Nothing gets deprioritized. The old tokens weren't forgotten — <strong>they were never sent</strong>. The crop is a pair of scissors, not a leaky bucket.</p>
<p>There's one dial on this loop worth knowing by name: <strong>temperature</strong>. It reshapes the die before you roll — low values sharpen toward the favorite, high values flatten toward chaos. It's a single division, and it's the entire "creativity slider" in every LLM API you've used. If you want to grab it and shove it around on real distributions from a real model, there's a playground in the companion article.</p>
<h2 id="what-feels-like-a-conversation-is-one-function-called-on-a-loop">What feels like a conversation is one function, called on a loop.<a class="headerlink" href="#what-feels-like-a-conversation-is-one-function-called-on-a-loop" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>Now the part that reframes everything. The function is stateless. It has no idea a conversation is happening.</p>
<p>So how does a chatbot follow the thread across twenty turns?</p>
<p>It doesn't. <strong>The outer loop re-sends the entire conversation on every turn.</strong></p>
<div class="language-python highlight"><pre><span></span><code><span class="n">history</span> <span class="o">=</span> <span class="n">system_prompt</span>

<span class="n">repeat</span> <span class="n">forever</span><span class="p">:</span>
    <span class="n">history</span> <span class="o">=</span> <span class="n">history</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">User: "</span> <span class="o">+</span> <span class="n">read_input</span><span class="p">()</span>
    <span class="n">history</span> <span class="o">=</span> <span class="n">history</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">Assistant: "</span> <span class="o">+</span> <span class="n">generate</span><span class="p">(</span><span class="n">history</span><span class="p">)</span>   <span class="c1"># the inner loop</span>
</code></pre></div>
<p>That's a chatbot. Two loops. The inner one produces a turn one token at a time. The outer one staples your new message onto the end of a string and runs the inner one again. Nothing was added to the model. Nothing persists between calls except a string that keeps getting longer.</p>
<p><img loading="lazy" alt="Figure 2 — the conversation is one growing string" src="/blog/its-a-function-in-a-loop-in-a-loop/fig-02-the-resend.svg"></p>
<p><strong>Fig. 2</strong> tracks that string across three turns. Turn 3 is the interesting one: the string has outgrown the context window, so the crop from the last section fires and eats the front of the conversation before the call goes out.</p>
<p>A pile of things stop being mysterious the moment you see that:</p>
<ul>
<li><strong>The context window isn't memory, it's a re-send budget.</strong> You're not filling up the model's brain. You're paying to retransmit the same conversation on every single turn. That's why long chats get expensive and slow — turn 40 ships forty turns of text.</li>
<li><strong>The system prompt is just text stapled to the front.</strong> There's no privileged channel, no special instruction port. It's early in the same string as everything else, which is exactly why prompt injection works: the model has no mechanism to tell your instructions from text that arrived later claiming to be instructions. It's all one string, and it's all the same kind of thing.</li>
<li><strong>"It forgot what I said" means it fell off the crop.</strong> Your message was on the wrong side of the scissors. It didn't fade, it wasn't deprioritized, it wasn't in the call at all.</li>
<li><strong>It doesn't learn from talking to you.</strong> Nothing in the generation loop updates the model. The numbers are frozen the moment training ends, months before you typed anything. A model that seems to "remember" you across sessions has a harness quietly pasting notes into the string — that's a database, not learning.</li>
<li><strong>Regenerating gives a different answer because you rolled the die again.</strong> Same string, same scores, different roll. Nothing changed its mind.</li>
</ul>
<h2 id="a-tool-call-is-just-another-prediction-in-the-same-loop">A tool call is just another prediction, in the same loop.<a class="headerlink" href="#a-tool-call-is-just-another-prediction-in-the-same-loop" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>Nothing new happened. The model predicted some text that looks like a request — wrap one more loop around that, where your code checks for the pattern and acts on it, and you've got <strong>ReAct</strong>, <strong>tool calling</strong>, <strong>MCP</strong>, and everything currently being sold as "agentic."</p>
<div class="language-python highlight"><pre><span></span><code><span class="n">repeat</span> <span class="n">until</span> <span class="n">done</span><span class="p">:</span>
    <span class="n">text</span> <span class="o">=</span> <span class="n">generate</span><span class="p">(</span><span class="n">history</span><span class="p">)</span>          <span class="c1"># the chat loop, from above</span>
    <span class="n">history</span> <span class="o">=</span> <span class="n">history</span> <span class="o">+</span> <span class="n">text</span>
    <span class="k">if</span> <span class="n">looks_like_a_tool_call</span><span class="p">(</span><span class="n">text</span><span class="p">):</span>  <span class="c1"># your code decides this</span>
        <span class="n">result</span>  <span class="o">=</span> <span class="n">run_tool</span><span class="p">(</span><span class="n">parse</span><span class="p">(</span><span class="n">text</span><span class="p">))</span>
        <span class="n">history</span> <span class="o">=</span> <span class="n">history</span> <span class="o">+</span> <span class="n">result</span>    <span class="c1"># paste it on and call again</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">done</span> <span class="o">=</span> <span class="n">true</span>
</code></pre></div>
<p><img loading="lazy" alt="Figure 3 — nothing is added, only nested" src="/blog/its-a-function-in-a-loop-in-a-loop/fig-03-loop-nesting.svg"></p>
<p><strong>Fig. 3</strong> is the honest picture of the whole stack. Four boxes, and every single wrapper's only move is <em>append something and call the model again</em>. The model didn't gain a capability at any layer. The harness did.</p>
<p>Read that <code>if</code> closely, because it's where a lot of confusion about AI safety gets planted. <strong>The model never runs anything.</strong> It produced text. Your code pattern-matched that text, decided it looked like a request, and chose to execute something. A tool call is a suggestion, written in a format you agreed in advance to honor.</p>
<p>So the sandbox is the safety layer, not the model. If the parser is loose, or the tool accepts arguments it shouldn't, or it runs with more permission than the task needs — that failure is yours. The model is still doing the only thing it does: publishing a forecast.</p>
<h2 id="the-cheat-block">The cheat block<a class="headerlink" href="#the-cheat-block" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<table>
<thead>
<tr>
<th>You've heard</th>
<th>What it actually is</th>
<th>Why it matters</th>
</tr>
</thead>
<tbody>
<tr>
<td>The model</td>
<td>A function: tokens in, a score per possible next token out</td>
<td>It never emits text and never acts</td>
</tr>
<tr>
<td>Tokens</td>
<td>Integers standing in for text chunks</td>
<td>Everything is a list of numbers, in and out</td>
</tr>
<tr>
<td>Context window</td>
<td>A hard cap on how many tokens fit in one call</td>
<td>Overflow gets cropped, not forgotten</td>
</tr>
<tr>
<td>Generation</td>
<td>The call in a loop, feeding output back as input</td>
<td>Streaming is the loop, not a UI effect</td>
</tr>
<tr>
<td>Temperature</td>
<td>One division that reshapes the die before you roll</td>
<td>Same model, different personality</td>
</tr>
<tr>
<td>Chat</td>
<td>That loop, plus append-and-resend the whole history</td>
<td>Long chats cost more because you resend everything</td>
</tr>
<tr>
<td>System prompt</td>
<td>Text stapled to the front of the same string</td>
<td>No privileged channel — hence prompt injection</td>
</tr>
<tr>
<td>Agent / ReAct</td>
<td>That loop, plus run-a-tool-and-append</td>
<td>Your code runs the tool, so your sandbox is the safety layer</td>
</tr>
<tr>
<td>Hallucination</td>
<td>It sampled a plausible token</td>
<td>There is no fact check anywhere in the loop</td>
</tr>
<tr>
<td>"It learned from our chat"</td>
<td>A harness writing notes into the string</td>
<td>That's a database</td>
</tr>
</tbody>
</table>
<h2 id="your-turn">Your turn<a class="headerlink" href="#your-turn" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>Here's the exercise that made this stick for me, and it takes about ninety seconds with no code at all.</p>
<p>Open whatever chat assistant you use. Ask it something, get an answer, then ask a follow-up that depends entirely on the first answer — "why?" works fine. Now picture what actually crossed the wire on that second call: your first message, its first answer, and your "why," all concatenated into one string and shipped whole. Not a reference to the earlier turn. The earlier turn itself, again.</p>
<p>Then keep going until it loses the thread, and notice you can predict roughly when. That's not the model getting tired. That's the scissors.</p>
<p>Once that clicks, the natural next question is what's inside the box — how a pile of numbers turns a list of tokens into a decent guess about the next one. That's where the actual machinery lives, and I built it three times in an afternoon to find out: A Transformer Is Just a Function Call.</p>]]></content:encoded>
  </item>
  <item>
    <title>The Decode Loop and the MCP Handoff</title>
    <link>https://jchimp.tech/blog/the-decode-loop-and-the-mcp-handoff/</link>
    <guid>https://jchimp.tech/blog/the-decode-loop-and-the-mcp-handoff/</guid>
    <pubDate>Fri, 07 Aug 2026 00:00:00 +0000</pubDate>
    <description>Overview of what runs between a prompt and a tool result in an LLM system.</description>
    <content:encoded><![CDATA[<p>A language model generates one token at a time and does nothing else. It reads a sequence of tokens, produces a probability distribution over the next one, and returns. Sampling, stopping, calling tools, and splicing results back into the conversation are all done by the program around the model, the harness. The distinction sounds academic until something breaks, at which point it is the only thing that matters: latency, caching, stop conditions, tool routing, and the security boundary most architecture diagrams leave out all live in the harness, not the model.</p>
<h2 id="the-loop-is-four-steps">The loop is four steps<a class="headerlink" href="#the-loop-is-four-steps" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>The decode loop is four steps and one exit: forward pass, sample, append, check for a stop.</p>
<div class="language-python highlight"><pre><span></span><code><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
    <span class="n">logits</span> <span class="o">=</span> <span class="n">model</span><span class="o">.</span><span class="n">forward</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>       <span class="c1"># the only neural step</span>
    <span class="n">token</span>  <span class="o">=</span> <span class="n">sample</span><span class="p">(</span><span class="n">logits</span><span class="p">,</span> <span class="n">temp</span><span class="p">,</span> <span class="n">top_p</span><span class="p">)</span>  <span class="c1"># harness</span>
    <span class="n">context</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">token</span><span class="p">)</span>                 <span class="c1"># harness</span>
    <span class="k">if</span> <span class="n">classify_stop</span><span class="p">(</span><span class="n">token</span><span class="p">,</span> <span class="n">context</span><span class="p">):</span>     <span class="c1"># harness</span>
        <span class="k">break</span>
</code></pre></div>
<p>Streaming text in a chat window is this loop and not a feature; each token that appears is one full pass through it. The single non-obvious piece is the KV cache. The first pass computes every position in the context; every pass after it computes exactly one and reads the rest from cache. That is the whole reason a 2000-token reply does not get 2000 times slower by the end, and also the reason that inserting anything into the context mid-stream forces a recompute over the inserted span.</p>
<h2 id="interactive-tool-call-demo">Interactive tool call demo<a class="headerlink" href="#interactive-tool-call-demo" title="Link to this section" rel="noopener noreferrer">#</a></h2>

<style>

/* ==========================================================================
   Page + widget
   ========================================================================== */
*{box-sizing:border-box}

.sub{color:var(--jc-text-dim);font-size:14px;max-width:70ch}
.legend{display:flex;gap:18px;flex-wrap:wrap;margin-top:14px;font-family:var(--jc-font-mono);font-size:11px;letter-spacing:.02em}
.legend span{display:inline-flex;align-items:center;gap:7px;color:var(--jc-text-dim)}
.dot{width:10px;height:10px;display:inline-block;border:1px solid currentColor}
.dot.m{background:var(--jc-wash-green);color:var(--jc-accent-ink)}
.dot.h{background:var(--jc-wash-blue);color:var(--jc-link-color)}
.dot.s{background:var(--jc-wash-brass);color:var(--jc-warn)}

/* theme toggle — mono chip, honors OS pref by default */
.modebtn{
  font-family:var(--jc-font-mono);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
  background:var(--jc-surface);color:var(--jc-text-dim);border:1px solid var(--jc-border);
  padding:6px 10px;cursor:pointer;white-space:nowrap;
}
.modebtn:hover{color:var(--jc-text);border-color:var(--jc-accent)}

.grid{display:grid;grid-template-columns:minmax(0,460px) minmax(0,1fr);gap:18px}
@media(max-width:880px){.grid{grid-template-columns:1fr}}

/* --- registration-mark card frame --- */
.bp{position:relative;border:1px solid var(--jc-border);background:var(--jc-surface);padding:14px}
.bp>.corner{position:absolute;width:11px;height:11px;color:var(--jc-accent)}
.bp>.corner.tl{top:-6px;left:-6px}.bp>.corner.tr{top:-6px;right:-6px}
.bp>.corner.bl{bottom:-6px;left:-6px}.bp>.corner.br{bottom:-6px;right:-6px}

.card h2{
  font-family:var(--jc-font-mono);font-size:10.5px;letter-spacing:var(--jc-tracking-label);
  text-transform:uppercase;color:var(--jc-text-dim);margin:0 0 10px;font-weight:500;
}

svg{width:100%;height:auto;display:block}
.nodeBox rect{fill:var(--jc-surface);stroke-width:1.2;rx:0}
.nlabel{font:600 11px/1 var(--jc-font-mono);fill:var(--jc-text);letter-spacing:.02em}
.nsub{font:500 8.5px/1 var(--jc-font-mono);fill:var(--jc-text-dim);letter-spacing:.03em}
.m rect{stroke:var(--jc-accent-ink);fill:var(--jc-wash-green)}
.h rect{stroke:var(--jc-link-color);fill:var(--jc-wash-blue)}
.s rect{stroke:var(--jc-warn);fill:var(--jc-wash-brass)}
.m .nsub{fill:var(--jc-accent-ink)}.h .nsub{fill:var(--jc-link-color)}.s .nsub{fill:var(--jc-warn)}
.edge{stroke:var(--jc-border);stroke-width:1.4;fill:none;marker-end:url(#arr)}
.elabel{font:500 8px/1 var(--jc-font-mono);fill:var(--jc-text-dim);letter-spacing:.02em}

/* active node — heavier hairline, no glow/shadow per brand rules */
.nodeBox.on rect{stroke-width:2.4;stroke:var(--jc-error)}
.nodeBox.on .nlabel{fill:var(--jc-text);font-weight:700}

/* context buffer */
.buffer{display:flex;flex-direction:column;gap:9px;max-height:420px;overflow:auto;padding-right:4px}
.seg{border-left:3px solid var(--jc-border);padding:7px 10px;background:var(--jc-bg)}
.seg .role{font-family:var(--jc-font-mono);font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;
  color:var(--jc-text-dim);margin-bottom:5px;display:flex;justify-content:space-between;gap:8px}
.seg .role em{font-style:normal;color:var(--jc-text-dim);opacity:.8;font-size:9px;letter-spacing:.04em;text-transform:none}
.seg.sys{border-color:var(--jc-term);background:var(--jc-term-bg)} .seg.sys .role{color:var(--jc-term)}
.seg.user{border-color:var(--jc-link-color);background:var(--jc-link-bg)} .seg.user .role{color:var(--jc-link-color)}
.seg.asst{border-color:var(--jc-accent-ink);background:var(--jc-wash-green)} .seg.asst .role{color:var(--jc-accent-ink)}
.seg.tool{border-color:var(--jc-warn);background:var(--jc-warn-bg)} .seg.tool .role{color:var(--jc-warn)}
.toks{display:flex;flex-wrap:wrap;gap:3px}
.tok{font-family:var(--jc-font-mono);font-size:11.5px;padding:1px 4px;background:var(--jc-surface);
  color:var(--jc-text);border:1px solid var(--jc-border);white-space:pre}
.tok.sp{background:var(--jc-code-bg);border-color:var(--jc-code-bg);color:var(--jc-logo);font-weight:700}
.tok.fresh{animation:pop .45s ease both}
.caret{display:inline-block;width:7px;height:15px;background:var(--jc-accent-ink);animation:blink 1s steps(1) infinite;vertical-align:-2px;margin-left:1px}
@keyframes pop{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
@keyframes blink{50%{opacity:0}}
@media(prefers-reduced-motion:reduce){.tok.fresh{animation:none}.caret{animation:none}}

/* detail / scope panel — the code tile, same in both modes */
.scope{margin-top:14px}
.scope pre{margin:0;font-family:var(--jc-font-mono);font-size:11px;background:var(--jc-code-bg);
  border:1px solid var(--jc-border);padding:10px;overflow:auto;color:var(--jc-code-text);line-height:1.7}
.scope .k{color:var(--jc-code-num)} .scope .str{color:var(--jc-code-str)} .scope .num{color:var(--jc-code-flag)}
.logit{display:flex;align-items:center;gap:8px;font-family:var(--jc-font-mono);font-size:11px;margin:3px 0}
.logit .lt{width:96px;color:var(--jc-text);text-align:right;white-space:pre}
.logit .bar{height:11px;background:var(--jc-accent-ink);opacity:.85}
.logit .lp{color:var(--jc-text-dim);width:42px}
.logit.pick .lt{color:var(--jc-accent-ink);font-weight:700}

/* status + controls */
.status{display:flex;gap:14px;align-items:flex-start;margin-top:18px;
  background:var(--jc-surface);border:1px solid var(--jc-border);padding:14px 16px}
.phase{flex:0 0 auto;width:52px;height:52px;display:grid;place-items:center;
  font-family:var(--jc-font-mono);font-weight:700;font-size:17px;
  border:1.4px solid var(--jc-link-color);color:var(--jc-link-color);background:var(--jc-wash-blue)}
.phase.m{border-color:var(--jc-accent-ink);color:var(--jc-accent-ink);background:var(--jc-wash-green)}
.phase.s{border-color:var(--jc-warn);color:var(--jc-warn);background:var(--jc-wash-brass)}
.stxt{flex:1;min-width:0}
.stitle{font-weight:700;font-size:14.5px;margin:0 0 3px}
.sbody{color:var(--jc-text-dim);font-size:12.5px;margin:0}
.note{margin-top:8px;font-family:var(--jc-font-mono);font-size:11.5px;color:var(--jc-text);
  background:var(--jc-bg);border:1px dashed var(--jc-border);padding:7px 10px}
.note b{color:var(--jc-link-color);font-weight:600}

.controls{display:flex;gap:9px;align-items:center;margin-top:16px;flex-wrap:wrap}
button{font-family:var(--jc-font-body);font-weight:600;font-size:13px;
  background:var(--jc-accent);color:var(--jc-on-accent);border:1px solid var(--jc-accent-ink);
  padding:9px 16px;cursor:pointer;transition:.15s}
button:hover:not(:disabled){background:var(--jc-accent-ink)}
button:disabled{opacity:.4;cursor:not-allowed}
button.ghost{background:transparent;color:var(--jc-text);border:1px solid var(--jc-border)}
button.ghost:hover:not(:disabled){border-color:var(--jc-accent-ink);background:transparent}
.counter{margin-left:auto;font-family:var(--jc-font-mono);color:var(--jc-text-dim);font-size:11.5px;letter-spacing:.03em}
.track{height:3px;background:var(--jc-border);margin-top:12px;overflow:hidden}
.track i{display:block;height:100%;background:var(--jc-accent-ink);transition:width .3s ease}
kbd{font-family:var(--jc-font-mono);background:var(--jc-surface);border:1px solid var(--jc-border);
  border-bottom-width:2px;padding:0 5px;font-size:10px;color:var(--jc-text-dim)}
footer.foot{margin-top:22px;font-family:var(--jc-font-mono);font-size:10.5px;letter-spacing:.08em;
  text-transform:uppercase;color:var(--jc-text-dim);text-align:right}
</style>

  <header>
    <div class="htext">
      <div class="legend">
        <span><i class="dot m"></i>Model &middot; forward pass &amp; token emission</span>
        <span><i class="dot h"></i>Harness &middot; the loop, sampling, routing</span>
        <span><i class="dot s"></i>MCP server &middot; the real execution</span>
      </div>
    </div>
  </header>

  <div class="grid">
    <!-- LEFT: the machine -->
    <div class="bp card">
      <i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
      <h2>The machine</h2>
      <svg viewBox="0 0 480 500" aria-label="decode loop diagram">
        <defs>
          <marker id="arr" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
            <path d="M0 0L10 5L0 10z" fill="currentColor" style="color:var(--jc-text-dim)"/>
          </marker>
        </defs>

        <!-- context inflow -->
        <text x="6" y="58" class="elabel">context</text>
        <path class="edge" d="M6 62 L40 62"/>

        <!-- top loop edges -->
        <path class="edge" d="M160 62 L300 62"/>            <!-- FWD->SAMPLE -->
        <path class="edge" d="M360 84 L360 150"/>           <!-- SAMPLE->APPEND -->
        <path class="edge" d="M300 172 L160 172"/>          <!-- APPEND->STOP -->
        <path class="edge" d="M100 150 L100 84"/>           <!-- STOP->FWD loopback -->
        <text x="106" y="120" class="elabel">not done &uarr;</text>

        <!-- branch down -->
        <path class="edge" d="M100 194 L100 270"/>
        <text x="106" y="236" class="elabel">tool-call complete</text>
        <text x="106" y="248" class="elabel">&rarr; HALT</text>
        <path class="edge" d="M100 314 L100 345"/>          <!-- PARSE->REQ -->
        <path class="edge" d="M100 389 L100 420"/>          <!-- REQ->SRV -->
        <path class="edge" d="M160 442 L330 442"/>          <!-- SRV->INJECT -->
        <text x="196" y="436" class="elabel">JSON-RPC result</text>

        <!-- resume perimeter route: INJECT -> right -> top -> FWD -->
        <path class="edge" d="M450 430 L466 430 L466 18 L100 18 L100 40"/>
        <text x="370" y="13" class="elabel">resume decode</text>

        <!-- nodes -->
        <g class="nodeBox m" id="n-FWD"><rect x="40" y="40" width="120" height="44"/>
          <text class="nlabel" x="100" y="60" text-anchor="middle">FORWARD PASS</text>
          <text class="nsub" x="100" y="73" text-anchor="middle">ctx &rarr; logits (KV cache)</text></g>

        <g class="nodeBox h" id="n-SAMPLE"><rect x="300" y="40" width="120" height="44"/>
          <text class="nlabel" x="360" y="60" text-anchor="middle">SAMPLE</text>
          <text class="nsub" x="360" y="73" text-anchor="middle">temp / top-p &rarr; 1 token</text></g>

        <g class="nodeBox h" id="n-APPEND"><rect x="300" y="150" width="120" height="44"/>
          <text class="nlabel" x="360" y="170" text-anchor="middle">APPEND</text>
          <text class="nsub" x="360" y="183" text-anchor="middle">token &rarr; context</text></g>

        <g class="nodeBox h" id="n-STOP"><rect x="40" y="150" width="120" height="44"/>
          <text class="nlabel" x="100" y="170" text-anchor="middle">STOP?</text>
          <text class="nsub" x="100" y="183" text-anchor="middle">EOS / stop / tool</text></g>

        <g class="nodeBox h" id="n-PARSE"><rect x="40" y="270" width="120" height="44"/>
          <text class="nlabel" x="100" y="290" text-anchor="middle">PARSE CALL</text>
          <text class="nsub" x="100" y="303" text-anchor="middle">tokens &rarr; {name,args}</text></g>

        <g class="nodeBox h" id="n-MCP_REQ"><rect x="40" y="345" width="120" height="44"/>
          <text class="nlabel" x="100" y="365" text-anchor="middle">MCP CLIENT</text>
          <text class="nsub" x="100" y="378" text-anchor="middle">tools/call &rarr; transport</text></g>

        <g class="nodeBox s" id="n-MCP_SRV"><rect x="40" y="420" width="120" height="44"/>
          <text class="nlabel" x="100" y="440" text-anchor="middle">MCP SERVER</text>
          <text class="nsub" x="100" y="453" text-anchor="middle">execute for real</text></g>

        <g class="nodeBox h" id="n-INJECT"><rect x="330" y="420" width="120" height="44"/>
          <text class="nlabel" x="390" y="440" text-anchor="middle">INJECT RESULT</text>
          <text class="nsub" x="390" y="453" text-anchor="middle">append tool tokens</text></g>
      </svg>

      <div class="scope" id="scope"></div>
    </div>

    <!-- RIGHT: the context buffer -->
    <div class="bp card">
      <i class="corner tl"></i><i class="corner tr"></i><i class="corner bl"></i><i class="corner br"></i>
      <h2>Context buffer &middot; the one growing token sequence</h2>
      <div class="buffer" id="buffer"></div>
    </div>
  </div>

  <!-- status -->
  <div class="status">
    <div class="phase" id="phase">0</div>
    <div class="stxt">
      <p class="stitle" id="stitle"></p>
      <p class="sbody" id="sbody"></p>
      <div class="note" id="note"></div>
    </div>
  </div>

  <div class="track"><i id="track"></i></div>
  <div class="controls">
    <button class="ghost" id="reset">&#8635; Reset</button>
    <button class="ghost" id="prev">&larr; Back</button>
    <button id="next">Step &rarr;</button>
    <button class="ghost" id="play">&#9654; Auto</button>
    <span class="counter"><kbd>&larr;</kbd> <kbd>&rarr;</kbd> to step &nbsp;&middot;&nbsp; <span id="counter"></span></span>
  </div>

<script>

// ---- token helpers ----
const tk = s => s.split(/(\s+)/).filter(x=>x!=="").map(t=>({t, sp:false}));
const sp = t => [{t, sp:true}];
let GEN = 0; // index of the snapshot currently being authored
const mark = arr => arr.map(o=>({...o, gen:GEN}));

// ---- build the script of full scene states ----
const STATES = [];
let buf = [];
function seg(role, cls, meta){ const s={role, cls, toks:[], meta}; buf.push(s); return s; }
function add(s, toks){ s.toks.push(...mark(toks)); }
function step(meta, mutate){ GEN = STATES.length; if(mutate) mutate(); STATES.push({ buf: structuredClone(buf), ...meta }); }

// seed
let sysSeg = seg('system + tools','sys','from MCP tools/list at startup');
add(sysSeg, tk('You are a helpful assistant.'));
add(sysSeg, sp('⟨tool⟩'));
add(sysSeg, tk('get_weather(city, state) → current conditions'));
add(sysSeg, sp('⟨/tool⟩'));
let userSeg = seg('user','user');
add(userSeg, tk("What's the weather in Polson, MT?"));

let asst1, toolSeg, asst2;

const L = { // helper for logits panel html
  bars(rows){ return rows.map(r=>`<div class="logit ${r.pick?'pick':''}"><span class="lt">${r.t}</span><span class="bar" style="width:${r.p*1.7}px"></span><span class="lp">${(r.p/100).toFixed(2)}</span></div>`).join(''); }
};
const J = s => s
  .replace(/"(\w+)":/g,'"<span class="k">$1</span>":')
  .replace(/: ?"([^"]*)"/g,': "<span class="str">$1</span>"')
  .replace(/: ?(\d+)/g,': <span class="num">$1</span>');

// 0 — setup
step({
  active:null, lane:'h', phase:'0',
  title:'Setup — context is loaded, model hasn’t run yet',
  body:'The system prompt, the tool schemas (fetched once via the MCP handshake + tools/list), and the user message are all serialized into a single token sequence. That sequence is the only input the model ever sees.',
  note:'Nothing has executed. Tools are just <b>text descriptions</b> sitting in the prompt right now.',
  scope:`<pre>// at startup, harness ↔ server:
{ "method": "${'initialize'}" }      → capabilities
{ "method": "tools/list" }       → [ get_weather, ... ]
// schemas get pasted into the system prompt ↑</pre>`
});

// 1 — open turn + forward pass
step({
  active:'FWD', lane:'m', phase:'1',
  title:'Harness opens the assistant turn, model runs a forward pass',
  body:'Your code appends the assistant role delimiter to the buffer, then runs one forward pass over the whole context. Out comes a logit (a score) for every token in the vocabulary — a probability distribution over “what comes next”.',
  note:'The forward pass touches the entire context, but the <b>KV cache</b> means tokens already seen aren’t recomputed — only the new position is.',
  scope:`<div style="font-family:var(--jc-font-mono);font-size:10px;color:var(--jc-code-dim);margin-bottom:6px">logits → softmax, next-token candidates:</div>`+
        L.bars([{t:'⟨tool_call⟩',p:71,pick:true},{t:'"It',p:12},{t:'I',p:8},{t:'Sure',p:5},{t:'…',p:4}])
}, ()=>{ asst1 = seg('assistant','asst','being generated, token by token'); });

// 2 — sample first token (the tool-call opener)
step({
  active:'SAMPLE', lane:'h', phase:'2',
  title:'Sample one token, append it',
  body:'Sampling (temperature, top-p, top-k) collapses that distribution to exactly one token. Here the model decided to call a tool, so the first emitted token is the special tool-call marker. The harness appends it to the buffer.',
  note:'Special tokens like <b>⟨tool_call⟩</b> aren’t magic — they’re ordinary vocabulary entries the model was trained to emit in the right spot.',
  scope:`<div style="font-family:var(--jc-font-mono);font-size:11px">picked → <span class="tok sp" style="display:inline-block">⟨tool_call⟩</span><br><span style="color:var(--jc-code-dim);font-size:10px">appended at position N. one loop iteration done.</span></div>`
}, ()=>{ add(asst1, sp('⟨tool_call⟩')); });

// 3 — stop check, continue
step({
  active:'STOP', lane:'h', phase:'3',
  title:'Stop-check: not finished → loop back',
  body:'After every token the harness asks: was that EOS? a stop sequence? a complete tool call? None apply yet, so control loops straight back to FORWARD PASS for the next token.',
  note:'This <b>read → emit → check → repeat</b> cycle is the entire generator. Streaming text in a chat UI is literally this loop, one token at a time.',
  scope:`<pre>while True:
    logits = model(context)     <span style="color:var(--jc-code-dim)"># forward</span>
    tok    = sample(logits)     <span style="color:var(--jc-code-dim)"># 1 token</span>
    context.append(tok)
    if is_stop(tok, context):   <span style="color:var(--jc-warn)"># ← here</span>
        break</pre>`
});

// 4 — stream the call JSON (partial)
step({
  active:'FWD', lane:'m', phase:'4',
  title:'The loop spins, emitting the call as JSON tokens',
  body:'Each turn of the loop adds one more token. Several iterations later the model has written the tool name and started the arguments. Every one of these chunks is a full forward-pass + sample + append + stop-check.',
  note:'The model is <b>writing</b> a tool call as text. It still hasn’t called anything — it can’t. It has no hands.',
  scope:`<div style="font-family:var(--jc-font-mono);font-size:10px;color:var(--jc-code-dim)">≈ 9 loop iterations collapsed into this step</div>`
}, ()=>{ add(asst1, tk('{"name": "get_weather", ')); });

// 5 — finish the call + closer
step({
  active:'SAMPLE', lane:'h', phase:'5',
  title:'Arguments finished, closing marker emitted',
  body:'The model fills in the arguments object and then samples the closing tool-call marker. From the model’s side this is just more tokens; it has no idea a round-trip is about to happen.',
  note:'Arguments are generated text → they can be malformed. Robust harnesses validate against the tool’s JSON schema before trusting them.',
  scope:`<pre>${J('{"name": "get_weather",\n "arguments": {"city": "Polson",\n               "state": "MT"}}')}</pre>`
}, ()=>{ add(asst1, tk('"arguments": {"city": "Polson", "state": "MT"}}')); add(asst1, sp('⟨/tool_call⟩')); });

// 6 — stop check -> branch / HALT
step({
  active:'STOP', lane:'h', phase:'6',
  title:'Stop-check fires: complete tool call → HALT',
  body:'Now the harness’s parser recognizes a closed, well-formed tool-call block. This is a stop condition. Generation halts and control diverts off the decode loop into the tool path.',
  note:'Pivotal moment: the model produced <b>only tokens</b>. Detecting “this is a tool call” and stopping is the <b>harness’s</b> job, not the model’s.',
  scope:`<pre>is_stop():
    if eos: return True
    if buffer.ends_with("⟨/tool_call⟩"):
        halt_generation()       <span style="color:var(--jc-warn)"># branch out</span>
        return True</pre>`
});

// 7 — parse
step({
  active:'PARSE', lane:'h', phase:'7',
  title:'Harness parses the emitted tokens into a structured call',
  body:'The raw token span between the markers is decoded back into a real data structure: a function name and an arguments object your code can actually use.',
  note:'Text in the buffer → a typed call in your program. The boundary between “language” and “software” is right here.',
  scope:`<pre>call = ${J('{"name": "get_weather",\n  "args": {"city": "Polson", "state": "MT"}}')}</pre>`
});

// 8 — MCP request
step({
  active:'MCP_REQ', lane:'h', phase:'8',
  title:'MCP client sends tools/call over the transport',
  body:'The MCP client (living inside your app) wraps the parsed call as a JSON-RPC 2.0 request and ships it to the MCP server over the negotiated transport — stdio for a local server, or HTTP + SSE / streamable HTTP for a remote one.',
  note:'MCP is just <b>JSON-RPC over a pipe</b>. Nothing model-specific about it — any program could speak it.',
  scope:`<pre>${J('{"jsonrpc": "2.0",\n "id": 7,\n "method": "tools/call",\n "params": {\n   "name": "get_weather",\n   "arguments": {"city": "Polson",\n                 "state": "MT"}}}')}</pre>`
});

// 9 — server executes
step({
  active:'MCP_SRV', lane:'s', phase:'9',
  title:'The MCP server actually does the work',
  body:'Now real execution happens — outside the model entirely. The server hits a weather API (or a DB, a filesystem, your BisTrack SQL view, whatever it wraps), then returns a JSON-RPC result.',
  note:'This is the only step where anything “happens” in the world. The model is idle, waiting.',
  scope:`<pre>${J('{"jsonrpc": "2.0",\n "id": 7,\n "result": {\n   "content": [{"type": "text",\n     "text": "61°F, clear, wind 6mph"}]}}')}</pre>
  <div style="font-family:var(--jc-font-mono);font-size:10px;color:var(--jc-code-dim);margin-top:6px">illustrative values</div>`
});

// 10 — inject result
step({
  active:'INJECT', lane:'h', phase:'10',
  title:'Result is spliced back into the context as tokens',
  body:'The harness formats the server’s result as a tool-role message and appends it to the same token buffer. It then runs a prefill pass over those new tokens to extend the KV cache.',
  note:'Crucial idea: the result re-enters as <b>ordinary context tokens</b>. To the next forward pass it’s indistinguishable from any other text in the prompt.',
  scope:`<pre>buffer += render_tool_msg(result)
prefill(buffer[-k:])   <span style="color:var(--jc-code-dim)"># extend KV cache</span>
resume_decode()</pre>`
}, ()=>{
  toolSeg = seg('tool','tool','injected by harness');
  add(toolSeg, sp('⟨tool_result⟩'));
  add(toolSeg, tk('61°F, clear, wind 6mph'));
  add(toolSeg, sp('⟨/tool_result⟩'));
});

// 11 — resume forward pass
step({
  active:'FWD', lane:'m', phase:'11',
  title:'Decode loop resumes — model now “sees” the result',
  body:'Control returns to FORWARD PASS. The model reads the extended context, tool result included, and begins generating its final answer — back to one token at a time.',
  note:'The model never “received” the weather. It just read tokens that happened to contain it. Same mechanism as reading the user’s question.',
  scope:L.bars([{t:'"It',p:64,pick:true},{t:'Right',p:14},{t:'Currently',p:10},{t:'The',p:7},{t:'…',p:5}])
}, ()=>{ asst2 = seg('assistant','asst','final answer'); });

// 12 — stream answer
step({
  active:'SAMPLE', lane:'h', phase:'12',
  title:'Loop emits the final reply token by token',
  body:'Same cycle as before, now producing natural language grounded in the tool result. This is what streams into the chat window word by word.',
  note:'Tool call and final answer come from the <b>identical loop</b>. The only difference was one stop-condition branch in the middle.',
  scope:`<div style="font-family:var(--jc-font-mono);font-size:10px;color:var(--jc-code-dim)">each word ≈ one or more loop iterations</div>`
}, ()=>{ add(asst2, tk("It's 61°F and clear in Polson right now — light wind out of the west.")); });

// 13 — EOS
step({
  active:'STOP', lane:'h', phase:'13',
  title:'Model emits EOS → loop exits → turn returned',
  body:'The model samples the end-of-turn token. Stop-check sees it, breaks the loop, and the harness hands the completed assistant message back to the UI. Turn over.',
  note:'One user turn = one trip around this loop, with an optional detour through MCP whenever the model decides to write a tool call.',
  scope:`<pre>tok = sample(...)   <span style="color:var(--jc-warn)"># ⟨end⟩</span>
if is_stop(tok): break
return assistant_message  →  UI</pre>`
}, ()=>{ add(asst2, sp('⟨end⟩')); });

// ---- render ----
let i = 0, timer=null;
const $ = id => document.getElementById(id);
const NODES = ['FWD','SAMPLE','APPEND','STOP','PARSE','MCP_REQ','MCP_SRV','INJECT'];

function render(){
  const s = STATES[i];
  // buffer
  const bufEl = $('buffer'); bufEl.innerHTML='';
  s.buf.forEach(seg=>{
    const d=document.createElement('div'); d.className='seg '+seg.cls;
    const meta = seg.meta? `<em>${seg.meta}</em>`:'';
    let toks = seg.toks.map(t=>`<span class="tok ${t.sp?'sp':''} ${t.gen===i?'fresh':''}">${esc(t.t)}</span>`).join('');
    const live = (seg.cls==='asst' && seg===s.buf[s.buf.length-1] && [1,2,4,5,11,12].includes(i)) ? '<span class="caret"></span>' : '';
    d.innerHTML = `<div class="role"><span>${seg.role}</span>${meta}</div><div class="toks">${toks}${live}</div>`;
    bufEl.appendChild(d);
  });
  bufEl.scrollTop = bufEl.scrollHeight;

  // nodes
  NODES.forEach(n=>{
    const el=$('n-'+n); el.classList.remove('on');
  });
  if(s.active){ $('n-'+s.active).classList.add('on'); }

  // status
  const ph=$('phase'); ph.textContent=s.phase; ph.className='phase '+(s.lane||'h');
  $('stitle').textContent=s.title;
  $('sbody').textContent=s.body;
  $('note').innerHTML=s.note;
  $('scope').innerHTML=s.scope||'';

  // controls
  $('prev').disabled = i===0;
  $('next').disabled = i===STATES.length-1;
  $('counter').textContent = `step ${i} / ${STATES.length-1}`;
  $('track').style.width = (i/(STATES.length-1)*100)+'%';
}
function esc(t){return t.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}

function go(n){ i=Math.max(0,Math.min(STATES.length-1,n)); render(); }
$('next').onclick=()=>{ go(i+1); if(i===STATES.length-1) stop(); };
$('prev').onclick=()=>go(i-1);
$('reset').onclick=()=>{ stop(); go(0); };
function stop(){ if(timer){clearInterval(timer);timer=null;$('play').innerHTML='&#9654; Auto';} }
$('play').onclick=()=>{
  if(timer){ stop(); return; }
  $('play').innerHTML='&#10073;&#10073; Pause';
  timer=setInterval(()=>{ if(i>=STATES.length-1){stop();return;} go(i+1); }, 2100);
};
document.addEventListener('keydown',e=>{
  if(e.key==='ArrowRight'){go(i+1);} if(e.key==='ArrowLeft'){go(i-1);}
});
render();
</script>

<h2 id="a-tool-call-is-text-the-model-wrote">A tool call is text the model wrote<a class="headerlink" href="#a-tool-call-is-text-the-model-wrote" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>When a model "calls a tool," it emits tokens that spell out a call and then stops: a marker, a name, some arguments, a closing marker. Those markers are ordinary vocabulary entries the model was trained to place in the right spot, no different in kind from the token for "the".</p>
<p>The model has no hands. It cannot send a request or run a function; it can only write the request as text and halt. Recognizing that it halted on a complete call is a string match in the harness, the same machinery that ends generation on a double newline or an end-of-sequence token.</p>
<div class="language-text highlight"><pre><span></span><code>USER&gt; What's the weather in Polson, MT?  
MODEL&gt; [call get_weather city=Polson state=MT]
</code></pre></div>
<p>That is one real prompt-and-call from the bundle, in compact format. Everything left of <code>[call</code> is the prompt; everything from <code>[call</code> to the closing bracket is tokens the model produced, one per loop iteration, with the loop checking after each whether the call is closed yet.</p>
<h2 id="the-handoff-is-json-rpc">The handoff is JSON-RPC<a class="headerlink" href="#the-handoff-is-json-rpc" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>Once the harness sees a complete call, it parses the emitted tokens into a structured request and sends it to an MCP server as JSON-RPC 2.0 over a transport: stdio for a local server, HTTP plus SSE for a remote one. Two methods carry the runtime, <code>tools/list</code> at startup and <code>tools/call</code> per use.</p>
<div class="language-json highlight"><pre><span></span><code><span class="p">{</span>
<span class="w">  </span><span class="nt">"jsonrpc"</span><span class="p">:</span><span class="w"> </span><span class="s2">"2.0"</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"id"</span><span class="p">:</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"method"</span><span class="p">:</span><span class="w"> </span><span class="s2">"tools/call"</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"params"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"get_weather"</span><span class="p">,</span>
<span class="w">    </span><span class="nt">"arguments"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span><span class="nt">"city"</span><span class="p">:</span><span class="w"> </span><span class="s2">"Polson"</span><span class="p">,</span><span class="w"> </span><span class="nt">"state"</span><span class="p">:</span><span class="w"> </span><span class="s2">"MT"</span><span class="p">}</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>The arguments are text a probabilistic model wrote, so the <code>json.loads</code> that parses them throws often enough that the error path is not optional. A working harness hands the parse error back to the model as a tool result and lets it retry, which is the only reason "the model corrected its own malformed call" ever shows up in a log instead of a stack trace.</p>
<h2 id="tool-definitions-are-paragraphs-until-used">Tool definitions are paragraphs until used<a class="headerlink" href="#tool-definitions-are-paragraphs-until-used" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p><code>tools/list</code> returns tool schemas, and the harness serializes them into the system prompt as plain text. Until the model emits a call, a tool is a paragraph describing a tool. Nothing connects that paragraph to the real function except the harness noticing a name it recognizes and routing it, which means a tool the harness forgets to register is, from the model's side, still perfectly callable and quietly inert.</p>
<h2 id="the-result-comes-back-as-tokens">The result comes back as tokens<a class="headerlink" href="#the-result-comes-back-as-tokens" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>The server returns a result; the harness formats it as a tool-role message, appends it to the same buffer, prefills the KV cache over the new positions, and resumes the loop.</p>
<div class="language-python highlight"><pre><span></span><code><span class="n">name</span><span class="p">,</span> <span class="n">args</span> <span class="o">=</span> <span class="n">parse_tool_call</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>     <span class="c1"># tokens -&gt; dict; can throw, by design</span>
<span class="n">result</span>     <span class="o">=</span> <span class="n">mcp_client</span><span class="o">.</span><span class="n">call</span><span class="p">(</span><span class="n">name</span><span class="p">,</span> <span class="n">args</span><span class="p">)</span>  <span class="c1"># JSON-RPC, out of process</span>
<span class="n">context</span>   <span class="o">+=</span> <span class="n">render_tool_message</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>  <span class="c1"># result re-enters as plain tokens</span>
<span class="n">prefill_kv_cache</span><span class="p">(</span><span class="n">context</span><span class="p">)</span>                 <span class="c1"># extend cache over the inserted span</span>
<span class="c1"># loop resumes; the model now reads the result as ordinary context</span>
</code></pre></div>
<p>To the next forward pass, the tool result is indistinguishable from anything else in the context. There is no token-level column marked "data" and another marked "instructions"; there is one sequence. That is the whole mechanism behind prompt injection through tool output, and the reason that telling the model to ignore malicious tool results keeps almost working and never quite does.</p>
<h2 id="reference">Reference<a class="headerlink" href="#reference" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>Who does what, per step:</p>
<table>
<thead>
<tr>
<th>Step</th>
<th>Actor</th>
<th>Output</th>
</tr>
</thead>
<tbody>
<tr>
<td>forward pass</td>
<td>model</td>
<td>logits over the vocabulary</td>
</tr>
<tr>
<td>sample</td>
<td>harness</td>
<td>one token</td>
</tr>
<tr>
<td>append</td>
<td>harness</td>
<td>token added to context</td>
</tr>
<tr>
<td>stop-check</td>
<td>harness</td>
<td>none / eos / stop sequence / complete tool call</td>
</tr>
<tr>
<td>parse + validate</td>
<td>harness</td>
<td>{name, arguments} or an error</td>
</tr>
<tr>
<td>tools/call</td>
<td>harness (MCP client)</td>
<td>JSON-RPC request on the wire</td>
</tr>
<tr>
<td>execute</td>
<td>MCP server</td>
<td>the real side effect</td>
</tr>
<tr>
<td>inject + prefill</td>
<td>harness</td>
<td>result as tokens, cache extended</td>
</tr>
</tbody>
</table>
<p>MCP methods worth keeping straight:</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>When</th>
<th>Returns</th>
</tr>
</thead>
<tbody>
<tr>
<td>initialize</td>
<td>once, at connect</td>
<td>capabilities, protocol version</td>
</tr>
<tr>
<td>tools/list</td>
<td>at startup</td>
<td>tool schemas (these get pasted into the prompt)</td>
</tr>
<tr>
<td>tools/call</td>
<td>per tool use</td>
<td>content blocks (the result)</td>
</tr>
</tbody>
</table>
<p>Stop conditions the harness checks after every token:</p>
<ul>
<li>end-of-sequence token, the model signaling it is done</li>
<li>a configured stop sequence, a plain string match such as a double newline</li>
<li>a complete tool call, the closing marker, also a string match</li>
<li>a hard cap on token count, the one that saves the bill when the other three fail</li>
</ul>
<h2 id="lab">Lab<a class="headerlink" href="#lab" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>The bundle runs on python3; numpy is needed only for the two transformer scripts. The first three steps cover the loop and the MCP handoff and need nothing but the standard library.</p>
<p><strong>Step 1, the bare loop.</strong> <code>python3 decode_loop.py</code> streams one assistant turn, prints the four-step trace per token, halts on the tool call, runs a stubbed MCP round trip, injects the result, and resumes. The model is a hand-written table, which keeps the loop in view instead of under a framework.</p>
<p><strong>Step 2, inspect the handoff.</strong> <code>python3 decode_loop.py --step</code> pauses at each iteration, which makes the stop-check and the parse, call, and inject sequence readable one frame at a time.</p>
<p><strong>Step 3, break the call on purpose.</strong> <code>python3 decode_loop_ngram.py --order 4</code> swaps in a trained character model with a deliberately short memory; at order 4 it tends to emit a malformed or off-target call, exercising the harness error path that real systems hit when a production model writes invalid arguments.</p>
<p><strong>Step 4, note what did not change.</strong> The same harness drives every model in the bundle; only <code>forward()</code> differs. The loop, the stop logic, the MCP round trip, and the injection are constant across all of them.</p>
<p>Out of scope for these notes: <code>decode_loop_transformer.py</code> and <code>attention_peek.py</code> carry a separate thread, namely why model quality changes what the loop can produce. That one includes a from-scratch numpy transformer that copies a value out of a tool result into its answer, and a heatmap of the single attention head doing the copying. Different page.</p>
<h2 id="the-short-version">The short version<a class="headerlink" href="#the-short-version" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>The loop is about fifteen lines. The model is simple in one specific way, it emits tokens, and that simplicity is load-bearing: it pushes caching, stopping, validation, tool routing, result injection, and the entire trust boundary into the harness, where they can be read and changed. Most "the model did X" sentences are really "the harness did X in response to tokens the model wrote," and keeping that straight is the difference between debugging the loop and arguing with the weather.</p>]]></content:encoded>
  </item>
  <item>
    <title>Claude Code lives in two folders</title>
    <link>https://jchimp.tech/blog/claude-code-lives-in-two-folders/</link>
    <guid>https://jchimp.tech/blog/claude-code-lives-in-two-folders/</guid>
    <pubDate>Tue, 23 Jun 2026 00:00:00 +0000</pubDate>
    <description>Claude Code's entire configuration layer is two folders of plain text files. Here's what goes in each one and how to actually use them.</description>
    <content:encoded><![CDATA[<p>Last Tuesday I had four terminals open. Three VS Code windows. A different Claude session in each — one pulling database schemas, one reading legacy repos, one turning those notes into instructions for an agent I'm building, and one I genuinely cannot account for.</p>
<p>I was alt-tabbing between them like a raccoon checking which trash can had the good stuff. Copy-pasting prompts out of a file called <code>prompts_GOOD.md</code>. Explaining to each session, again, that I'm on Windows, and no, please don't hand me PowerShell paths inside WSL commands.</p>
<p>It worked. It was also dumb.</p>
<p>The fix isn't more terminals. It's realizing Claude Code is configured by <strong>files on disk</strong> — and once that clicks, you stop doing this by hand every morning. </p>
<h2 id="two-folders-hold-all-of-it">Two folders hold all of it<a class="headerlink" href="#two-folders-hold-all-of-it" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<ul>
<li><code>~/.claude/</code> — your stuff. Follows you into every project.</li>
<li><code>&lt;repo&gt;/.claude/</code> — this project's stuff. Lives in git, so your team gets it too.</li>
</ul>
<p>Both folders take the same files. When they disagree, the project wins. That's the whole override model, and it's why a team can share a setup without stomping on how you personally like to work (fig. 1).</p>
<p><img loading="lazy" alt="fig-01-two-folders" src="/blog/claude-code-lives-in-two-folders/fig-01-two-folders.png"></p>
<p>Here's what goes inside either one:</p>
<table>
<thead>
<tr>
<th>file</th>
<th>what it does</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>CLAUDE.md</code></td>
<td>Instructions Claude reads at session start. A system prompt you don't retype.</td>
</tr>
<tr>
<td><code>settings.json</code></td>
<td>Permissions, default model, hooks, env vars.</td>
</tr>
<tr>
<td><code>commands/&lt;name&gt;.md</code></td>
<td>A slash command. <code>/name</code> fires your saved prompt.</td>
</tr>
<tr>
<td><code>agents/&lt;name&gt;.md</code></td>
<td>A subagent — its own job, its own context window.</td>
</tr>
<tr>
<td><code>skills/&lt;name&gt;/SKILL.md</code></td>
<td>A skill. A saved procedure with steps.</td>
</tr>
<tr>
<td><code>.mcp.json</code></td>
<td>MCP server definitions. Usually project-only.</td>
</tr>
</tbody>
</table>
<p>There are <code>.local</code> versions too — <code>CLAUDE.local.md</code> and <code>settings.local.json</code>. Those get gitignored automatically, so that's where per-machine weirdness goes. The stuff that shouldn't follow the repo to anyone else's laptop.</p>
<h2 id="claudemd-suggests-settingsjson-enforces">CLAUDE.md suggests, settings.json enforces<a class="headerlink" href="#claudemd-suggests-settingsjson-enforces" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>This is the one distinction that matters most, and it's the one people miss.</p>
<p><code>CLAUDE.md</code> is context. The model reads it and tries to honor it. <code>settings.json</code> gates whether a tool call actually runs. Write "don't delete files without asking" in <code>CLAUDE.md</code> and you've got a strong nudge. Write it as a <code>deny</code> rule in <code>settings.json</code> and it's a wall, no matter what the model decides it wants to do.</p>
<p>Preferences go in <code>CLAUDE.md</code>. Hard limits go in <code>settings.json</code>.</p>
<p>Here's an example <code>~/.claude/CLAUDE.md</code>:</p>
<div class="language-markdown highlight"><pre><span></span><code><span class="gh"># Environment</span>
<span class="k">-</span><span class="w"> </span>WSL2 Ubuntu on Windows. VS Code via Remote-WSL.
<span class="k">-</span><span class="w"> </span>Plans before edits. Small diffs.

<span class="gh"># Defaults</span>
<span class="k">-</span><span class="w"> </span>Read-only against any database unless explicitly told otherwise.
<span class="k">-</span><span class="w"> </span>Treat .env, *.pem, anything in secrets/ as off-limits to read.
<span class="k">-</span><span class="w"> </span>Match existing code style in a repo over personal defaults.
</code></pre></div>
<p>And the <code>settings.json</code> that backs it up:</p>
<div class="language-json highlight"><pre><span></span><code><span class="p">{</span>
<span class="w">  </span><span class="nt">"model"</span><span class="p">:</span><span class="w"> </span><span class="s2">"opusplan"</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"includeCoAuthoredBy"</span><span class="p">:</span><span class="w"> </span><span class="kc">false</span><span class="p">,</span>
<span class="w">  </span><span class="nt">"permissions"</span><span class="p">:</span><span class="w"> </span><span class="p">{</span>
<span class="w">    </span><span class="nt">"allow"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w">      </span><span class="s2">"Read"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Grep"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Glob"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"Bash(git status:*)"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Bash(git diff:*)"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Bash(git log:*)"</span>
<span class="w">    </span><span class="p">],</span>
<span class="w">    </span><span class="nt">"ask"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Write"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Edit"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Bash(git push:*)"</span><span class="p">],</span>
<span class="w">    </span><span class="nt">"deny"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span>
<span class="w">      </span><span class="s2">"Bash(rm -rf:*)"</span><span class="p">,</span>
<span class="w">      </span><span class="s2">"Read(.env)"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Read(**/*.pem)"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Read(**/secrets/**)"</span>
<span class="w">    </span><span class="p">]</span>
<span class="w">  </span><span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>In the example above: reads are free, writes ask, destructive and secret-adjacent things are simply off the table. Start there and loosen it when something annoys you.</p>
<h2 id="three-things-you-can-save-a-prompt-a-procedure-a-worker">Three things you can save: a prompt, a procedure, a worker<a class="headerlink" href="#three-things-you-can-save-a-prompt-a-procedure-a-worker" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>Commands, skills, and subagents get conflated constantly. They're three different tools (fig. 2).</p>
<p><img loading="lazy" alt="fig-02-three-primitives" src="/blog/claude-code-lives-in-two-folders/fig-02-three-primitives.png"></p>
<p>A <strong>slash command</strong> is a saved prompt. <code>.claude/commands/&lt;name&gt;.md</code>. Reach for it when the <em>wording</em> is the only thing you're reusing. It takes arguments — <code>$1</code>, <code>$ARGUMENTS</code> — same as any script.</p>
<p>A <strong>skill</strong> is a saved procedure. <code>.claude/skills/&lt;name&gt;/SKILL.md</code>. Reach for it when the work has real steps, expects a specific output shape, or wants helper files sitting next to it — templates, scripts, a checklist.</p>
<p>A <strong>subagent</strong> is a saved worker with its own brain. <code>.claude/agents/&lt;name&gt;.md</code>. It runs in a separate context window with its own tool permissions. When it reads twenty files hunting for a pattern, only its summary comes back to you. The twenty file reads stay in its context and vanish when it exits.</p>
<p>That last one is the real trick for long sessions, and it's also the expensive one — every subagent carries its own system prompt and context, so fan-out workflows can burn several times the tokens of a single thread. Worth it for parallel work and noisy investigation. Not worth it for a one-line edit.</p>
<p>A subagent is just markdown with frontmatter:</p>
<div class="language-markdown highlight"><pre><span></span><code>---
name: schema-investigator
description: Read-only DB and schema analysis. Use proactively for schema pulls
  and "what does this table do" questions.
tools: Read, Grep, Glob, Bash
<span class="gu">model: sonnet</span>
<span class="gu">---</span>
Investigate schemas and report findings. Never modify data or run DDL/DML.

When invoked:
<span class="k">1.</span> Locate connection config without echoing secrets.
<span class="k">2.</span> Enumerate requested objects.
<span class="k">3.</span> Return a tight summary, not raw dumps.
</code></pre></div>
<p><code>tools</code> is the safety line — an agent without <code>Write</code> can't write, even if you ask nicely. <code>model</code> pins it to a model regardless of what your session is running. And <code>description</code> is what the main session reads to decide whether to hand off work, so write it like a job posting, not a label.</p>
<h2 id="homework-point-it-at-your-scripts-folder">Homework: point it at your scripts folder<a class="headerlink" href="#homework-point-it-at-your-scripts-folder" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>You know the folder. <code>~/scripts/</code>, or <code>~/sql/</code>, or wherever the bodies are buried. Forty files named <code>final.py</code>, <code>final_v2.py</code>, <code>final_REAL.py</code>. Three do nearly the same thing. Two haven't run since 2022 and you're not sure whether they're broken or whether the server they talked to just doesn't exist anymore.</p>
<p>Make <code>.claude/commands/inventory-scripts.md</code>:</p>
<div class="language-markdown highlight"><pre><span></span><code>---
description: Inventory a scripts directory and propose a sane organization
argument-hint: [path]
allowed-tools: Read, Glob, Grep, Bash
<span class="gu">model: sonnet</span>
<span class="gu">---</span>
Look at directory <span class="sb">`$1`</span>. Do not move or delete anything.

<span class="k">1.</span> Find all .py, .sh, .sql, .ps1 files. Group by extension. Note count and size.
<span class="k">2.</span> Read the first 30 lines of each (skip files &gt;1MB). Infer purpose in one sentence.
<span class="k">3.</span> Flag suspected duplicates: similar names, similar imports, similar first lines.
<span class="k">4.</span> Flag risk: hardcoded paths, embedded secrets, references to hosts or databases
   that may no longer exist.
<span class="k">5.</span> Propose a structure by purpose (backups/, db-pulls/, monitoring/, one-offs/,
   archive/), with renames for the <span class="sb">`final_REAL_v3`</span> situation.
<span class="k">6.</span> Write the plan to PLAN.md. Do not execute.
</code></pre></div>
<p>Then run it:</p>
<div class="language-text highlight"><pre><span></span><code>&gt; /inventory-scripts ~/scripts
</code></pre></div>
<p>After runs, read <code>PLAN.md</code>. If it isn't unhinged, follow up in plain English: <em>"Execute the plan. Move, don't delete. Write INDEX.md describing every script. Log moves to MOVES.log so I can undo it."</em></p>
<p>Now you have an indexed scripts library instead of a folder you're mildly afraid of. The first time it works you'll feel a little strange about it. That's correct — you just handed off an annoying chore to a config file you wrote once. The same command works on <code>~/sql/</code>, on old dotfiles, on the <code>/tmp/stuff/</code> directory that's been sitting there for two years.</p>
<h2 id="pick-the-model-per-job-not-per-session">Pick the model per job, not per session<a class="headerlink" href="#pick-the-model-per-job-not-per-session" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>There's no autorouter for model selection. You get to chose, at whatever time you want: <code>/model</code> live in a session, <code>--model</code> on the command line at launch, or the <code>model</code> key in <code>settings.json</code> as the default, or <code>model:</code> in the frontmatter of a skill or subagent.</p>
<p>Model selection:</p>
<table>
<thead>
<tr>
<th>Model</th>
<th>reach for it when</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>sonnet</code></td>
<td>default. Fits about 90% of the work.</td>
</tr>
<tr>
<td><code>opus</code></td>
<td>architecture, hard debugging, synthesizing across many inputs.</td>
</tr>
<tr>
<td><code>haiku</code></td>
<td>mechanical bulk — renames, formatting, simple transforms.</td>
</tr>
<tr>
<td><code>opusplan</code></td>
<td>your session default when there's real planning. Opus plans, Sonnet executes.</td>
</tr>
</tbody>
</table>
<p>When it comes to models, <code>opusplan</code> is the sleeper. Most coding work doesn't need Opus writing the edits; it needs Opus deciding <em>which</em> edits. Substantially cheaper, no drop in quality where it counts.</p>
<h2 id="it-also-runs-outside-the-editor">It also runs outside the editor<a class="headerlink" href="#it-also-runs-outside-the-editor" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p><code>claude -p "&lt;prompt&gt;"</code> runs headless. No session, no editor — reads stdin, prints to stdout, exits. Add <code>--output-format json</code> and it's just another thing in a pipeline:</p>
<div class="language-bash highlight"><pre><span></span><code><span class="c1"># Triage SSH brute-force attempts from auth.log</span>
cat<span class="w"> </span>/var/log/auth.log<span class="w"> </span><span class="se">\</span>
<span class="w">  </span><span class="p">|</span><span class="w"> </span>claude<span class="w"> </span>-p<span class="w"> </span><span class="s2">"Group failed SSH attempts by source IP. Output JSON:</span>
<span class="s2">               [{ip, count, first_seen, ports[]}]"</span><span class="w"> </span><span class="se">\</span>
<span class="w">           </span>--output-format<span class="w"> </span>json<span class="w"> </span><span class="se">\</span>
<span class="w">  </span><span class="p">|</span><span class="w"> </span>jq<span class="w"> </span><span class="s1">'.[] | select(.count &gt; 10)'</span>
</code></pre></div>
<p>That's the jump from "AI in my editor" to "AI as a step in a script I already had." Log triage, schema docs, cert inventory — a lot of admin chores collapse into one-liners once the commands and subagents exist.</p>
<p>There's a <code>--dangerously-skip-permissions</code> flag that bypasses confirmations. It's named that way for a reason. Sandboxes only. Against anything with real data, that confirmation prompt is the only thing between a runaway tool call and your filesystem.</p>
<h2 id="when-its-set-up-complex-work-is-one-command">When it's set up, complex work is one command<a class="headerlink" href="#when-its-set-up-complex-work-is-one-command" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>Reading four legacy repos and turning them into a spec for a downstream agent used to be an afternoon and four browser tabs. Configured, it's:</p>
<ol>
<li>One session. <code>/add-dir</code> each repo into context.</li>
<li><code>/build-agent-spec</code> fans out to a <code>repo-summarizer</code> subagent per repo, in parallel. Each runs Sonnet, read-only, writes to <code>summaries/&lt;repo&gt;.md</code>.</li>
<li>The same command hands the collected summaries to an <code>instruction-writer</code> subagent on Opus, which writes the final spec.</li>
</ol>
<p>One command. The files did the coordinating. And once the interactive version works, the exact same run goes headless with <code>claude -p "/build-agent-spec ..."</code> and lives in cron.</p>
<p>Here's the whole layout:</p>
<div class="language-text highlight"><pre><span></span><code>~/.claude/                    # yours, global
  CLAUDE.md
  settings.json
  commands/&lt;name&gt;.md
  agents/&lt;name&gt;.md
  skills/&lt;name&gt;/SKILL.md

&lt;repo&gt;/.claude/               # this project's, in git
  settings.json
  settings.local.json         # gitignored
  rules/*.md                  # path-scoped instructions
  commands/, agents/, skills/
  .mcp.json
&lt;repo&gt;/CLAUDE.md              # project instructions, in git
&lt;repo&gt;/CLAUDE.local.md        # personal project notes, gitignored
</code></pre></div>
<p>One habit covers all of this: <strong>if you've typed a prompt or an explanation twice, it belongs in a file.</strong> A <code>CLAUDE.md</code> line, a slash command, a skill, or a subagent. Once the three primitives are clear, picking which one is mechanical.</p>
<p>I still have four terminals open sometimes. Just not for the same job.</p>
<h2 id="cheat-block">Cheat block<a class="headerlink" href="#cheat-block" title="Link to this section" rel="noopener noreferrer">#</a></h2>
<p>Worth scrolling back to.</p>
<p>In-session slash commands:</p>
<table>
<thead>
<tr>
<th>command</th>
<th>purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>/init</code></td>
<td>bootstrap a project <code>CLAUDE.md</code> from the repo</td>
</tr>
<tr>
<td><code>/memory</code></td>
<td>show all loaded instructions — debug missing rules here</td>
</tr>
<tr>
<td><code>/model</code></td>
<td>switch model live in the current session</td>
</tr>
<tr>
<td><code>/agents</code></td>
<td>create and manage subagents interactively</td>
</tr>
<tr>
<td><code>/add-dir</code></td>
<td>bring another directory into the session's context</td>
</tr>
<tr>
<td><code>/rewind</code></td>
<td>roll back Claude's edits to an earlier state</td>
</tr>
<tr>
<td><code>/compact</code></td>
<td>summarize current context to free token space</td>
</tr>
<tr>
<td><code>/clear</code></td>
<td>reset context entirely</td>
</tr>
<tr>
<td><code>/ide</code></td>
<td>connect an external terminal to a VS Code window</td>
</tr>
</tbody>
</table>
<p>Keyboard:</p>
<table>
<thead>
<tr>
<th>key</th>
<th>action</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Shift+Tab</code></td>
<td>cycle Default → Auto-Accept → Plan modes</td>
</tr>
<tr>
<td><code>Ctrl+Esc</code></td>
<td>open the VS Code extension panel</td>
</tr>
<tr>
<td><code>Ctrl+B</code></td>
<td>background a running task</td>
</tr>
<tr>
<td><code>Alt+K</code></td>
<td>insert <code>@file#lines</code> from the current editor selection</td>
</tr>
</tbody>
</table>
<p>CLI flags:</p>
<table>
<thead>
<tr>
<th>flag</th>
<th>purpose</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>--model &lt;alias&gt;</code></td>
<td>override model for this launch</td>
</tr>
<tr>
<td><code>-p "..."</code> / <code>--print</code></td>
<td>headless, non-interactive</td>
</tr>
<tr>
<td><code>--output-format json</code></td>
<td>parseable output</td>
</tr>
<tr>
<td><code>--resume</code></td>
<td>reopen a past conversation</td>
</tr>
<tr>
<td><code>--add-dir &lt;path&gt;</code></td>
<td>extra working directory at launch</td>
</tr>
<tr>
<td><code>--dangerously-skip-permissions</code></td>
<td>bypass confirmations; sandbox only</td>
</tr>
</tbody>
</table>]]></content:encoded>
  </item>
</channel></rss>
