<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://shashforge.dev/feed.xml" rel="self" type="application/atom+xml" /><link href="https://shashforge.dev/" rel="alternate" type="text/html" /><updated>2026-08-30T14:18:28+02:00</updated><id>https://shashforge.dev/feed.xml</id><title type="html">Shashi Shankar — AI Systems, Platform &amp;amp; Edge AI Architect</title><subtitle>Architecture notes, production experiments, benchmarks and case studies across AI platforms, agent systems, evaluation, Edge AI, Android/AOSP and AAOS. By Shashi Shankar — 15+ years in C++, embedded systems and automotive platform engineering.</subtitle><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><entry><title type="html">The forge, audited</title><link href="https://shashforge.dev/log/the-forge-audited/" rel="alternate" type="text/html" title="The forge, audited" /><published>2026-08-30T00:00:00+02:00</published><updated>2026-08-30T00:00:00+02:00</updated><id>https://shashforge.dev/log/the-forge-audited</id><content type="html" xml:base="https://shashforge.dev/log/the-forge-audited/"><![CDATA[<p>Four weeks ago <a href="/log/lighting-the-forge/">this site lit</a> on a
premise: claims are cheap, so ship the evidence, dated and
versioned, where it can embarrass me. Four weeks is long enough for
the premise to face its own method. This entry audits the forge the
way the forge audits everything else: numbers first, gaps named,
excuses attributed to their owner.</p>

<h2 id="what-exists-that-did-not">What exists that did not</h2>

<p>Fifteen entries, counting today’s two.
<a href="https://github.com/shashforge/agent-harness">agent-harness</a>: 952
lines of Python at version 0.3.4, 292 tests green in under a second,
CI across CPython 3.10 through 3.13 on every push, two architecture
decision records in <code class="language-plaintext highlighter-rouge">docs/adr/</code>, a golden trace pinning the
executor’s canonical behavior, a 250-seed invariant sweep, and a
trace inspector that reads any run back as a timeline. As of today,
<a href="https://github.com/shashforge/edge-bench">edge-bench</a>: the
<a href="/log/edge-ai-benchmark-protocol/">benchmark protocol</a> published on
August 4 turned into code that enforces it: 232 lines, 12 tests,
including one that fails if anyone ever adds a <code class="language-plaintext highlighter-rouge">mean()</code>. As of this
audit, 113 commits across the site and agent-harness, each one
small enough to read.</p>

<h2 id="what-the-method-bought">What the method bought</h2>

<p>The compounding was the point, and it showed up on schedule. The
golden trace, built on August 5 before anything needed it, is what
made <a href="/log/same-trace-different-guts/">the August 9 refactor</a> boring
instead of risky. The failure taxonomy, argued on paper in
<a href="/log/the-harness-gets-a-spine/">the spine post</a>, became the literal
dispatch of the executor. The invariant sweep found no bugs and
forced <a href="/log/adr-002-seeds-over-hypothesis/">an ADR</a> anyway. The
best entries came from the worst weeks: a three-day GitHub outage
sat on this site’s deploys and produced
<a href="/log/the-badge-is-a-claim/">the CI post’s</a> whole argument about
handing guard duty to infrastructure that had just failed me.</p>

<h2 id="what-did-not-happen-and-whose-fault-it-is">What did not happen, and whose fault it is</h2>

<p>The roadmap’s oldest open item is the live-model trace. The run kit
has needed one key and one human since August 6, which is
twenty-four days, and both belong to me. The Edge AI lane now has a
protocol and a harness and still no numbers, because the phone they
require is in my pocket and has stayed there. The pattern across
both gaps is not subtle: the tooling half of this operation has
out-shipped the half that holds the credentials. The audit records
this the way the bench records an empty cell: reason attached,
nothing silent.</p>

<p>One habit needs correcting too, and it is subtler than an errand.
The roadmap struck “LLM-backed planner” as done on August 5 on the
strength of tests that script the transport seam, while the live
proof sat unstruck one line below. Those are different claims.
Verified in the lab and proven in the field deserve different marks,
and a roadmap that blurs them is borrowing credibility from its own
future. When the live trace lands, the strike-through earns its
ink; until then, the honest reading of that line is “built, not yet
witnessed.”</p>

<h2 id="the-next-four-weeks">The next four weeks</h2>

<p>Live trace first; it unblocks the post this log has owed longest.
Then the CPU column of the benchmark matrix, on the two devices the
protocol promises to name. C++ enters where ADR-001 said it would: when a
measurement points at a hot path, and not before. The cadence stays
what it has been, entries when there is evidence, quiet when there
is not.</p>

<h2 id="ledger">Ledger</h2>

<p>Twenty-seven days. Fifteen entries. Two code repos, 1,184 lines,
304 tests, every one green this morning. Two ADRs. One outage survived.
Two items blocked, both on the same person, who keeps writing that
down instead of doing something about it.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[Four weeks ago this site lit on a premise: claims are cheap, so ship the evidence, dated and versioned, where it can embarrass me. Four weeks is long enough for the premise to face its own method. This entry audits the forge the way the forge audits everything else: numbers first, gaps named, excuses attributed to their owner.]]></summary></entry><entry><title type="html">800 kilobytes per token</title><link href="https://shashforge.dev/log/800-kilobytes-per-token/" rel="alternate" type="text/html" title="800 kilobytes per token" /><published>2026-08-26T00:00:00+02:00</published><updated>2026-08-26T00:00:00+02:00</updated><id>https://shashforge.dev/log/800-kilobytes-per-token</id><content type="html" xml:base="https://shashforge.dev/log/800-kilobytes-per-token/"><![CDATA[<p>One token of context in OPT-13B costs 800 kilobytes of KV cache: two
vectors, 5120 wide, across 40 layers, in FP16. Hold a 2,000-token
conversation and the model is dragging 1.6 gigabytes of state behind
it, per request, before a single new token is computed. The
<a href="https://arxiv.org/abs/2309.06180">PagedAttention paper</a> (Kwon et
al., SOSP 2023, the paper behind vLLM) starts from that arithmetic,
and the arithmetic is the whole argument: LLM serving looks like a
compute problem and bills like a memory problem.</p>

