Decision Tier

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.

Code guards, planner, policies, verification Jev decision model calls LLM Claude Human the owner Environment browser and window

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.

hours · rare

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.

minutes · per wake

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.

seconds · ~$0.001 a tick

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.

milliseconds · per tick

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.

every second · free

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.

  1. LLMClaude wakes

    From a watcher event (idle, escalate, stale, milestone…) or a 20-minute fallback, it calls jevkit.tickBrief().

  2. 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.

  3. 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.

  4. 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.

  5. JevDoubt check, a separate call

    The chosen action, in words, goes back to Jev: "Would a careful player object to this right now?"

  6. CodeGate

    Any check above 0.7 sets escalate. Otherwise plan() turns the action into UI steps: navigate, pick a category, pick a recipe, press Create.

  7. LLM CodeClick

    For each step, locate(step, frameWidth) returns screen coordinates just before the click; Claude clicks and waits about a second.

  8. 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.

  9. JevIf a step failed: recover

    Transient timing or layout issue (retry, at most twice), or an unexpected state that needs Claude?

  10. LLMIf escalated: Claude

    Inspect, decide, act, then fix the cause in a lower tier and log it.

  11. 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

EventFires whenWho handles it
idleNothing is running (a recipe ran out between wakes)LLM ticks for the next action
escalateThe last tick crossed 0.7 on a checkLLM reviews and fixes the cause
milestoneThe current milestone changedLLM saves; writes the next chapter after the last one
staleNo fresh snapshot for 30 sCode boot script, then LLM
hiddenThe game is a background tab and stallsCode focus script, then Human
hp-lowUnder 30% HP in combat (the guard should have prevented it)LLM audits the guard
loot20+ stacks waiting (the guard collects at 8)LLM audits the guard
save-oldThe cloud save is an hour oldLLM 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

QuestionTypeAsked aboutHow code uses the answer
tgt_iScore 0–4Each actionable target (up to 8)Weighted pick, plus milestone and stay bonuses
need_iScore 0–4Each activity, only when every target is blocked× (0.7 + 0.3 × relative XP rate)
do_iYes/noEach 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 JevDo the best one at ≥ 0.7
use_iScore 0–4Each sellable bank item (up to 8)Junk if use/4 ≤ 0.15, under bank pressure
check_backScore 0–3How soon to look again5 s to 10 min, capped by when the recipe runs out
eat_now, popup_choice, tutorial_readyYes/no, choiceOnly when in combat, a dialog is open, or in the tutorialEat at ≥ 0.6; a dialog choice under 0.6 confidence waits for Claude
novel_situationYes/noIs this outside the goal and playbook?> 0.7 escalates
doubt_actionYes/no, own callThe chosen action, in words> 0.7 escalates; 0.5–0.7 watch band
transient, needs_claudeYes/no, on failureA failed step and the screen stateRetry 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):

Decided by a code policy
217
23% of ticks, up from 17% as the farm and town went to code; the rest followed Jev's weighted scores
Novelty p10 / p50 / p90
0.36 / 0.46 / 0.55
843 readings; 3 above 0.7, 219 in 0.5–0.7
Doubt p10 / p50 / p90
0.24 / 0.38 / 0.53
842 readings; 4 above 0.7, 128 in 0.5–0.7
Escalations from ticks
10
doubt 4 · novelty 3 · chapter complete 3; none on day six
Most common code policiesTicks
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 plan21
Sell surplus gear under bank pressure16
Buy the next tool tier (axe, pickaxe, rod) when it costs ≤ 25% of gold10
Sell a training-bow stack once its run stops9
Buy bank slots when nearly full8
Claim mastery tokens7
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 fighting35

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.