By Stephen Meier

  • Three Ways to Improve a Small Agent, and What Each Was Actually Worth

    I wanted to know what each of the standard ways of improving an agent is actually worth. Not in the abstract, which is unanswerable, but in one setting controlled tightly enough to compare them directly: the same 8B model, the same agent loop, the same 240 held-out tasks, the same scoring throughout. Start with the base model, add a carefully written skills prompt, then finetune on demonstrations, then apply reinforcement learning, and measure after every step.

    The setting is TextWorld’s cooking games, and the base model fails in ways that are easy to picture. The recipe calls for a sliced carrot, so it chops the carrot, and the game ends. It finds a potato that has already been roasted and roasts it again, which also ends the game. It walks between two rooms for fifty turns looking for a kitchen it has already stood in. Out of 240 games it had never seen, it won 34, killed itself in 90, and exhausted its move budget without scoring a single point in another 62.

    By the end the same agent wins 171 of those 240. All three interventions helped, by very different amounts, and the pattern in which one fixed which failure is the part I think carries over to other agent work. It is not the pattern I expected going in.

    1. The task

    TextWorld [1] is a framework from Microsoft Research that procedurally generates text adventures. A game looks like this:

    -= Kitchen =-
    You arrive in a kitchen. You make out a closed fridge. You see a
    counter, and on it a cookbook and a knife.
    
    > examine cookbook
    Recipe #1
      Ingredients: red hot pepper
      Directions: chop the red hot pepper, roast the red hot pepper,
      prepare meal
    
    > take red hot pepper
    You take the red hot pepper from the counter.
    Your score has just gone up by one point.Code language: PHP (php)

    The agent has to find the cookbook, gather each ingredient from around a house, process every ingredient exactly as the recipe directs, then prepare and eat the meal. Chopping something the recipe wanted sliced ends the game. So does cooking something that is already cooked. There is no partial credit for a near miss on those.

    Three properties make this a good measurement target rather than a toy. The environment scores itself, awarding points per subgoal and declaring wins and losses, so no LLM judge and no human labelling sits in the loop. Difficulty is parameterised, so the benchmark can be a ladder instead of a flat set. And the ingredient pool splits into train, validation, and test partitions, so a finetuned model can be tested on recipes it has genuinely never seen.

    I built four tiers of 60 games each, 240 per split, with disjoint seeds so no game appears twice.

    TierDifficultyWhat it stresses
    tier11 ingredient, 1 roomBasic procedure
    tier22 ingredients, 1 room, cutting and cookingMulti-step processing
    tier33 ingredients, 6 rooms, cutting and cookingProcessing plus navigation
    tier43 ingredients, 9 rooms, plus inventory limitsNavigation under constraint

    Table 1. The four benchmark tiers. Scores throughout are the normalised game score, meaning points earned divided by points available, averaged across games. A win means the agent ate a correctly prepared meal.

    The agent itself is an ordinary ReAct loop built on deepagents [2] and LangGraph, with tools mirroring the game’s own verb list. The policy model is ibm-granite/granite-4.1-8b [3]. Everything ran on a single NVIDIA DGX Spark.

    2. How the untouched agent fails

    The baseline scores 0.309 and wins 34 of 240. Reading the transcripts, its losses sort into two piles.

    The larger pile is state-tracking failure. An ingredient’s name encodes its history: “chopped roasted red potato” is a potato that has already been chopped and roasted. The recipe directs chopping and roasting. The agent reads the name, reads the direction, and roasts it again. The game ends. This accounts for most of the 90 deaths, and it is not a knowledge problem, since the relevant fact is sitting in the context window in plain English.

    The smaller pile is search failure. On the larger maps the agent revisits rooms it has already searched, or asks for an object that is not present and receives a disambiguation prompt (“Which do you mean, the red apple or the red onion?”), then loops on it until the move budget runs out. That is most of the 62 zero-score stalls.

    Both are behavioural rather than informational. The agent is not missing facts about cooking. It is failing to act on facts it already has, which is what made the first intervention seem obvious and, in the end, mostly ineffective.

    3. A better prompt

    The obvious fix is to tell the model what it is doing wrong. I wrote a Claude-style SKILL.md playbook and served it through deepagents’ skills middleware, which advertises skill files to the model and lets it decide when to read them.

    It scored below baseline. The reason turned out to be mechanical rather than conceptual: the model called read_file on the skill in 0 of 32 episodes. The body of the playbook never entered the context window even once, so the measurement captured a skill that was never read plus the token cost of advertising it. Progressive disclosure assumes the model recognises when a reference would help. An 8B model that has not been trained for that pattern does not.

    The fix was to stop relying on the model’s judgement about when to look things up. The load-bearing rules moved into the 1024-character skill description, which is always in context, and the system prompt now instructs the agent to read the file before its first move. After five versions this reached 0.407, up from 0.309, and lifted wins from 34 to 59.

    Then it stopped improving, and how it stopped is the useful part.

    Version 4 attacked the state-tracking pile directly. It mandated an inventory check before every cook or cut, so the agent would see an ingredient’s current name before acting on it. The model complied completely: inventory calls tripled. Re-cook deaths went from 23 to 25, which is to say they did not move. The agent would dutifully check its inventory, read “chopped roasted red potato,” and roast it.

    Version 4b attacked the search pile by supplying the parser fact behind the disambiguation loops. The targeted loops shrank by roughly a quarter. The score did not move at all.

    My reading is that instructions install actions readily and inferences poorly. “Check your inventory” is an action, and the model performed it on command, every time. “Notice that this ingredient’s name already contains the word roasted, and therefore conclude that roasting is done” is an inference, and no phrasing I tried produced it. Version 4b makes the same point from the other side: giving the model the missing fact reduced the surface behaviour without changing the outcome, because knowing why a loop happens is not the same as being able to break it.

    That distinction is what sent me to the weights.

    4. Supervised finetuning on demonstrations

    If instructions cannot install an inference, perhaps examples can. The question is where examples come from.

    TextWorld hands you each game’s walkthrough, an oracle command sequence that wins. Training on walkthroughs alone has one disqualifying gap: a walkthrough never triggers a disambiguation prompt. The oracle walks straight to the correct object and never hears “Which do you mean.” So the free, perfect data demonstrates the clean path and leaves the single most common failure entirely undemonstrated.

    The alternative is the agent’s own successful runs. A game the policy won at temperature is a game where it actually hit an ambiguity, recovered, hunted through containers, and finished. That is the behaviour worth amplifying, and it arrives in the model’s own distribution rather than an oracle’s.

    So I split the data by what each source can actually show. For tiers where the policy sometimes wins, I sampled its own trajectories, eight per game across 60 training games, and kept the wins. For tier4, where the policy had won exactly zero games in every condition ever measured and rejection sampling therefore yields nothing at all, I used walkthroughs. The final set was 250 examples: 190 sampled from 1,920 episodes, plus 60 tier4 walkthroughs. The skills prompt was used to generate the demonstrations and then removed from the training context, so the behaviour would live in the weights rather than the prompt.

    Training on that set is ordinary supervised finetuning: 250 examples, three epochs, loss computed on assistant tokens only. The environment’s text is context for the model to condition on rather than something it should learn to produce, and including it would teach the model to imitate the game engine alongside the player.

    That last detail cost me a full training run. TRL’s assistant_only_loss option locates assistant spans using {% generation %} markers in the tokenizer’s chat template, and Granite’s stock template contains none. Rather than raising an error, the option silently produced an all-zero loss mask. The run completed, nothing complained, and the weights were unchanged. Patching the template fixed it, and verifying the fix meant confirming that rendered conversations stayed byte-identical while the supervised fraction rose from nothing to between 5.8% and 15% of tokens.

    The result, evaluated with no prompt at all: 0.712, and 140 wins.

    The failure piles are what convinced me this was real rather than a mean shifting for boring reasons. Deaths fell from 90 to 28. Zero-score stalls fell from 62 to 17. Both happened while the agent played four times as many games through to completion, which normally creates more opportunities to die.

    Set that beside version 4 of the prompt, where an explicit order to check inventory tripled inventory calls and left re-cook deaths at 23 against 25. The demonstrations installed the inference that the instruction could not. That is the clearest result in this project and the one I would generalise first.

    Tier4 is the sharpest illustration. It sat at exactly 0.000 win rate under every prompt condition ever measured, in both decoding regimes. After finetuning it reached 0.508. Every one of those points traces to the 60 oracle walkthroughs, which is to say to the data source that covers precisely what the agent could never demonstrate for itself.

    5. Reinforcement learning

    By this point the question had changed. It was no longer whether RL can rescue a weak policy but whether it can improve a competent one.

    Finetuning first made RL affordable, in a specific sense worth spelling out. GRPO [4] learns only from reward variance within a group of rollouts on the same problem. If all eight rollouts score identically, that group contributes nothing to the gradient. At the baseline’s 14% win rate most groups came back all-zero, so most of the compute bought nothing. Starting from the finetuned policy, 58 of 60 groups had usable variance, and the two that did not were degenerate from the ceiling: all eight rollouts scored a perfect 1.0 on a game the policy had completely solved.

    Reward is TextWorld’s own normalised score, unshaped. Rollouts run through the same agent graph used for evaluation, via TRL’s GRPO trainer [5]. Sixty steps over 20 training games took 59 hours 48 minutes on one machine.

    The result: 0.796 and 171 wins, with no prompt. Deaths fell further, from 28 to 23, and stalls from 17 to 14.

    The gain concentrates almost entirely in tier3, the tier that combines multi-ingredient processing with a six-room map. Tier1 was already near its ceiling, and tier2 and tier4 moved within noise. I had predicted the opposite spread and was wrong for a mundane reason worth recording: I reasoned from training games where the hardest ones regressed, but the 20 training games are not tier-stratified the way the benchmark is, so “hard training game” was never a proxy for “high tier.”

    6. What each rung was worth

    ConditionScore [95% CI]WinsDeathsStalls
    Baseline0.309 [0.269, 0.352]349062
    Best prompt0.407 [0.360, 0.456]595558
    Demonstrations (SFT)0.712 [0.665, 0.758]1402817
    Reinforcement learning0.796 [0.752, 0.838]1712314

    Table 2. All four conditions on the same 240 held-out games, greedy decoding, no prompt for the two trained models. A death is a fatal processing error; a stall is an episode that spent its whole move budget without scoring.

    Conditiontier1tier2tier3tier4
    Baseline0.4940.3620.2470.133
    Best prompt0.5560.5920.3240.156
    Demonstrations0.7610.8960.6850.508
    Reinforcement learning0.7830.9560.8770.565

    Table 3. The same runs by tier. Tier4 remains the weakest at 0.565, and its failure mode is systematic exploration of a nine-room map rather than anything about recipes.

    Comparing each rung against the one below it, on a per-game paired basis (§11), the prompt was worth +0.098, demonstrations +0.305 on top of that, and reinforcement learning a further +0.083. Every one of those intervals excludes zero.

    The ordering is the practical finding. Prompt engineering consumed the most calendar time of the three, across five versions and a good deal of failure analysis, and returned the smallest gain. Demonstrations cost 250 training examples and a few hours, and returned roughly four times as much. Reinforcement learning cost 60 hours of compute and returned a real but modest amount on top. If I were starting a similar agent project tomorrow I would build the demonstration pipeline first and treat prompting as a way to generate those demonstrations rather than as the deliverable.

    7. The prompt stopped being worth anything

    Since the trained models were evaluated with no prompt, an obvious question follows: would they do better with it? I ran the identical playbook against both ends of the ladder.

    PolicyNo promptWith the playbookPaired difference
    Baseline0.3090.407+0.098 [+0.051, +0.144]
    Trained (RL)0.7960.813+0.017 [−0.011, +0.045]

    Table 4. The same file, applied to the weakest and strongest policies. On the trained model, 201 of 240 games play out identically with and without it.

    The playbook that was worth +0.098 on the base model is worth nothing measurable on the trained one, and its interval now spans zero. The scaffold was a stepping stone: useful for generating the demonstrations that made it obsolete, and dead weight in the context window afterwards. Anyone maintaining a long prompt alongside a finetuned model should probably measure whether it is still doing anything.

    One exception is worth flagging as a hypothesis rather than a result. On tier4 the playbook is still worth +0.070 [+0.005, +0.136]. The mechanism is plausible, since tier4 is navigation-bound, the playbook has an explicit navigation section, and tier4 is where the demonstration data was thinnest. But that is one positive result across four tiers with no correction for multiple comparisons, and its lower bound sits at +0.005. I would want a dedicated experiment before believing it.

    8. Reinforcement learning sharpened rather than broadened

    PolicyGreedySampled, 3 per gameGain from sampling
    Demonstrations (SFT)0.7120.731+0.019
    Reinforcement learning0.7960.799+0.003

    Table 5. Temperature sampling at 0.7 with three samples per game, against single greedy decoding.

    Sampling three times buys the finetuned policy a small amount and the RL policy almost nothing. In my assessment that is outcome reward doing what it is supposed to do, concentrating probability mass on trajectories that work and leaving less to gain from exploring alternatives at inference. Error counts agree, falling from 30 to 17 across the sampled runs.

    The practical consequence is a cost result rather than a quality one: the RL policy reaches the same score at roughly a third of the inference spend, because greedy decoding is as good as sampling three times and averaging.

    9. Two-thirds of the RL run bought nothing

    CheckpointDev scoreWins
    Before RL0.741150
    Step 40.736150
    Step 200.803175
    Step 400.831184
    Step 600.821179

    Table 6. Validation scores across the run. Step 40 was selected on this split and then evaluated once on test.

    Learning plateaus by step 40. The paired difference between step 40 and step 60 is +0.010 [−0.015, +0.035], and 219 of the 240 games play out identically between them. Roughly 20 of the 60 hours were productive; the remaining 40 changed almost nothing.

    I could only see that because I copied checkpoints out from under the trainer’s own rotation. TRL keeps the last few by default, which here would have been steps 52, 56, and 60, all of them inside the plateau. The run would have looked like steady improvement to the finish. A small background script hardlinked selected checkpoints into a separate directory as each save completed, using hardlinks rather than copies because writing 33 GB creates that much dirty page cache, and page-cache pressure immediately after a checkpoint save had already killed two earlier training runs on this machine.

    The step-4 row earns its place too. Four steps of RL produce a clean null: 202 of 240 games identical, win count unchanged. That is a useful control, since it shows the evaluation pipeline does not manufacture differences out of serving noise.

    10. Things I would do differently

    • Reach for demonstrations earlier. Every prompt version that mandated an action got compliance. Every one that required the model to draw a conclusion from state already in its context failed, regardless of phrasing. If a failure looks like the model not noticing something rather than not knowing something, prompting is unlikely to fix it.
    • Pick the demonstration source per failure mode, not per convenience. Oracle walkthroughs are free and perfect and structurally cannot demonstrate error recovery, because the oracle never makes errors. Self-sampled wins cover recovery but yield nothing where the policy never wins. Each source covered exactly what the other could not.
    • Keep a spread of checkpoints, not the last few. Default rotation preserves the checkpoints that tell you least once learning has flattened. Four preserved checkpoints cost 130 GB and revealed that two-thirds of a 60-hour run was wasted.
    • Re-measure the prompt after training. A prompt tuned against a weak policy can become dead weight against a strong one, and nothing will tell you unless you check.

    The remaining headroom is legible enough to name. Tier4 sits at 0.565 against tier2’s 0.956, and its failures are about systematically exploring a nine-room map rather than anything to do with recipes. Twenty-three deaths and fourteen stalls survive out of 240 games. Whether those are reachable by more reinforcement learning or need a different kind of demonstration is the next thing I would measure.

    11. How this was measured

    Two conventions apply to every number above, and they are the reason I trust the ordering in §6.

    Every score carries a bootstrap 95% confidence interval from 10,000 percentile resamples. Where two intervals overlap I describe the conditions as indistinguishable rather than reporting the point estimates as a difference.

    Comparisons are paired per game, not marginal. Game difficulty varies enormously even within a tier, and that variance swamps the effects being measured. Since every condition plays the identical game set under greedy decoding, the right statistic is the per-game score difference with its own interval. Two overlapping marginal intervals are not evidence of no effect; usually they are difficulty variance that pairing removes.

    The cost of skipping that is concrete. Early in the prompt phase, one comparison read +0.19 at 10 games, +0.23 at 32, then −0.006 at 80, before settling at +0.098 at 240. Small samples do not merely widen the interval, they break selection: at three different points I would have shipped a different prompt version, each time on a number that later reversed.

    Model selection ran on a separate validation split of 240 games, and the test set was used once, at the end, for the single selected checkpoint. Choosing the best of several near-tied checkpoints on validation inflates that estimate, so the test result landing slightly below the validation result (+0.083 against +0.090) is expected rather than a failure to replicate.

    Two measurement mistakes are worth passing on. First, mid-run confidence intervals on the training games cleared zero twice and collapsed twice, because the game loader cycles in fixed order and front-loads the games RL helps, making any partial cycle a biased sample. Only the complete cycle was interpretable. Second, while trying to speed up rollouts, I found a genuine 1.49x improvement from reusing the KV cache across turns and rejected it: it shifted per-token logprobs by 0.11 to 0.56 nats, and GRPO’s importance ratio is an exponential of exactly that difference, so it would have distorted every ratio by 12% to 75% while every downstream signal continued to look healthy. Two successive correctness checks I wrote passed it spuriously before a third caught it, one because it compared only five tokens and one because it derived its own pass threshold from the same broken computation it was checking.

    References

    1. TextWorld, Microsoft Research. Also: TextWorld: A Learning Environment for Text-based Games.
    2. deepagents, LangChain.
    3. ibm-granite/granite-4.1-8b, IBM.
    4. DeepSeekMath: Pushing the Limits of Mathematical Reasoning in Open Language Models, which introduces GRPO.
    5. TRL, Hugging Face.
  • Grounded Verification and Operational Failure Modes in an Agentic Trading-Card Cataloging Pipeline

    Over the past several months I have operated a small fleet of software agents performing a narrowly scoped but operationally demanding task: given a photographic scan of a trading card, determine its identity, assign a price, and record it in an inventory system. The task’s narrowness is, in my view, precisely what makes it instructive. Card cataloging belongs to a comparatively uncommon class of practical agentic tasks in which a ground-truth answer exists and is independently checkable — each card possesses exactly one correct name, set, and collector number, verifiable against public catalogs. This property shaped nearly every design decision described below, and motivates my writing up the system for practitioners building comparable agentic pipelines.

    1. System overview

    The system operates on Multica [1], an open-source platform for managing software agents as task-assignable collaborators — issues, assignments, and comments function analogously to a human team’s workflow. Each agent executes through Pi [2], a minimal open-source agent harness, against a language model served on locally controlled hardware rather than a hosted commercial API (§6 characterizes this configuration in detail). Ground-truth identity information is drawn from public sources — Scryfall for Magic: The Gathering, TCGCSV for other trading card games — together with a privately maintained vector index of annotated card images accumulated over the course of operation. The complete stack thus comprises five components: an issue-tracking board, an agent harness, a locally served open-weight model, an image index, and a small number of external reference catalogs.

    The unit of work is deliberately minimal: one card corresponds to exactly one issue. A scan is attached to the issue; the card’s identity is withheld, and exactly one agent is assigned. I consider fine-grained task decomposition of this kind to be underappreciated in agentic system design. It confers four properties simultaneously: retries become inexpensive, failures remain isolated to a single unit, progress is directly observable, and each completed task’s transcript constitutes a well-formed training example. Coarser decomposition — assigning an agent to “process a batch” — forfeits all four.

    flowchart TD
      A[Scan filed as one issue] --> B[Card Analyst]
      B --> C[Identify: image read + vector match + catalog cross-check]
      C --> D[Record card and cite every source]
      D --> E{Validator re-fetches and verifies}
      E -->|all claims sourced| F[Done]
      E -->|substance fail, under 2 rounds| B
      E -->|still failing after 2 rounds| G[Blocked for a human]
      H[Queue TTL expires an unclaimed task] -.->|watchdog re-enqueues| B

    Fig 1. The complete pipeline. The Card Analyst hands off to the Validator, which returns one of three outcomes — pass, revision request, or escalation to a human reviewer — and a Watchdog agent re-enqueues issues silently dropped by the task queue (§5).

    2. Grounding procedure

    The first agent, the Card Analyst, is responsible for identification and recording. The identification step itself is straightforward; the more consequential design choices concern the constraints imposed on it, described below.

    • Two independent reads, cross-checked prior to acceptance. The agent reads the image directly and, separately, embeds the scan and performs a nearest-neighbor lookup against the annotated corpus (cosine similarity ≈1.0 indicates an identical image; ≥0.85 indicates probable identity). This read is then confirmed against an external catalog by set code and collector number. Acceptance requires agreement among all three signals.
    • Every factual claim must cite an independently re-fetchable source. A Scryfall URI or a TCGCSV product identifier (with category and group) is required, rather than an unsupported assertion of recognition. Where no source can be located, the agent records only what is directly legible from the scan and states explicitly that the remainder could not be sourced. I take a confidently stated incorrect fact to be strictly worse than an honestly reported failure to identify.
    • Prices are never estimated. A price is recorded only when a genuine market datapoint exists (a Scryfall prices.usd field, a TCGplayer price row); extrapolation from comparable cards, corpus averages, or rarity class is disallowed. An unpriced record is an acceptable outcome; a fabricated price constitutes a failure by definition.

    A subtler design principle underlies this procedure and, in my view, generalizes beyond this application: the model should not be asked to infer a quantity that a cheap deterministic input already determines. Foil status and condition are not inferred from the image; both are supplied by the operator, since cards are physically pre-sorted by these attributes prior to scanning, and the agent is explicitly instructed not to infer foil status from surface glare. Substituting a known input for a model judgment eliminates an entire class of error at no cost.

    3. Verification as an independent procedure

    I consider the following design pattern to be the most broadly generalizable finding of this work: the agent performing a task should not be the agent that determines whether the task was performed correctly. A second, independently instantiated agent — the Validator — is triggered upon handoff from the Card Analyst. Its function is restricted to confirming that every claim made by the analyst is supported by the source cited for it: each citation is re-fetched independently and checked against the claimed name, collector number, card text, and price. A claim lacking a citation fails; a citation that does not support the claim fails; an estimated price fails.

    Two further constraints are necessary for this arrangement to function in practice.

    First, failure is restricted to substantive grounds only, never presentation. The Validator is explicitly prohibited from failing a ticket for formatting, wording, layout, or stylistic preference. Exactly four failure conditions are defined — an uncited claim, a claim contradicted by its cited source, a fabricated price, or a missing traceability tag — and no others. This constraint is more consequential than it may initially appear: the default failure mode of an LLM-based critic is to identify presentational deficiencies rather than substantive ones, and restricting the checker to substance is what prevents it from degenerating into a pedantic gate rather than a useful one.

    def validate(claim, source):
        if claim.citation is None:            # uncited: fail
            return FAIL
        if not source.confirms(claim.value):  # source contradicts: fail
            return FAIL
        if claim.is_price and not source.is_market_datapoint:
            return FAIL                       # estimated or fabricated price: fail
        return PASS   # never fail on wording, layout, or formattingCode language: Python (python)

    Second, a bounded retry mechanism. Each rejection by the Validator increments a counter; after two rejections, the system escalates to a human reviewer rather than permitting indefinite iteration. Adversarially paired agents require an explicit termination condition, absent which they will iterate without converging on precisely the cases that most warrant human attention.

    The rationale for separating execution from evaluation is that self-assessment is unreliable: a model tends toward leniency with respect to its own output and shares its own systematic blind spots. A second agent operating under a narrowly distinct mandate — verification against external sources, rather than cataloging — detects a measurable fraction of errors precisely because its judgments are anchored in re-fetched ground truth rather than in the same priors that produced the original claim.

    4. Structured output and its limits

    In an earlier iteration, both agents reported findings as unstructured free-text comments. Content converged reliably across runs, but layout did not — headings in one instance, a table in the next, the verdict positioned inconsistently. I subsequently standardized on a fixed template comprising required fields (identity, operator-supplied inputs, price, sources, record identifier) and a verdict-first format for the Validator’s output.

    ## Catalog: CARD_NAME — SET-NUMBER
    
    ### Identity
    - Game / Name / Set / Number / Rarity
    
    ### Operator inputs
    - Condition · Foil · Batch      (deterministic — from the pre-sorted piles)
    
    ### Price
    - $X.XX USD   —or—   Unpriced (no market datapoint)
    
    ### Sources
    - every claim cites a Scryfall URI or a TCGCSV product id
    
    ### binder
    - card_id: CARD_IDCode language: Markdown (markdown)

    A predictable failure mode follows from this standardization: the temptation to have the Validator enforce the template. This should be avoided. Doing so directly contradicts the substance-only failure criterion (§3) and converts the quality gate into a formatting linter capable of rejecting correct work over an incorrectly labeled heading. I instead treat the template as a strong expectation for the producing agent and explicitly exclude it from the checking agent’s failure conditions — yielding consistency where it benefits human readers and downstream parsing, without introducing new failure modes.

    A final practice worth noting: agent instructions are maintained as version-controlled source. Instruction files reside in a git repository and are synchronized verbatim into the platform, such that prompt modifications are subject to review, diffing, and reversion in the same manner as application code. One consequence of this synchronization step deserves mention: the platform strips angle-bracket tokens as though they were HTML markup, causing placeholder tokens of the form <card_id> to be silently deleted; I resolved this by adopting uppercase placeholder tokens instead. Prompts, in my view, constitute software in the fullest sense — subject to defects and requiring a build step.

    5. Operational failure modes

    The most instructive failure observed in this system’s operation was unrelated to model quality. Following submission of a large batch (several hundred cards), the majority of issues remained unprocessed, with both agents idle.

    The underlying cause was a queue time-to-live constraint internal to Multica. Issue assignment creates a task; a task remaining unclaimed in the queue beyond a fixed threshold (two hours, in the version deployed here) is marked “expired” by a background sweeper. Given the analyst’s configured concurrency limit, a 300-card batch drained more slowly than this threshold, such that the queue’s tail expired silently and the corresponding issues were left in an orphaned state — assigned, but without an active task and without automatic retry. This behavior was identified only by inspecting Multica’s scheduler source following the observed symptom; it is not documented in any user-facing reference.

    The Watchdog agent exists solely as a consequence of this platform-level constraint; it addresses a limitation of Multica’s scheduler rather than a defect in my own agents’ behavior. Operating on an hourly schedule, its sole function is to identify orphaned issues — assigned to an agent, in a pre-execution state, whose most recent task terminated with a queue- or platform-level error, and lacking any currently active task — and re-enqueue them. This constitutes a maintenance procedure rather than an intelligent process; however, the underlying failure mode is intrinsic to asynchronous agent queues in general, and to the specific time-to-live constraint enforced by Multica, such that the Watchdog is retained as a permanent component of the system.

    Three general observations follow, none of which are novel individually but which I consider collectively underappreciated:

    • Sustained throughput must exceed the platform’s queue time-to-live, or work accumulates and silently expires. Both quantities should be known explicitly, not assumed.
    • Trigger mechanisms require idempotency and deduplication. Re-execution of a task must not produce a duplicate record, and a recovery process must never re-enqueue a task that remains genuinely in flight.
    • A dedicated maintenance process should be planned for from the outset. Any sufficiently long-running agentic system accumulates stuck state, and a process for detecting and repairing it should be regarded as a required component rather than an optional addition.

    6. The inference engine

    Every agent in the pipeline is served against a self-hosted model rather than a hosted commercial API, using vLLM [3] on a single NVIDIA DGX Spark unit (a GB10 Blackwell system with 128 GB unified memory). The model is Qwen3.6-35B-A3B, a hybrid Mamba-attention mixture-of-experts architecture (approximately 35B total parameters, approximately 3B active per forward pass), quantized using Unsloth’s [4] dynamic-precision NVFP4 scheme — a mixed-precision compressed-tensors quantization retaining attention, output, and a subset of late-layer experts at FP8 while the majority of mixture-of-experts parameters are quantized to NVFP4. This scheme is reported to benchmark meaningfully faster than a uniform NVFP4 quantization at comparable output quality, a property of practical relevance when attempting to meet a 35B-parameter model’s throughput requirements on a single GPU.

    vllm serve unsloth/Qwen3.6-35B-A3B-NVFP4 \
      --served-model-name nvidia/Qwen3.6-35B-A3B-NVFP4 \
      --tensor-parallel-size 1 \
      --kv-cache-dtype fp8 \
      --attention-backend flashinfer \
      --gpu-memory-utilization 0.7 \
      --max-model-len auto \
      --max-num-seqs 4 \
      --max-num-batched-tokens 8192 \
      --enable-chunked-prefill \
      --async-scheduling \
      --enable-prefix-caching \
      --enable-prompt-tokens-details \
      --load-format fastsafetensors \
      --reasoning-parser qwen3 \
      --tool-call-parser qwen3_xml \
      --enable-auto-tool-choice \
      --default-chat-template-kwargs '{"enable_thinking": true, "preserve_thinking": true}'Code language: Bash (bash)

    The engine is deployed via Docker (the official vllm/vllm-openai image), a decision that proved more consequential than anticipated. On this GB10/Blackwell hardware, several of vLLM’s optimized code paths — speculative decoding and certain quantization kernels among them — were observed to silently deadlock the engine (characterized by sustained 100% GPU utilization at an anomalously low power draw, with no tokens emitted) or to produce syntactically well-formed but semantically incorrect output on specific nightly builds. Neither failure mode is attributable to the Qwen architecture or the Unsloth quantization; both arise from the intersection of recently released hardware with recently released inference code. Remediation was unglamorous: pinning to a known-stable image tag rather than tracking the rolling nightly release, disabling speculative decoding, and verifying each configuration change against an active health probe rather than a clean startup log. I note that none of these failure modes are specific to the trading-card application; they represent a general cost of deploying inference workloads on newly released accelerator hardware.

    7. Economic characterization

    I report measured, rather than estimated, operating characteristics. Throughput: real timestamps recorded by the issue-tracking board indicate that a batch of 323 cards, in progress at the time of writing, completed at a rate of approximately 16 cards per hour, a figure bounded by the Card Analyst’s configured concurrency limit. Token usage: the platform records per-issue token consumption directly; a sample of 43 completed cards drawn from this batch yields a mean of approximately 26,000 fresh input tokens, approximately 150,000 cache-read tokens (a hit rate exceeding 85%), and approximately 3,200 output tokens per card. The high cache-hit rate reflects substantial context sharing between the Card Analyst and Validator — identity information, research tooling, and the comment format are largely invariant across cards. Power: GPU power draw was measured directly via nvidia-smi while both agents were actively processing tasks, yielding a mean of approximately 50 W on the GB10 system-on-chip power rail, against a manufacturer-specified [5] 140 W SoC thermal design power and a 240 W total system power supply rating.

    Applying this identical per-card token profile to Claude Sonnet 5’s published pricing [6] yields the comparison in Table 1.

    ApproachCost / cardCost / 1,000 cards
    Local (Spark, measured GPU power draw, at an assumed $0.15/kWh)$0.0005$0.46
    Local (NVIDIA’s rated 240W system ceiling, worst case)$0.0022$2.23
    Claude Sonnet 5 — intro pricing (through 2026-08-31)$0.11$114
    Claude Sonnet 5 — standard pricing$0.17$171

    Table 1. Comparative per-unit cost, self-hosted inference versus a hosted commercial API, computed from the measured token profile described above.

    I emphasize that the $0.15/kWh figure is a stated assumption rather than a measurement of actual utility billing — power draw was measured at the GPU only, not at the wall outlet or utility meter — and the local-cost figures should accordingly be treated as illustrative rather than exact. Nor do I consider this an equitable comparison in the sense of “the hosted API is categorically worse”: one configuration is a fully managed service requiring no operational burden, with the failure modes described in §5 handled transparently; the other is hardware already owned by the operator, otherwise idle. The comparison does, however, constitute the substantive economic argument underlying this approach. At the token volumes observed here, self-hosting a comparatively small model converts bulk cataloging from a viable-but-metered activity into one sufficiently inexpensive that consumption need not be rationed — a property that permits the fleet to be applied uniformly across an entire inventory rather than reserving hosted-API calls for a difficult subset.

    8. Discussion: verification as reward signal

    I consider this the most consequential implication of the system described above, and it is precisely the checkable-answer property (§1) that makes it possible. The pipeline produces, as an unavoidable byproduct of ordinary operation, the following artifacts for every processed card:

    • a complete transcript of the analyst’s attempted solution to a well-defined task;
    • an independently verifiable pass/fail signal from the Validator, grounded in re-fetched external sources;
    • and, for a subset requiring adjudication, human-applied labels (identity correct or incorrect; research sourced or hallucinated).

    This is structurally identical to a reward signal. A production quality-assurance step that verifies output against ground truth constitutes, in effect, a reward model that required no separate training procedure, and its outputs form a stream of (task, response, verified-reward) tuples. This admits reward-filtered supervised fine-tuning and RLVR-style approaches for continued improvement of a small, computationally inexpensive, open-weight model on this specific task, with frontier models employed only as a bootstrapping teacher and as a fallback for difficult cases. The economic argument follows directly: volume is directed toward a model with substantially lower marginal cost per call, one that improves incrementally with each human correction, rather than incurring hosted-API rates for a task a specialized model in the 4–8B parameter range can perform adequately.

    I note explicitly that the described training flywheel represents a direction of ongoing work rather than a completed result. The underlying claim, however, holds independent of the extent to which it is realized: for an agentic workflow whose outputs admit verification, verification and training-data generation are the same artifact.

    9. Generalizable principles

    Abstracting away the specific application, I summarize the following principles as applicable to agentic systems generally:

    1. Minimal task granularity. One task should correspond to one clean transcript, yielding inexpensive retries, isolated failures, and training examples requiring no further preparation.
    2. Grounding of every claim in an independently re-fetchable source, with an honestly reported failure to identify preferred over a confidently incorrect assertion.
    3. Substitution of deterministic inputs for model judgments wherever a low-cost known value exists (§2 illustrates this with foil status and physical condition).
    4. Separation of execution from evaluation, with the evaluating agent restricted to a narrow verify-against-ground-truth mandate, a substance-only failure criterion, and an explicit termination condition.
    5. Structured output formats to ensure consistency, without permitting format compliance to constitute an independent failure criterion.
    6. Treatment of agent instructions as version-controlled source, subject to review, diffing, and reversion.
    7. Explicit operational planning: monitoring throughput against platform-imposed time-to-live constraints, ensuring trigger idempotency, and constructing a dedicated recovery process.
    8. Design for verifiability from the outset, such that production quality assurance doubles as a reward signal and the workflow generates its own training data as an operational byproduct.

    None of these principles is individually novel. The trading-card domain, however, compelled their consistent application, owing to a task structure that is unforgiving of incorrect output while being generous in signaling when output is incorrect — a combination that, in my assessment, constitutes an effective teacher for agentic system design more broadly.

    References

    1. Multica: an open-source platform for managing agents. https://multica.ai (source: github.com/multica-ai/multica).
    2. Pi: a minimal open-source agent harness. https://pi.dev.
    3. vLLM: a high-throughput inference engine for large language models. github.com/vllm-project/vllm.
    4. Unsloth: quantization and fine-tuning tooling for open-weight models. https://unsloth.ai.
    5. NVIDIA. DGX Spark User Guide — Hardware Overview. docs.nvidia.com/dgx/dgx-spark/hardware.html.
    6. Anthropic. Claude models overview and pricing. platform.claude.com/docs/en/about-claude/models/overview.