<h2 id="the-measured-indictment">The measured indictment</h2>

<p>Before proposing anything, the authors measure what existing serving
systems did with that memory, and the number deserves to be quoted
exactly: 61.8 to 79.6 percent of KV cache memory wasted. Only a
fifth to a third of it held actual token state. The waste has three
named sources, and anyone who has run a memory allocator will
recognize all of them: slots reserved for tokens not yet generated,
internal fragmentation from provisioning every request for its
maximum possible length, and external fragmentation from the
allocator itself. Serving systems were burning most of their
scarcest resource on bookkeeping failures that operating systems
classified, named, and solved decades ago.</p>

<h2 id="the-old-answer-imported-honestly">The old answer, imported honestly</h2>

<p>The fix is virtual memory, applied without embarrassment. Chop the
KV cache into fixed blocks of sixteen tokens, keep a block table per
sequence mapping logical to physical, allocate on demand, share
read-only blocks across sequences, copy on write when a shared block
diverges. Beam search stops copying whole caches and starts sharing
prefixes. Every sentence of that description could have come from an
OS textbook, and the paper is candid about the lineage.</p>

<p>What makes it architecture rather than borrowing is where the cost
went. Contiguity was never a law of nature; it was a contract the
attention kernel imposed on the storage layer. The authors moved
that constraint out of the contract and paid for it inside a
rewritten kernel that walks the block table. Utilization became
near-total, and the price is indirection on every attention read:
20 to 26 percent higher attention-kernel latency by their own
microbenchmark, diluted to little at system level because attention
is one operator among many.
Naming the price is what makes the trade honest, and the outcome at
system level is 2 to 4 times the throughput of FasterTransformer and
Orca at equal latency, widening with longer sequences and larger
models.</p>

<p><img src="/assets/img/paged-kv.svg" alt="Diagram comparing KV cache layouts. On the left, contiguous per-request allocation shows large hatched regions of waste: reserved slots, internal fragmentation, external fragmentation, totaling 61.8 to 79.6 percent. On the right, paged allocation maps logical blocks through a block table to scattered fixed-size physical blocks of 16 tokens, with near-zero waste and copy-on-write sharing between sequences. A footer records 800 KB per token for OPT-13B and 2 to 4 times throughput at equal latency." /></p>

<h2 id="what-survives-the-trip-to-the-edge">What survives the trip to the edge</h2>

<p>My lane is the device, so the honest question is which of these
gains travel. The uncomfortable answer: the headline one mostly does
not. vLLM’s throughput wins come from packing more concurrent
requests into a fixed pool of GPU memory, and a phone has exactly
one user. Batching gains that justify the indirection in a
datacenter have nothing to batch on-device.</p>

<p>What does travel is the diagnosis. On-device KV cache competes with
the entire OS for RAM, so the fragmentation and over-reservation the
paper measured are, proportionally, a worse crime on an 8-gigabyte
phone than on an 80-gigabyte GPU. Block-based allocation still
belongs there, for different reasons: long contexts that grow without
pre-reserving worst case, and a shared system-prompt prefix kept
once across an app’s sessions rather than per conversation. Same
mechanism, different justification. Importing the mechanism without
re-deriving the justification is how edge runtimes end up carrying
datacenter machinery they cannot afford, which is a mistake I intend
to measure rather than assert when the
<a href="/log/edge-ai-benchmark-protocol/">benchmark protocol</a> gets its
numbers.</p>

<h2 id="ledger">Ledger</h2>

<p>This entry ships no code of its own; it is the analysis lane doing
its job. Every number above is from the paper itself, checked
against it today: 800 KB per token, 61.8 to 79.6 percent measured
waste, blocks of sixteen, 20 to 26 percent kernel overhead, 2 to 4
times throughput at equal latency. The Edge AI lane, meanwhile, got
its protocol turned into runnable code, so the only thing still
standing between that lane and numbers is a phone in my pocket.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[One token of context in OPT-13B costs 800 kilobytes of KV cache: two vectors, 5120 wide, across 40 layers, in FP16. Hold a 2,000-token conversation and the model is dragging 1.6 gigabytes of state behind it, per request, before a single new token is computed. The PagedAttention paper (Kwon et al., SOSP 2023, the paper behind vLLM) starts from that arithmetic, and the arithmetic is the whole argument: LLM serving looks like a compute problem and bills like a memory problem.]]></summary></entry><entry><title type="html">The trace tells it back</title><link href="https://shashforge.dev/log/the-trace-tells-it-back/" rel="alternate" type="text/html" title="The trace tells it back" /><published>2026-08-21T00:00:00+02:00</published><updated>2026-08-21T00:00:00+02:00</updated><id>https://shashforge.dev/log/the-trace-tells-it-back</id><content type="html" xml:base="https://shashforge.dev/log/the-trace-tells-it-back/"><![CDATA[<p>A JSONL trace is honest and unreadable in roughly equal measure. The
store has been load-bearing since
<a href="/log/replay-is-the-feature/">the replay post</a>: append-only, one
record per line, the whole run reconstructible from the file. It is
also a wall of JSON, and evidence that demands squinting does not get
read. Today the harness grew a reader:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>python -m agent_harness.inspect out/run.jsonl
</code></pre></div></div>

<h2 id="what-it-prints">What it prints</h2>

<p>The run, as a timeline. This is real output from a run whose tool
flaked once on its second step:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>run.jsonl · 11 transitions · 3 checkpoints · final: done

   1  step 0  plan → act             step planned                 tool=echo
   2  step 0  act → verify           result produced
   3  step 0  verify → checkpoint    verified                   checkpoint
   4  step 1  plan → act             step planned                 tool=echo
   5  step 1  act → act              transient error, retrying  retry  error=network hiccup
   6  step 1  act → verify           result produced
   7  step 1  verify → checkpoint    verified                   checkpoint
   8  step 2  plan → act             step planned                 tool=echo
   9  step 2  act → verify           result produced
  10  step 2  verify → checkpoint    verified                   checkpoint
  11  step 3  plan → done            goal met

retries: 1 · escalations: 0 · checkpoints: 3
replay: clean · seq gapless · every checkpoint verified
</code></pre></div></div>

