Case study Part 3 of 4 · architecture
The Melvor cascade, built
How code, Jev, Claude and a person share the work of playing Melvor Idle for days. Each part below has a diagram or color-coded pseudo-code. The pseudo-code is condensed from the real source, and each line is marked with the tier that runs it.
The tiers in this agent
Human
Real money, the account and its settings, the owner's own save slot, anything irreversible outside the game, and preferences about how the character plays. Also the one fix automation can't reach: bringing a hidden window back.
LLM
Claude, woken by events. Clicks the planned steps, reviews escalations and the watch band, audits plans, writes milestones and playbook lines, and changes the code so the same escalation doesn't come back.
Jev
One decision call per tick: target priorities (0–4), one-off actions (yes/no), item usefulness, when to look again, novelty. A second, separate call checks doubt on the chosen action. On failures it judges whether a retry makes sense.
Code
jevkit and brain: read state, list only legal actions, plan whole chains, size batches, run the farm and the town, apply policies that have a right answer, combine Jev's scores with explicit weights, locate buttons, verify outcomes.
Code · guard
A 1-second loop inside the game tab: eat, flee, collect loot, stop at targets, protect reserved stock, restart a fight after a stall. It acts and logs; it never decides strategy.
One tick, end to end
A tick is one decision: what should the character be doing now? Here is the path, colored by the tier that runs each step.
- LLMClaude wakes
From a watcher event (idle, escalate, stale, milestone…) or a 20-minute fallback, it calls
jevkit.tickBrief(). - CodeObserve
Compact state: skills, bank, gear, the running action, combat, loot, popups, farm plots (including which plot types have no seed) and the town. Only legal candidate actions. The current milestone and standing goals, each with the planner's next step and why.
- JevOne decision call
Up to 32 questions over that state plus goal, playbook and milestone: score each target, yes/no each one-off, rate each sellable item's usefulness, how soon to check back, and
novel_situation. - CodeDecide: policies first, then weighted scores
Computable policies win outright: dialogs, low HP, loot, farm chores, mastery tokens, Township claims and builds, plot unlocks, food, empty armor slots, gear upgrades, ammo, bank space, tools, training-bow sales, surplus, seed runs and weapon swaps. Otherwise target score = Jev's score/4 + 0.1 if it's the milestone + 0.08 if already running. The planner already chose each target's step.
- JevDoubt check, a separate call
The chosen action, in words, goes back to Jev: "Would a careful player object to this right now?"
- CodeGate
Any check above 0.7 sets
escalate. Otherwiseplan()turns the action into UI steps: navigate, pick a category, pick a recipe, press Create. - LLM CodeClick
For each step,
locate(step, frameWidth)returns screen coordinates just before the click; Claude clicks and waits about a second. - CodeVerify the outcome
Is the activity running, or did its product count rise? Did the gold drop after a purchase? Is the item worn? A guard stop on purpose counts as success.
- JevIf a step failed: recover
Transient timing or layout issue (retry, at most twice), or an unexpected state that needs Claude?
- LLMIf escalated: Claude
Inspect, decide, act, then fix the cause in a lower tier and log it.
- HumanOnly across fixed lines
Money, accounts, the owner's save, irreversible outside the game, preferences.
Note what's missing from the hot path: Claude never chooses the action on a routine tick. It carries out a decision made by code and Jev, and it is only asked to judge when something escalates.
The guard: safety in the fastest tier
Claude wakes every few minutes at best, which is far too slow for combat. Early on, a Cow took the character from 102 HP to 6 between two wakes. The guard is a timer inside the page:
every 1 s in the game tab: log arrows, XP, food and deaths per monster # feeds the planner's measurements if fighting: if ranged and the quiver is empty: run; record a deliberate stop if enemy max hit ≥ 40% of max HP: run; mark the monster unsafe floor = max(40% of max HP, 2 × enemy max hit) if HP < floor: if this food slot is empty: switch to the next stocked slot eat, or run if every slot is empty # a death loses a worn item if 8+ loot stacks are waiting: loot all # a full container throws drops away stop the running action at the planner's target (item count or level) stop a recipe that would eat into reserved stock after an offline catch-up ended a fight: restart it
Every rule here started as a failure found by a higher tier. The food-slot switch came from a near miss: the guard ate only from the current slot, so with that slot empty it did nothing while HP fell. The 40% risk rule came from a death-risk policy the owner approved (see part 4).
Deciding: policies first, judgment second
answers = jev(state + goal + playbook + milestone, questions) # one call, ≤ 32 questions# policies with a computable answer come first, in this orderif a dialog is open: press Jev's choice, or wait for Claude if confidence < 0.6if HP < 40% or answers.eat_now ≥ 0.6: eatif 8+ loot stacks wait, or loot waits and the fight stopped: collect itfarm: harvest > clear dead > Gloop or compost an empty plot that has seeds > buy compost in packs of 25 > plant into an insured plot # an uncomposted crop grows half the time; packs mean fewer shop tripsif mastery tokens and (≤ 3 free slots or a stack ≥ 20): claim them # single drops cost a trip eachif a Township task gives nothing, or pays ≥ its cost at sell prices: claim it # a trade is arithmeticif the town plan's next build is affordable above a 60k gold floor: build it # the town pays no gold for a long timeif a farm plot costs ≤ 10% of gold: unlock itfood top-up · fill an empty armor slot · gear upgradeammo top-up / upgrade / bootstrapbest one-off Jev rated ≥ 0.7 # ranked before the 8-question cap: rare kinds firstbank slots (full, or ≤ 2 free and gold ≥ 2.5× price)next tool tier at ≤ 25% of goldsell a training-bow stack worth 10k+ once its run stops, never a milestone's bow # bows are the incomesell surplus gear · sell junk Jev rated useless, under bank pressure# otherwise Jev's priorities pick the target;# the planner already picked its stepscore(t) = answers.tgt[t] / 4 + 0.1·[t is the milestone] + 0.08·[t is running]if plots of a type sit empty with no seed for them: that type's seed goal goes first # idle plots once sat 8 hif the target wants another weapon (dropWeapon): swap firstif the target is a fight and food is short: cook firstdo = best(t).step
The same order as a picture: code settles what it can, Jev's answers settle the rest, and only a reading over 0.7 reaches Claude. The share decided in code grew from 17% to 23% as the farm and town moved there.
Two design choices matter here. Jev picks the goal, code picks the step. Jev rates how much each target matters now. The planner works out the chain (mine, then smelt, then smith, then fletch) and sizes each batch. Weights live in code. Jev's scores are features, not verdicts, so a bias in Jev can be corrected by changing a number instead of rephrasing a question.
Planning: what code works out (updated October 14)
Jev never sees these choices. Each was a push-down: a gap Claude found in the options, fixed so the planner offers better ones.
The planner walks back from the goal (2,000 arrows) to whatever is short, sizes each step to the batch, and the guard stops each step at its count. Gray inputs are gathered, banked or bought; teal steps are crafted.
# recipes that take any of several inputs (Arrow Shafts: any log)alt = the alternative with the most output from stock: min(held / cost) × yield # 19 Maple ×5 lost to 2,066 Mahogany ×6# training batcheswant = XP still needed / XP per craftbatch = want ≤ 500 ? want : want split evenly into chunks of at most 1,000 # 1,638 logs → 2 × 820, not 5 × 400an input that can't cover the whole batch is "missing": make or gather it first # 34 Emeralds for a 400 batch# which monster trains Rangedvalue(m) = (measured XP per arrow if both have 50+ arrows, else modelled damage per arrow) / (1 + food per arrow) for every safe monster, easy or hard # a level-up made an eater "easy"# which weapon farms dropsbow = hits to kill(ranged max) × (3.0 s + 2.5 s to make each arrow) + 3 s respawnblade = hits to kill(melee max) × 2.4 s + 3 s respawn # max hits remembered per style while wornweapon = the faster one # predicted 7.5 s a kill, measured 6.7# the running batchits stop point (item count or level) survives every one-off: sell, buy, claim, build, plant… # a town build once reset a cut# the townplan = 20 shelters → 20 woodcutters → 4 carpenters → (miners, farmland if stone or food runs low) → 20 carpenters → 30 huts once 10 carpenters run # huts took the carpenters' woodbuild 5 at a time, or exactly what finishes a step; Max when it builds exactly that # a trip per hourly updatea step only waiting for its batch holds the plan # later steps can't spend its stock# the farmseed goals per plot type follow Farming level: Potato → Onion → Cabbage → Tomato; Garum for herbsthe planner finds the drop source; an unknown category name fails loudly # a typo once read as "done"
The common thread: each rule replaced a choice that looked reasonable among the options shown. Jev's doubt on these sat at 0.19–0.50 and never crossed 0.7.
Doubt and the escalation gate
chosen = describe(do) # e.g. "Start: Fletching Arrow Shafts — the planner's next step toward 'Ammo stock'"doubt = jev(state + chosen, "Would a careful player object to this action right now?")checks = { novel_situation, doubt_action, popup_unsure? }if no milestone is left: checks.milestones_complete = 1log every reading to ticks.jsonlif any check > 0.7: escalate to Claude nowelif a check is in 0.5–0.7: watch band; Claude reviews it at the next wake
Doubt judges the choice, not the options
Above the line, doubt has been right every time. A one-line context change moves it reliably. But every planner gap in the middle row sat under 0.7 while the plan was wrong, because Jev judged the options it was given. Those were found by Claude reading plans, or by the owner asking. Part 4 walks one of them through as a sequence diagram.
Doubt is asked after the choice, in its own call, with the concrete action in view. Inside the decision call, novelty sat in a narrow band and rarely fired. The separate doubt call spread widely and tracked real problems. More on that in Novelty and doubt.
Verify and recover
ok = verify(lastTick) # the outcome, not the start buy → gold went down equip → the item is worn sell → gone from the bank food → equipped food went up build → the obstacle is built plot → harvested, cleared, unlocked, composted or growing town → the building count reached the target task → completed token → the stack is gone action → running, or its product count rose, or a guard stopped it on purposeif not ok or a step couldn't be located: v = jev("transient timing or layout issue?", "unexpected state that needs a stronger reasoner?") if v.transient ≥ 0.6 and attempts < 2 and v.needs_claude ≤ 0.7: retry else: escalate to Claude (logged with state and verdict)
The "stopped on purpose" clause matters. Before it existed, every Ranged round ended in a false failure: the guard flees when the quiver runs dry, so verify saw "fight not running" and escalated a perfectly normal ending.
Each wake
on a watcher event or the 20-minute fallback: if stale or hidden: boot the bridge, reinstall jevkit, bring the tab forward if the tab still can't be brought forward: ask the owner to click it brief = jevkit.tickBrief() # observe + Jev + plan if brief.escalate: inspect, decide, act, then FIX THE CAUSE BELOW and log it for step in brief.steps: xy = locate(step, frameWidth) click xy; wait about 1 s r = verify(); if not r.ok: recover(...) re-tick after one-offs, sales and dismissals (up to 4 ticks a wake) Force Save about every 15 min; confirm the save header reset money · accounts · the owner's save · irreversible outside the game · preferences → stop and ask report one line; schedule the fallback wake
Waking on events, not timers
| Event | Fires when | Who handles it |
|---|---|---|
idle | Nothing is running (a recipe ran out between wakes) | LLM ticks for the next action |
escalate | The last tick crossed 0.7 on a check | LLM reviews and fixes the cause |
milestone | The current milestone changed | LLM saves; writes the next chapter after the last one |
stale | No fresh snapshot for 30 s | Code boot script, then LLM |
hidden | The game is a background tab and stalls | Code focus script, then Human |
hp-low | Under 30% HP in combat (the guard should have prevented it) | LLM audits the guard |
loot | 20+ stacks waiting (the guard collects at 8) | LLM audits the guard |
save-old | The cloud save is an hour old | LLM Force Saves and checks the header |
Two events, hp-low and loot, exist to catch the guard failing. A safety layer needs its own audit.
What Jev is asked
| Question | Type | Asked about | How code uses the answer |
|---|---|---|---|
tgt_i | Score 0–4 | Each actionable target (up to 8) | Weighted pick, plus milestone and stay bonuses |
need_i | Score 0–4 | Each activity, only when every target is blocked | × (0.7 + 0.3 × relative XP rate) |
do_i | Yes/no | Each one-off that needs judgment: buy, equip, open, a Township trade that costs more than it pays (up to 8, rare kinds ranked first). Farm chores, town builds and token claims are code-only and never reach Jev | Do the best one at ≥ 0.7 |
use_i | Score 0–4 | Each sellable bank item (up to 8) | Junk if use/4 ≤ 0.15, under bank pressure |
check_back | Score 0–3 | How soon to look again | 5 s to 10 min, capped by when the recipe runs out |
eat_now, popup_choice, tutorial_ready | Yes/no, choice | Only when in combat, a dialog is open, or in the tutorial | Eat at ≥ 0.6; a dialog choice under 0.6 confidence waits for Claude |
novel_situation | Yes/no | Is this outside the goal and playbook? | > 0.7 escalates |
doubt_action | Yes/no, own call | The chosen action, in words | > 0.7 escalates; 0.5–0.7 watch band |
transient, needs_claude | Yes/no, on failure | A failed step and the screen state | Retry at ≥ 0.6, at most twice |
Shared context (goal, playbook, milestone) travels once in the state; each question just points at it. Arithmetic always beats judgment where both apply. Jev's check_back is overridden by the moment the current recipe will run out of inputs.
What the logs say
From all 935 ticks across six days (updated October 14):
| Most common code policies | Ticks |
|---|---|
| Farm chores (harvest, clear, compost, buy compost, plant) | 45 |
| Ammo top-up (equip arrows from the bank when the quiver runs low) | 42 |
| Collect loot (now mostly done by the guard, between ticks) | 24 |
| Township build plan | 21 |
| Sell surplus gear under bank pressure | 16 |
| Buy the next tool tier (axe, pickaxe, rod) when it costs ≤ 25% of gold | 10 |
| Sell a training-bow stack once its run stops | 9 |
| Buy bank slots when nearly full | 8 |
| Claim mastery tokens | 7 |
| Ammo bootstrap · fill an empty armor slot · ammo upgrade · weapon swap · unlock a farm plot · seed run · food top-up · claim a Township task · cook before fighting | 35 |
Every one of those policies began as a question Jev answered, or as a mistake an upper tier caught. Each became code once it was clear the question had a computable answer. Part 4 tells those stories.
Next: Push-downs: 65 resolved escalations, who caught each one, and which tier the fix landed in.