DEV Community

Mukunda Rao Katta
Mukunda Rao Katta

Posted on

I replaced 200 lines of ad-hoc state management in my Hermes agent with one object.

Hermes Agent Challenge Submission: Build With Hermes Agent

This is a submission for the Hermes Agent Challenge.

My Hermes research agent was tracking state across 20+ variables. Turn counter. Running cost. Message history. Sub-tasks done. Sub-tasks pending. Errors per tool. Each was a standalone variable at the top of the loop, updated individually, saved separately, and restored manually after a crash.

By turn 47, the state management was 200 lines of ad-hoc code spread across the loop. I replaced it with one object.

One object for all agent state

from agent_state_bag import StateBag

state = StateBag({
    "turn": 0,
    "cost_usd": 0.0,
    "messages": [],
    "sub_tasks_done": [],
    "errors": 0,
})

for turn in range(1, 50):
    state["turn"] = turn
    state.increment("cost_usd", 0.05)
    state.increment("errors") if tool_failed else None

    response = call_llm(state["messages"])
    state["messages"].append({"role": "assistant", "content": response.text})
Enter fullscreen mode Exit fullscreen mode

StateBag is a dict wrapper with extra features. It passes through all the dict methods you expect, plus the things a long-running agent actually needs.

Snapshot before each turn

state.push_turn()   # saves current state to history

# If the turn fails badly, restore to before this turn
state.reset_to(state.last_snapshot())
Enter fullscreen mode Exit fullscreen mode

At the start of each turn, push a snapshot. If something goes catastrophically wrong mid-turn, you can roll back to the clean state before that turn started.

Diff what changed

snap = state.snapshot()

# ... agent does stuff ...

changes = state.diff(snap)
# {"cost_usd": (0.15, 0.20), "messages": (old_list, new_list), "sub_tasks_done": ([], ["task1"])}
Enter fullscreen mode Exit fullscreen mode

I log the diff to my trace file at the end of each turn. When reviewing a run, I can see exactly what each turn changed — without comparing full state snapshots manually.

Numeric helpers

state.increment("turn")               # += 1
state.increment("cost_usd", 0.05)     # += 0.05
state.decrement("retries_left")       # -= 1
Enter fullscreen mode Exit fullscreen mode

Returns the new value. Initializes to 0 if the key is missing.

Persist to disk

state.save("state.json")
Enter fullscreen mode Exit fullscreen mode
state = StateBag.load("state.json")
Enter fullscreen mode Exit fullscreen mode

Plain JSON. Grep-able, inspectable, resumable. I save state after every successful turn. On crash, StateBag.load restores exactly where I was.

Turn history

state.push_turn()    # checkpoint this turn
# ...10 more turns...
state.history        # list of all saved snapshots
state.turn_count     # 11
state.last_snapshot() # most recent snapshot
Enter fullscreen mode Exit fullscreen mode

The history grows across the run. After the run, I can replay it to see how state evolved turn by turn.

Merge from another source

# Worker agent's output state
state.merge(worker_output)   # other wins on conflict; deep-copied
Enter fullscreen mode Exit fullscreen mode

In my multi-agent setup, workers return their results as dicts. The supervisor merges them into its own state.

Zero dependencies

Standard library only: json, copy, dataclasses, typing. No third-party packages.

pip install agent-state-bag
Enter fullscreen mode Exit fullscreen mode

Repo: https://github.com/MukundaKatta/agent-state-bag

Top comments (0)