<p>The marks in the right column come from a small table keyed by state
pair. An <code class="language-plaintext highlighter-rouge">act → act</code> edge is a retry because the state machine says
it is, not because a string in the reason field looked retry-ish.
The taxonomy classified the failure when it happened; the inspector
only has to read the classification back.</p>

<h2 id="tool-free-by-construction">Tool-free by construction</h2>

<p>The inspector imports the store and <code class="language-plaintext highlighter-rouge">replay()</code>, and nothing else. It
has no way to execute a tool, so pointing it at a trace can never
change anything, and the verdict on the last line comes from the
same <code class="language-plaintext highlighter-rouge">replay()</code> the test suite trusts, run over the same file. A
trace that fails those invariants prints <code class="language-plaintext highlighter-rouge">CORRUPT TRACE</code>, names the
violation, and exits 2, so a script or a CI job can gate on the
story holding together.</p>

<p><img src="/assets/img/trace-inspector.svg" alt="Diagram of the trace inspector. The append-only run.jsonl store flows into python -m agent_harness.inspect, which is marked as unable to execute anything. Out comes the timeline with retries, escalations, and checkpoints marked, a summary count line, and a verdict from replay(): exit 0 for a clean trace, exit 2 with the violation named for a corrupt one." /></p>

<h2 id="what-it-refuses-to-show">What it refuses to show</h2>

<p>Control flow, not payloads. Checkpoint results stay in the file; the
timeline shows what the machine did, never what the tools returned.
Payloads are where size and privacy live, and a viewer for them is
a different tool with different problems. The story of a run — planned, acted, retried,
verified, escalated — fits in eighty-four lines of reader precisely
because it declines to be a browser.</p>

<h2 id="ledger">Ledger</h2>

<p>952 lines of harness, of which the inspector is 84. Four new tests
bring the suite to 292, still green in half a second. Version 0.3.4.
The reader was built one errand ahead of the first real trace it
exists to read; the README’s count on that errand stands at fifteen
days.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[A JSONL trace is honest and unreadable in roughly equal measure. The store has been load-bearing since the replay post: append-only, one record per line, the whole run reconstructible from the file. It is also a wall of JSON, and evidence that demands squinting does not get read. Today the harness grew a reader:]]></summary></entry><entry><title type="html">ADR-002: Seeds over Hypothesis</title><link href="https://shashforge.dev/log/adr-002-seeds-over-hypothesis/" rel="alternate" type="text/html" title="ADR-002: Seeds over Hypothesis" /><published>2026-08-17T00:00:00+02:00</published><updated>2026-08-17T00:00:00+02:00</updated><id>https://shashforge.dev/log/adr-002-seeds-over-hypothesis</id><content type="html" xml:base="https://shashforge.dev/log/adr-002-seeds-over-hypothesis/"><![CDATA[<p>Friday’s <a href="/log/nobody-chose-these-tests/">invariant sweep</a> spent one
paragraph on why it uses a seeded <code class="language-plaintext highlighter-rouge">random.Random</code> and not a
property-testing framework. A paragraph in a post is where decisions
go to be forgotten. This is the record, and as of today it lives in
the repo at <code class="language-plaintext highlighter-rouge">docs/adr/</code>, next to a backfilled ADR-001, so the code
carries its own reasons.</p>

<p><strong>Status:</strong> accepted. <strong>Date:</strong> 2026-08-17. <strong>Decider:</strong> me, having
already shipped the code on Friday and owing it a defense.</p>

<h2 id="context">Context</h2>

<p>The executor promises invariants: a terminal state always; a gapless,
strictly increasing trace; a checkpoint one for one with a recorded
verification; no tool called past its budget; every trace replays.
Those needed testing against behavior I did not hand-pick, which
means something has to generate planners, tools, and verifiers with
conduct I never chose. Two candidate generators. The repo has zero
runtime dependencies and one test dependency, and its whole purpose
is to be read as a reference in an afternoon.</p>

<h2 id="options">Options</h2>

<p><strong>A. Hypothesis.</strong> Strategies for planners, tools, and verifiers,
<code class="language-plaintext highlighter-rouge">@given</code> over the executor, shrinking on failure. Battle-tested, and
the shrinker is real engineering I would not want to write myself.</p>

<p><strong>B. Seeded stdlib random.</strong> <code class="language-plaintext highlighter-rouge">random.Random(seed)</code> builds each world,
<code class="language-plaintext highlighter-rouge">@pytest.mark.parametrize("seed", range(N))</code> runs them all, and a
failing seed number is the whole reproduction.</p>

<p><strong>C. Both.</strong> Hypothesis for depth, seeds for the fast CI lane. Two
mechanisms for one job.</p>

<table>
  <thead>
    <tr>
      <th>Dimension</th>
      <th>A: Hypothesis</th>
      <th>B: Seeds</th>
      <th>C: Both</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>New dependency</td>
      <td>yes</td>
      <td>no</td>
      <td>yes</td>
    </tr>
    <tr>
      <td>Failure minimization</td>
      <td>shrinker</td>
      <td>none</td>
      <td>shrinker</td>
    </tr>
    <tr>
      <td>Reproduction</td>
      <td>example database + seed</td>
      <td>one integer</td>
      <td>mixed</td>
    </tr>
    <tr>
      <td>CI change</td>
      <td>pin, cache the database</td>
      <td>none</td>
      <td>yes</td>
    </tr>
    <tr>
      <td>Reader follows the file cold</td>
      <td>needs framework fluency</td>
      <td>yes</td>
      <td>no</td>
    </tr>
    <tr>
      <td>250 worlds, wall clock</td>
      <td>seconds, tunable</td>
      <td>0.7 s</td>
      <td>more</td>
    </tr>
  </tbody>
</table>

<h2 id="decision">Decision</h2>

<p>Option B. The argument that settled it: <strong>the failure I most need to
defend against is a reader not trusting the test, and every line of
framework between the reader and the executor costs trust.</strong> The
sweep is 169 lines a stranger can read top to bottom and see exactly
what gets generated and exactly what gets asserted. Nothing is
imported that a Python programmer has to go learn first.</p>

