Skip to content

Go back or try another path

Two tools: snapshot and restore go back to an earlier point. fork keeps the State as it is and tries something else on a copy. Use them between turns, not while the model is being waited on or tool calls are pending.

Go back

state.snapshot() returns a StateSnapshot: the State's value at that moment, frozen, so it never changes. Take one before something you may want to undo, and state.restore(snapshot) goes back:

from alpineagents import Agent, Message, State

agent = Agent(model="claude-sonnet-5")

state = State(messages=[Message.user("Fix calc.py")])
agent.run(state)

before = state.snapshot()  # a frozen value: it never changes
state.add_message(Message.user("Rewrite it as a class"))
agent.run(state)

if "class" not in state.answer:
    state.restore(before)  # the State is back where the snapshot was taken
    print(len(state.history) > len(before.history))  # True: history keeps everything, and says it went back
  1. before is the State as it was after the first run.
  2. The second run adds to state.
  3. restore(before) puts the messages back. If before was taken from another State, or while calls were pending, restore raises ValueError.

What goes back and what does not:

Part After restore
messages, turn, extra_data, finished, answer, stopped What they were
usage Unchanged: the tokens were spent
history Unchanged, plus one context_change entry with kind == "restore". A store saves the going back like any other step

A snapshot lives in memory. To keep one, keep what a store keeps, its history: State(history=snapshot.history) rebuilds the same value, in this process or another.

Try another path

state.fork() makes a new State with the same history and a new id, bound to no store and to no running Agent:

from alpineagents import Agent, Message, State

agent = Agent(model="claude-sonnet-5")

state = State(messages=[Message.user("Fix calc.py")])
agent.run(state)

attempt = state.fork()  # the same content, a new id, nothing shared
attempt.add_message(Message.user("Rewrite it as a class"))
agent.run(attempt)

print(state.answer)  # unchanged by the attempt
print(attempt.answer)
  1. attempt starts as a copy of state. They share nothing.
  2. The new message and the run change only attempt.
  3. state.answer is still the first answer.

  4. With Agent(store=...), the fork is saved under its own id the first time the Agent runs it.

  5. A fork of a finished State is finished too. To continue from its messages, start State(messages=state.messages).
  6. To use a snapshot of another State, build one from it with State(history=snapshot.history).
  7. fork().snapshot() == state.snapshot(): equal histories give equal snapshots.
  8. fork() raises ValueError while the model is being waited on. Fork before think, or after it returns, for example in Reporter.on_think_end.