<p>Shrinking is the real thing given up, and I want to be precise about
why that is bearable here rather than pretend it costs nothing. A
failing seed in this codebase yields a trace of a few dozen
transitions at most, each carrying its reason string. That is already
a counterexample small enough to read unminimized. Shrinking earns
its keep when the failing input is large and mostly irrelevant; the
executor’s inputs are neither.</p>

<p><img src="/assets/img/adr-002-seeds.svg" alt="Diagram of ADR-002. Three options are weighed: Hypothesis with its shrinker and example database, a seeded stdlib random with one-integer reproduction, and both together. The seeded option is chosen, with the deciding argument that framework layers between a reader and the executor cost trust. A revisit clause lists the two conditions that would reopen it: an unreadable failing trace, or generated worlds needing structure the hand-rolled generators cannot express cleanly." /></p>

<h2 id="consequences">Consequences</h2>

<p>Easier: widening the sweep is one number; the CI file stays at 29
lines; the test file doubles as the plainest statement of the
invariants in the repo. Harder: no automatic minimization; the
distributions are hand-written and exactly as hostile as I made them,
no more; 250 draws cover what 250 draws cover, and a strategy-aware
engine would explore differently.</p>

<p>Revisit when either of two things happens. A seed fails and its trace
is too large to read, which is the day shrinking stops being a luxury.
Or the generated worlds need structure the hand-rolled generators
make ugly: nested plans, stateful tools, dependent draws. Either one
justifies Hypothesis as an additional dev dependency, kept out of the
default CI job so the badge keeps meaning what it means today.</p>

<h2 id="why-the-record-moved-into-the-repo">Why the record moved into the repo</h2>

<p>ADR-001 lived only on this site until today. A decision record that
sits outside the codebase it governs is a blog post with a template;
the next person to open the repo has no reason to know it exists.
Both records now sit in <code class="language-plaintext highlighter-rouge">docs/adr/</code>, numbered, with an index that
states the one rule for the folder: never rewrite. A superseded record
keeps its place, changes its status, and points forward. Hindsight
gets a new number.</p>

<h2 id="ledger">Ledger</h2>

<p>Two ADRs in the repo, 147 lines between them and their index. Version
0.3.3. The roadmap’s ADR item is struck; the live-run item is not,
and I notice the README has said “it needs a key and a human” since
August 6, which is eleven days. The key is mine. So is the human.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[Friday’s invariant sweep spent one paragraph on why it uses a seeded random.Random and not a property-testing framework. A paragraph in a post is where decisions go to be forgotten. This is the record, and as of today it lives in the repo at docs/adr/, next to a backfilled ADR-001, so the code carries its own reasons.]]></summary></entry><entry><title type="html">Nobody chose these tests</title><link href="https://shashforge.dev/log/nobody-chose-these-tests/" rel="alternate" type="text/html" title="Nobody chose these tests" /><published>2026-08-14T00:00:00+02:00</published><updated>2026-08-14T00:00:00+02:00</updated><id>https://shashforge.dev/log/nobody-chose-these-tests</id><content type="html" xml:base="https://shashforge.dev/log/nobody-chose-these-tests/"><![CDATA[<p>All 35 tests in the harness were, until now, cases I picked:
behaviors I could already name, pinned one at a time. That is their
job, and also their ceiling. A suite I chose inherits my
imagination, blind spots included. Today the repo gained a file
where the cases are picked by a seeded <code class="language-plaintext highlighter-rouge">random.Random</code>, and my only
contribution is the list of things that must be true no matter what
comes out of it.</p>

<h2 id="the-world-the-seed-builds">The world the seed builds</h2>

<p>Each of 250 seeds constructs a small hostile world. One to four
tools, some of which demand a scope the run was never granted. Tool
bodies that flake with a <code class="language-plaintext highlighter-rouge">TransientToolError</code>, refuse with a
permanent one, or reject their own work outright, at fixed odds per
call. A verifier that is a weighted coin. A planner drawing random
steps toward a random goal. And a human with finite patience: zero
to three resumes, then abort.</p>

<p>The executor runs each world with a store attached, and then the
invariants get their say. The run ends in <code class="language-plaintext highlighter-rouge">DONE</code> or <code class="language-plaintext highlighter-rouge">FAILED</code>, never
anywhere else. The transition sequence is gapless and strictly
increasing. A checkpoint exists exactly when a verification was
recorded, one for one. No tool is ever called past its declared
budget. And <code class="language-plaintext highlighter-rouge">replay()</code> reads the file the run left behind and
reaches the same conclusions without executing anything. Until now
replay had only ever been shown tidy runs and hand-forged
corruptions; these 250 traces arrive full of denials, retries,
escalations, and aborts.</p>

<p><img src="/assets/img/invariant-sweep.svg" alt="Diagram of the invariant sweep. A seed builds a random world of misbehaving tools, a random-goal planner, a coin-flip verifier, and a human with finite patience. The executor runs it and the trace must satisfy four invariants: terminal state always, gapless sequence, checkpoint only with verification, budgets respected. replay() then re-derives the run from the stored trace, agreeing 250 times out of 250." /></p>

<h2 id="why-a-seed-and-not-a-framework">Why a seed and not a framework</h2>

<p>Hypothesis would do this with more sophistication, and its failure
shrinking is real value I am walking away from. What a seed gives
back is a perfect reproduction for free: if seed 217 fails, you run
seed 217, forever, on any machine. It also costs nothing: no new
dependency, no change to the CI file whose 29 lines I bragged about
four days ago. For a reference implementation whose whole argument
is a small dependency surface, that trade goes one way.</p>

<p>Randomness got a veto on everything except termination. A generated
human that resumes forever would earn a run that runs forever, which
is the design working as stated — the human is a state, and states
can loop. So every generated human has finite patience, and a
tripwire fails any seed whose planner is consulted ten thousand
times. The sweep is allowed to be hostile. It is not allowed to be
eternal.</p>

<h2 id="what-it-found">What it found</h2>

<p>No invariant violations. 250 seeds, zero counterexamples, and I
will not pretend that proves the executor correct; it proves the
executor survives 250 worlds drawn from the distributions I wrote,
which is a smaller and more honest claim.</p>

<p>What writing it actually produced was precision. Stating invariants
formally forced distinctions the prose versions blurred. One became
a new targeted test on the spot: the permission gate sits before the
call meter, so a denied call is never billed against the tool’s
budget. True in the code all along, pinned by nothing until today.
A second test sharpens an old pin: the suite already checked that a
tireless planner triggers the step-budget escalation; it now also
checks what such a run banks on the way out, exactly <code class="language-plaintext highlighter-rouge">max_steps</code>
verified checkpoints and not one more.</p>

<h2 id="ledger">Ledger</h2>

<p>868 lines of harness, unchanged. One new 169-line test file, four
test functions, one of which carries all 250 seeds. The suite pytest
collects went from 35 to 288 and still finishes in half a second.
Version 0.3.2, and the README’s rules list gained its eleventh
entry: the invariants hold under behavior nobody chose.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[All 35 tests in the harness were, until now, cases I picked: behaviors I could already name, pinned one at a time. That is their job, and also their ceiling. A suite I chose inherits my imagination, blind spots included. Today the repo gained a file where the cases are picked by a seeded random.Random, and my only contribution is the list of things that must be true no matter what comes out of it.]]></summary></entry><entry><title type="html">The badge is a claim</title><link href="https://shashforge.dev/log/the-badge-is-a-claim/" rel="alternate" type="text/html" title="The badge is a claim" /><published>2026-08-10T00:00:00+02:00</published><updated>2026-08-10T00:00:00+02:00</updated><id>https://shashforge.dev/log/the-badge-is-a-claim</id><content type="html" xml:base="https://shashforge.dev/log/the-badge-is-a-claim/"><![CDATA[<p>Every assertion this repo makes about itself has so far been checked
on one machine: mine. Thirty-five tests, a golden trace, a taxonomy
where every leaf has a raiser. All verified locally, which is a
polite way of saying: testimony. Starting today the suite runs in
public, on every push, and the README opens with the badge that
reports the result. Anyone can click it and read the run.</p>

<h2 id="twenty-nine-lines-nothing-clever">Twenty-nine lines, nothing clever</h2>

<p>The workflow is 29 lines: checkout, set up Python, <code class="language-plaintext highlighter-rouge">pip install
pytest</code>, <code class="language-plaintext highlighter-rouge">python -m pytest -q</code>. The suite finishes in a sixth of a
second, so the job’s wall clock is nearly all provisioning. A
dependency cache could shave seconds off that. It would also add
the one failure mode CI grows entirely on its own, staleness, to
save time nobody is waiting on. Everything a workflow does beyond
what the tests need is a place for the workflow itself to fail, and
I had my fill of CI failing for its own reasons last week.</p>

<p><img src="/assets/img/badge-claim.svg" alt="Diagram of the public test run. A push or pull request feeds the 29-line tests.yml workflow, which fans out to four interpreters, CPython 3.10 through 3.13, each running the full suite of 35 tests. All four feed the README badge, which reports the result. A footer notes the workflow runs with read-only repository permissions." /></p>

<h2 id="the-matrix-audits-the-metadata">The matrix audits the metadata</h2>

<p><code class="language-plaintext highlighter-rouge">pyproject.toml</code> claims <code class="language-plaintext highlighter-rouge">requires-python = "&gt;=3.10"</code>. Until today
nothing checked that claim: the suite ran on whichever single
interpreter my machine had, so support down to 3.10 was an opinion
extended on credit. The matrix now runs the full suite on CPython
3.10 through 3.13 — 140 test executions per push. If a future
commit leans on syntax the older interpreter lacks, the 3.10 job
fails and the metadata stops being aspirational.
Declared support that no runner exercises is just a comment with
better formatting.</p>

<p>One more deliberate choice: <code class="language-plaintext highlighter-rouge">permissions: contents: read</code>. The
workflow can read the repo and do nothing else. A test runner that
can push is one compromised dependency away from being an author.
The two actions it uses are pinned to commit SHAs rather than
version tags for the same reason the token is read-only: a tag can
be moved by whoever controls it, and I would rather review an
upgrade than inherit one.</p>

<h2 id="about-handing-this-job-to-actions-of-all-systems">About handing this job to Actions, of all systems</h2>

<p>Four days ago GitHub Actions was still sitting on this site’s deploy
queue, three days into an outage; the commit history of the site
repo records the fight. Giving the same system guard duty over the
tests calls for a sentence of justification, so here it is: the
alternative is my laptop, and my laptop’s word is exactly what this
post retires. When Actions fails, it fails in a log anyone can read.
When my machine lies about what passed, nothing says anything.</p>

<h2 id="what-the-badge-does-not-prove">What the badge does not prove</h2>

<p>It proves the tests that exist ran and passed, on four interpreters,
as of the last push. It says nothing about whether those tests pin
anything that matters. That burden stays where it has been all
along: on the golden trace that names any transition that moves, and
on a suite where each test holds one rule from the design. The badge
is evidence the evidence is alive. Its quality still has to be
argued, which is what the rest of this log is for.</p>

<h2 id="ledger">Ledger</h2>

<p>868 lines of harness, 29 lines of workflow, 35 tests, four
interpreters, 140 executions per push. The roadmap’s next unstruck
item still needs a key and a human — the run kit has been sitting in
<code class="language-plaintext highlighter-rouge">examples/</code> since <a href="/log/the-lens-not-the-eraser/">the lens post</a>,
and the machine half of that pair is not the one holding things up.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[Every assertion this repo makes about itself has so far been checked on one machine: mine. Thirty-five tests, a golden trace, a taxonomy where every leaf has a raiser. All verified locally, which is a polite way of saying: testimony. Starting today the suite runs in public, on every push, and the README opens with the badge that reports the result. Anyone can click it and read the run.]]></summary></entry><entry><title type="html">Same trace, different guts</title><link href="https://shashforge.dev/log/same-trace-different-guts/" rel="alternate" type="text/html" title="Same trace, different guts" /><published>2026-08-09T00:00:00+02:00</published><updated>2026-08-09T00:00:00+02:00</updated><id>https://shashforge.dev/log/same-trace-different-guts</id><content type="html" xml:base="https://shashforge.dev/log/same-trace-different-guts/"><![CDATA[<p>There was an <code class="language-plaintext highlighter-rouge">if</code> in the executor that had no business being an <code class="language-plaintext highlighter-rouge">if</code>.
Verification failure, a card-carrying member of the failure taxonomy
with its own exception class since
<a href="/log/the-harness-gets-a-spine/">the spine post</a>, was being decided
by a plain conditional. <code class="language-plaintext highlighter-rouge">BudgetExhausted</code> had it worse: a boolean
return value. The README promises that the taxonomy decides
responses. Inside the executor, a conditional and a boolean were
doing the deciding.</p>

<p><a href="/log/the-lens-not-the-eraser/">The lens post</a> ended by admitting two
taxonomy entries sat unemployed, with a note that I’d rather stay
honest than invent work for them. What follows is not invented work.
The refactor was sitting there waiting to be seen: <code class="language-plaintext highlighter-rouge">_act_and_verify</code>
is now one <code class="language-plaintext highlighter-rouge">try</code> block, and every branch of control flow is an
<code class="language-plaintext highlighter-rouge">except</code> clause on an exception from <code class="language-plaintext highlighter-rouge">errors.py</code>. The permission gate
raises. The budget raises. So, at last, does the verifier. The
dispatch <em>is</em> the taxonomy, arm for arm.</p>

<h2 id="the-part-that-makes-this-publishable">The part that makes this publishable</h2>

<p>Rewiring the guts of a state machine is routine work. Proving that
nothing observable moved is the part worth writing down, and the
proof was already sitting in the repo: the golden trace from
<a href="/log/replay-is-the-feature/">the replay post</a>. The shipped
<code class="language-plaintext highlighter-rouge">happy_path.json</code> pins every transition of the canonical run, in
order, with reasons and fields. If this refactor had shifted a single
edge of the state machine, that test would have failed and named the
transition that moved.</p>

<p>It didn’t move. Thirty-one pre-existing tests, untouched, all green,
the golden among them. Building the regression harness before I
needed it felt close to process theater on August 5. Today it turned
a risky internal rewrite into a boring one, which is the highest
compliment a refactoring can receive.</p>

<p><img src="/assets/img/taxonomy-dispatch.svg" alt="Diagram of the executor's dispatch after the refactor. One try pipeline runs the permission gate, the call budget, the tool, and the verifier. Five except arms map exceptions to responses: PermissionDenied escalates and never retries, BudgetExhausted escalates, TransientToolError retries the same step, PermanentToolError re-plans, VerificationFailure retries then escalates. A footer notes the shipped golden trace came through unchanged, ten of ten transitions identical." /></p>

<h2 id="one-rule-got-wider-on-purpose">One rule got wider, on purpose</h2>

<p>Old behavior: the taxonomy applied only when the machinery raised it.
New behavior: it applies no matter who raises it. A tool that throws
<code class="language-plaintext highlighter-rouge">VerificationFailure</code> from its own body now gets exactly the
treatment a failed verifier gets — retry within budget, then
escalate. The new test has the tool announce “i checked my own work
and it is bad,” and the executor answers with two retries and an
escalation, per the contract. Self-rejecting tools were never in the
design. The dispatch handles them anyway, because it no longer cares
where an exception was born.</p>

<p>The call-budget path also picked up its first behavioral test. That
path always worked; nothing pinned it. Now something does.</p>

<h2 id="ledger">Ledger</h2>

<p>868 lines, 35 tests, and every leaf of the taxonomy now has a
raiser whose job it is: the budget raises <code class="language-plaintext highlighter-rouge">BudgetExhausted</code>, the
verifier raises <code class="language-plaintext highlighter-rouge">VerificationFailure</code>, tools own the transient and
permanent pair, and nothing’s fate is decided by a lonely <code class="language-plaintext highlighter-rouge">if</code>. The
two-day gap before this entry was GitHub’s outage sitting on my
deploy queue; the record of that fight lives in the site repo’s
commit history, where records belong.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[There was an if in the executor that had no business being an if. Verification failure, a card-carrying member of the failure taxonomy with its own exception class since the spine post, was being decided by a plain conditional. BudgetExhausted had it worse: a boolean return value. The README promises that the taxonomy decides responses. Inside the executor, a conditional and a boolean were doing the deciding.]]></summary></entry><entry><title type="html">The lens, not the eraser</title><link href="https://shashforge.dev/log/the-lens-not-the-eraser/" rel="alternate" type="text/html" title="The lens, not the eraser" /><published>2026-08-06T00:00:00+02:00</published><updated>2026-08-06T00:00:00+02:00</updated><id>https://shashforge.dev/log/the-lens-not-the-eraser</id><content type="html" xml:base="https://shashforge.dev/log/the-lens-not-the-eraser/"><![CDATA[<p>Every checkpoint the harness verifies becomes part of the planner’s
next prompt. I set that up on purpose, and it carries a failure mode
you can see coming from a distance: the context grows until the model
is drowning in its own history. The taxonomy has had an entry waiting
for this day since <a href="/log/the-harness-gets-a-spine/">the spine post</a>.
<code class="language-plaintext highlighter-rouge">ContextOverflow</code>: token watermark crossed; compact, then re-plan. As
of yesterday the README admitted, in writing, that nothing raised it.
Today something does.</p>

<h2 id="the-design-question-that-actually-matters">The design question that actually matters</h2>

<p>Compaction sounds like a summarization problem. It isn’t, mostly. The
hard question is <em>what gets compacted</em>, and the wrong answer ruins
everything this harness stands for. If crossing the watermark rewrites
the checkpoint history, then replay lies, the crash-resume file lies,
and the append-only trace I spent a whole post defending becomes
append-mostly.</p>

<p>So the rule, now enforced in code and tests: compaction is a lens,
not an eraser. When <code class="language-plaintext highlighter-rouge">ContextBudget</code> trips, the compactor shrinks the
<em>view</em> handed to the planner. The checkpoint list keeps every entry.
The store keeps every byte. Replay still verifies the full history,
and there’s a test that runs a compacted run against <code class="language-plaintext highlighter-rouge">replay()</code> to
prove the record came through intact. The model’s context is
negotiable; the record is not.</p>

<h2 id="what-the-planner-sees-instead">What the planner sees instead</h2>

<p><code class="language-plaintext highlighter-rouge">FoldingCompactor</code> folds everything but the last few checkpoints into
one digest entry: which steps it swallowed, which tools ran, and a
bounded scrap of each result. The planner still knows the shape of the
past, just not every byte of it. The folding is deterministic because
the core must never need a model just to function. A model-written
summary can implement the same callable later, and the executor won’t
know the difference — that’s the planner trick applied twice.</p>

<p>Known limit, stated plainly: the digest keeps a scrap per folded
step, so it grows with history. Run long enough and even the
compacted view outgrows the budget. That’s what the escalation path
is for, and it’s the first thing a smarter compactor should fix.</p>

<p>The unit estimator is serialized-length-over-four, and the code
labels it an approximation, because it is one. The budget needs a
monotonic size signal, not tokenizer-exact counts. Set the watermark
with headroom, well under the model’s hard limit: compaction should
run on my schedule, not as a panic response to an API error.
Pretending the estimator is a tokenizer would be a benchmark sin of
the kind I’ve already
<a href="/log/edge-ai-benchmark-protocol/">promised not to commit</a>.</p>

<p><img src="/assets/img/context-lens.svg" alt="Diagram of context compaction as a lens. The full checkpoint record, steps 0 through 5, passes through the compactor lens; the planner is shown a digest entry plus the last checkpoint verbatim. Below, the append-only run.jsonl file keeps all six checkpoints unchanged. The record never shrinks; only the view does." /></p>

<h2 id="failure-is-still-not-improvised">Failure is still not improvised</h2>

<p>Two edges got the taxonomy treatment. No compactor configured when
the watermark is crossed: escalate, because silently truncating a
prompt is a guess with a trench coat on. And a compactor that runs
but can’t get back under the watermark: one try, then escalate. No
loop of increasingly desperate squeezing. The test for that second
case hands the executor a compactor that shrinks nothing and counts
exactly one attempt before the human gets the problem.</p>

<h2 id="the-recorder-is-armed">The recorder is armed</h2>

<p>The other thing that shipped today is <code class="language-plaintext highlighter-rouge">examples/live_run.py</code>: a real
goal, four scoped filesystem tools, the store recording, and the LLM
planner behind the wheel. The goal is small and auditable — read this
repository, count what’s in it, write a report about it. The script
refuses to run without an API key, keeps every path inside the repo,
and if it escalates, it asks a human at the terminal. I dry-ran the
whole loop against a scripted transport; five checkpoints, <code class="language-plaintext highlighter-rouge">DONE</code>,
report on disk.</p>

<p>The README has promised a live trace, published unedited, since the
planner landed. The kit now sits in the repo next to everything it
will record. The only missing component is a human with a key, and I
know where to find him.</p>

<h2 id="ledger">Ledger</h2>

<p>The package is 849 lines now, 31 tests, and the exception the README
publicly listed as unemployed finally earns its keep. Two more are
still idle (<code class="language-plaintext highlighter-rouge">VerificationFailure</code> and <code class="language-plaintext highlighter-rouge">BudgetExhausted</code>, if you’re
auditing), and I’d rather the code stay honest about that than invent
work for them.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[Every checkpoint the harness verifies becomes part of the planner’s next prompt. I set that up on purpose, and it carries a failure mode you can see coming from a distance: the context grows until the model is drowning in its own history. The taxonomy has had an entry waiting for this day since the spine post. ContextOverflow: token watermark crossed; compact, then re-plan. As of yesterday the README admitted, in writing, that nothing raised it. Today something does.]]></summary></entry><entry><title type="html">Replay is the feature</title><link href="https://shashforge.dev/log/replay-is-the-feature/" rel="alternate" type="text/html" title="Replay is the feature" /><published>2026-08-05T00:00:00+02:00</published><updated>2026-08-05T00:00:00+02:00</updated><id>https://shashforge.dev/log/replay-is-the-feature</id><content type="html" xml:base="https://shashforge.dev/log/replay-is-the-feature/"><![CDATA[<p>The skeleton I published yesterday had a memory problem. The trace was
data, the checkpoints were data, and every byte of it lived in RAM.
Kill the process and the run’s whole story died with it. For a harness
whose sales pitch is auditability, that’s embarrassing. In embedded
work you learn early that state living only in RAM is state you’ve
already agreed to lose. Today I stopped agreeing.</p>

<h2 id="persistence-the-boring-way">Persistence, the boring way</h2>

<p>Transitions and checkpoints now stream to an append-only JSONL file as
they happen, not when the run ends. One file, two record kinds, no
database. <a href="https://github.com/shashforge/agent-harness"><code class="language-plaintext highlighter-rouge">persistence.py</code></a>
is 126 lines and most of them are documentation.</p>

<p>The constraint this buys is real: checkpoint results must be
JSON-serializable, and the store throws if they aren’t. Deliberate. A
checkpoint you can’t serialize is a checkpoint you can’t resume from,
and I want that failure at the moment of writing, while it still costs
nothing. There’s a test that pins exactly this.</p>

<h2 id="the-crash-test">The crash test</h2>

<p>My favorite test of the day gives the executor a tool that works twice
and then raises <code class="language-plaintext highlighter-rouge">RuntimeError("power cut")</code>. Not a taxonomy error —
the harness only catches failures it understands, so this one kills
the run the way a real crash would. The file left behind shows two
verified checkpoints and nothing more.</p>

<p>Then a second executor opens the same file with <code class="language-plaintext highlighter-rouge">resume=True</code>. The
planner sees the two verified checkpoints, plans step two, and
finishes the four-step goal. The sequence numbers continue from where
the dead process stopped: one continuous trace, two processes, and the
seam is visible in the file if you know where to look.</p>

<p><img src="/assets/img/crash-resume-trace.svg" alt="Timeline of the crash-resume test: process A records sequence numbers 1 through 7 with verified checkpoints for steps 0 and 1, then dies on a RuntimeError with step 2 planned but never verified. Everything streams into one append-only run.jsonl file. Process B opens the same file with resume=True, continues at sequence 8, checkpoints steps 2 and 3, and reaches DONE at sequence 14." /></p>

<h2 id="replay-refuses-bad-stories">Replay refuses bad stories</h2>

<p><code class="language-plaintext highlighter-rouge">replay()</code> reads the file and reconstructs the run without executing a
single tool: final state, verified progress, transition count. It also
enforces the invariants the executor promises. Sequence numbers must
strictly increase, because append-only means append-only. Every
checkpoint must have a <code class="language-plaintext highlighter-rouge">VERIFY -&gt; CHECKPOINT</code> transition vouching for
it, because nothing unverified counts as progress.</p>

<p>Two tests tamper with the file to prove the point. One forges a
checkpoint no verifier ever saw; one re-appends an old record so the
sequence runs backwards. Replay rejects both with <code class="language-plaintext highlighter-rouge">TraceCorruption</code>.
A trace that can’t support its own story is a corrupt flight
recorder, and replay treats it like one.</p>

<h2 id="golden-traces-or-evals-for-the-harness-itself">Golden traces, or: evals for the harness itself</h2>

<p>Once wall-clock time is stripped, a deterministic run is a snapshot of
the executor’s behavior. So the repo now ships one:
<code class="language-plaintext highlighter-rouge">tests/golden/happy_path.json</code>, ten transitions, the canonical
three-step run. A test replays the executor against it on every run.</p>

<p>Change the retry policy, reorder verification, touch the checkpoint
rule, and that test fails naming the exact transition that moved and
which fields changed. Recording a new golden is a reviewable diff in
version control, not silent drift. Regression tests for control flow,
not just for outputs. This is the eval harness the README promised,
and it cost 82 lines because replay had already paid for the hard
part.</p>

<h2 id="ledger">Ledger</h2>

<p>Yesterday: 339 lines, 8 tests. Tonight: 713 lines, 25 tests, one
golden trace committed. The planner also stopped being a stub today,
but that’s its own post.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[The skeleton I published yesterday had a memory problem. The trace was data, the checkpoints were data, and every byte of it lived in RAM. Kill the process and the run’s whole story died with it. For a harness whose sales pitch is auditability, that’s embarrassing. In embedded work you learn early that state living only in RAM is state you’ve already agreed to lose. Today I stopped agreeing.]]></summary></entry><entry><title type="html">The model shows up</title><link href="https://shashforge.dev/log/the-model-shows-up/" rel="alternate" type="text/html" title="The model shows up" /><published>2026-08-05T00:00:00+02:00</published><updated>2026-08-05T00:00:00+02:00</updated><id>https://shashforge.dev/log/the-model-shows-up</id><content type="html" xml:base="https://shashforge.dev/log/the-model-shows-up/"><![CDATA[<p>Two days ago I claimed the model is a component, not the product.
Cheap claim to make in prose. Today the component actually arrived:
<a href="https://github.com/shashforge/agent-harness"><code class="language-plaintext highlighter-rouge">llm_planner.py</code></a>, 147
lines that put a model behind the planner callable.</p>

<p>Here’s the part I care about: the executor did not change to
accommodate it. Zero lines. It still calls <code class="language-plaintext highlighter-rouge">planner(checkpoints)</code> and
gets back a step or <code class="language-plaintext highlighter-rouge">None</code>, exactly as it did when the planner was a
three-line function in a test. That non-diff is the design claim,
demonstrated. If wiring in an LLM had required touching the state
machine, everything this site has argued so far would have been wrong.</p>

<h2 id="the-contract-is-rigid-on-purpose">The contract is rigid on purpose</h2>

<p>The model answers with one JSON object per turn: a tool step, or
<code class="language-plaintext highlighter-rouge">{"done": true}</code>. That’s the whole protocol. Prose, half-JSON,
reasoning out loud, a tool that isn’t in the catalog — every one of
those raises <code class="language-plaintext highlighter-rouge">PlannerProtocolError</code> and stops the run.</p>

<p>I allow exactly one tolerance: a markdown code fence around the JSON,
because models love fences and stripping one is unambiguous. Nothing
else. The failure taxonomy applies to the model the same way it
applies to tools. When a tool breaks its contract we don’t guess what
it meant, and the model gets no more charity than the tools do.</p>

<p>A detail that fell out of the state machine for free: the planner is
prompted with <em>verified</em> checkpoints only. Failed attempts, retries,
unverified junk — none of it reaches the model’s context, because
none of it ever became a checkpoint. The persistence rule from this
morning’s post turns out to be a prompt-hygiene rule too.</p>

<h2 id="tested-at-the-seam-and-honest-about-it">Tested at the seam, and honest about it</h2>

<p>The API call is one injectable function: request payload in, response
body out. The tests script that seam. They prove everything on my side
of the wire: the model is shown the goal, the tool catalog, and the
verified checkpoints; the parser refuses prose; an out-of-catalog tool
never reaches the executor; the full loop runs to <code class="language-plaintext highlighter-rouge">DONE</code> on scripted
decisions.</p>

<p><img src="/assets/img/transport-seam.svg" alt="Architecture diagram of the transport seam. Executor calls the LLMPlanner callable, which speaks to a single injectable transport function, which speaks to the Messages API. A dashed line marks the seam between transport and API. Everything left of the seam is pinned by seven tests using a scripted transport; the reply contract allows one JSON tool step or done-true, anything else raises PlannerProtocolError. Right of the seam needs an API key; the live run is pending and there is no fake fallback." /></p>

<p>What these tests prove about the model itself: nothing. The code
refuses to blur that line: there is no offline fallback faking model
responses, the live transport won’t run without an API key, and a
test pins the refusal. Testing against your own mock and calling it a
result is the vendor-benchmark move, and I published a whole
<a href="/log/edge-ai-benchmark-protocol/">protocol</a> about not doing that.</p>

<h2 id="next">Next</h2>

<p>The first live run, with the resulting JSONL trace published unedited:
which steps the model planned, where it got retried, what the
transition log looks like when the planner has opinions. The recorder
went in first so the run itself can be published, not my summary of
it.</p>]]></content><author><name>Shashi Shankar</name><email>shashi@shashforge.dev</email></author><summary type="html"><![CDATA[Two days ago I claimed the model is a component, not the product. Cheap claim to make in prose. Today the component actually arrived: llm_planner.py, 147 lines that put a model behind the planner callable.]]></summary></entry></feed>