diff --git a/op-challenger/fault/agent.go b/op-challenger/fault/agent.go
index 341f88f3aaf85ecd4128d66a438a60228fce1c77..681777aefe19ccf623b8d90eca6e0c9c41ddc6c2 100644
--- a/op-challenger/fault/agent.go
+++ b/op-challenger/fault/agent.go
@@ -2,53 +2,68 @@ package fault
 
 import (
 	"context"
-	"sync"
+	"errors"
 
 	"github.com/ethereum/go-ethereum/log"
 )
 
 type Agent struct {
-	mu        sync.Mutex
-	game      Game
 	solver    *Solver
 	trace     TraceProvider
+	loader    Loader
 	responder Responder
 	maxDepth  int
 	log       log.Logger
 }
 
-func NewAgent(game Game, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent {
+func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent {
 	return Agent{
-		game:      game,
 		solver:    NewSolver(maxDepth, trace),
 		trace:     trace,
+		loader:    loader,
 		responder: responder,
 		maxDepth:  maxDepth,
 		log:       log,
 	}
 }
 
-// AddClaim stores a claim in the local state.
-// This function shares a lock with PerformActions.
-func (a *Agent) AddClaim(claim Claim) error {
-	a.mu.Lock()
-	defer a.mu.Unlock()
-	return a.game.Put(claim)
+// Act iterates the game & performs all of the next actions.
+func (a *Agent) Act() error {
+	game, err := a.newGameFromContracts(context.Background())
+	if err != nil {
+		a.log.Error("Failed to create new game", "err", err)
+		return err
+	}
+	// Create counter claims
+	for _, claim := range game.Claims() {
+		_ = a.move(claim, game)
+	}
+	// Step on all leaf claims
+	for _, claim := range game.Claims() {
+		_ = a.step(claim, game)
+	}
+	return nil
 }
 
-// PerformActions iterates the game & performs all of the next actions.
-// Note: PerformActions & AddClaim share a lock so the responder cannot
-// call AddClaim on the same thread.
-func (a *Agent) PerformActions() {
-	a.mu.Lock()
-	defer a.mu.Unlock()
-	for _, claim := range a.game.Claims() {
-		_ = a.move(claim)
+// newGameFromContracts initializes a new game state from the state in the contract
+func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
+	claims, err := a.loader.FetchClaims(ctx)
+	if err != nil {
+		return nil, err
+	}
+	if len(claims) == 0 {
+		return nil, errors.New("no claims")
 	}
+	game := NewGameState(claims[0], uint64(a.maxDepth))
+	if err := game.PutAll(claims[1:]); err != nil {
+		return nil, err
+	}
+	return game, nil
 }
 
 // move determines & executes the next move given a claim pair
-func (a *Agent) move(claim Claim) error {
+func (a *Agent) move(claim Claim, game Game) error {
+	a.log.Info("Fetching claims")
 	nextMove, err := a.solver.NextMove(claim)
 	if err != nil {
 		a.log.Warn("Failed to execute the next move", "err", err)
@@ -62,10 +77,33 @@ func (a *Agent) move(claim Claim) error {
 	log := a.log.New("is_defend", move.DefendsParent(), "depth", move.Depth(), "index_at_depth", move.IndexAtDepth(), "value", move.Value,
 		"letter", string(move.Value[31:]), "trace_index", move.Value[30],
 		"parent_letter", string(claim.Value[31:]), "parent_trace_index", claim.Value[30])
-	if a.game.IsDuplicate(move) {
+	if game.IsDuplicate(move) {
 		log.Debug("Duplicate move")
 		return nil
 	}
 	log.Info("Performing move")
 	return a.responder.Respond(context.TODO(), move)
 }
+
+// step attempts to execute the step through the responder
+func (a *Agent) step(claim Claim, game Game) error {
+	if claim.Depth() != a.maxDepth {
+		return nil
+	}
+	a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
+
+	step, err := a.solver.AttemptStep(claim)
+	if err != nil {
+		a.log.Info("Failed to get a step", "err", err)
+		return err
+	}
+
+	a.log.Info("Performing step",
+		"depth", step.LeafClaim.Depth(), "index_at_depth", step.LeafClaim.IndexAtDepth(), "value", step.LeafClaim.Value,
+		"is_attack", step.IsAttack)
+	callData := StepCallData{
+		ClaimIndex: uint64(step.LeafClaim.ContractIndex),
+		IsAttack:   step.IsAttack,
+	}
+	return a.responder.Step(context.TODO(), callData)
+}
diff --git a/op-challenger/fault/orchestrator.go b/op-challenger/fault/orchestrator.go
index f41326fbefb140d39463c09adbb32bdb31c463fb..40b45374830187cfad5fb7f42f23f28b746a9ae2 100644
--- a/op-challenger/fault/orchestrator.go
+++ b/op-challenger/fault/orchestrator.go
@@ -2,74 +2,74 @@ package fault
 
 import (
 	"context"
-	"os"
-	"time"
 
 	"github.com/ethereum/go-ethereum/log"
 )
 
 type Orchestrator struct {
-	agents    []Agent
-	outputChs []chan Claim
-	responses chan Claim
+	agents []Agent
+	claims []Claim
+	steps  []StepCallData
+
+	// tracking when to exit
+	claimLen, stepLen, step int
 }
 
 func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator {
 	o := Orchestrator{
-		responses: make(chan Claim, 100),
-		outputChs: make([]chan Claim, len(traces)),
-		agents:    make([]Agent, len(traces)),
+		agents: make([]Agent, len(traces)),
+		claims: []Claim{root},
+		steps:  make([]StepCallData, 0),
 	}
 	log.Info("Starting game", "root_letter", string(root.Value[31:]))
 	for i, trace := range traces {
-		game := NewGameState(root, maxDepth)
-		o.agents[i] = NewAgent(game, int(maxDepth), trace, &o, log.New("role", names[i]))
-		o.outputChs[i] = make(chan Claim)
+		o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, log.New("role", names[i]))
 	}
 	return o
 }
 
 func (o *Orchestrator) Respond(_ context.Context, response Claim) error {
-	o.responses <- response
+	response.ContractIndex = len(o.claims)
+	o.claims = append(o.claims, response)
 	return nil
 }
 
-func (o *Orchestrator) Step(ctx context.Context, stepData StepCallData) error {
+func (o *Orchestrator) Step(_ context.Context, stepData StepCallData) error {
+	log.Info("Step recorded", "step", stepData)
+	o.steps = append(o.steps, stepData)
 	return nil
 }
 
-func (o *Orchestrator) Start() {
-	for i := 0; i < len(o.agents); i++ {
-		go runAgent(&o.agents[i], o.outputChs[i])
-	}
-	o.responderThread()
+func (o *Orchestrator) FetchClaims(ctx context.Context) ([]Claim, error) {
+	c := make([]Claim, len(o.claims))
+	copy(c, o.claims)
+	return c, nil
 }
 
-func runAgent(agent *Agent, claimCh <-chan Claim) {
+func (o *Orchestrator) Start() {
 	for {
-		agent.PerformActions()
-		// Note: Should drain the channel here
-		claim := <-claimCh
-		_ = agent.AddClaim(claim)
-
+		for _, a := range o.agents {
+			_ = a.Act()
+		}
+		if o.shouldExit() {
+			log.Info("exiting")
+			return
+		}
 	}
 }
 
-func (o *Orchestrator) responderThread() {
-	timer := time.NewTimer(200 * time.Millisecond)
-	defer timer.Stop()
-	for {
-		select {
-		case resp := <-o.responses:
-			timer.Reset(200 * time.Millisecond)
-			for _, ch := range o.outputChs {
-				// Copy it. Should be immutable, but be sure.
-				resp := resp
-				ch <- resp
-			}
-		case <-timer.C:
-			os.Exit(0)
-		}
+func (o *Orchestrator) shouldExit() bool {
+	cl := o.claimLen
+	sl := o.stepLen
+
+	o.claimLen = len(o.claims)
+	o.stepLen = len(o.steps)
 
+	noProgress := o.claimLen == cl && o.stepLen == sl
+	if noProgress {
+		o.step = o.step + 1
+	} else {
+		o.step = 0
 	}
+	return noProgress && o.step == 1
 }
diff --git a/op-challenger/fault/solver.go b/op-challenger/fault/solver.go
index ea57e5f75489f709df686ce6a9097b824bef85ee..607b765fbb8a28925110028a20954c88b83fbe60 100644
--- a/op-challenger/fault/solver.go
+++ b/op-challenger/fault/solver.go
@@ -31,14 +31,13 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) {
 }
 
 type StepData struct {
-	LeafClaim  Claim
-	StateClaim Claim
-	IsAttack   bool
+	LeafClaim Claim
+	IsAttack  bool
 }
 
 // AttemptStep determines what step should occur for a given leaf claim.
 // An error will be returned if the claim is not at the max depth.
-func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) {
+func (s *Solver) AttemptStep(claim Claim) (StepData, error) {
 	if claim.Depth() != s.gameDepth {
 		return StepData{}, errors.New("cannot step on non-leaf claims")
 	}
@@ -46,20 +45,9 @@ func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) {
 	if err != nil {
 		return StepData{}, err
 	}
-	var selectorFn func(Claim) (Claim, error)
-	if claimCorrect {
-		selectorFn = state.PostStateClaim
-	} else {
-		selectorFn = state.PreStateClaim
-	}
-	stateClaim, err := selectorFn(claim)
-	if err != nil {
-		return StepData{}, err
-	}
 	return StepData{
-		LeafClaim:  claim,
-		StateClaim: stateClaim,
-		IsAttack:   claimCorrect,
+		LeafClaim: claim,
+		IsAttack:  claimCorrect,
 	}, nil
 }
 
diff --git a/op-challenger/fault/solver_test.go b/op-challenger/fault/solver_test.go
index d4f9b6804021e71b35baac7ac3e5c3ad1d706739..1a70d386aa35aceca446ec6bd21a4585e8cb2125 100644
--- a/op-challenger/fault/solver_test.go
+++ b/op-challenger/fault/solver_test.go
@@ -89,12 +89,11 @@ func TestAttemptStep(t *testing.T) {
 	require.NoError(t, g.Put(middle))
 	require.NoError(t, g.Put(bottom))
 
-	step, err := solver.AttemptStep(bottom, g)
+	step, err := solver.AttemptStep(bottom)
 	require.NoError(t, err)
 	require.Equal(t, bottom, step.LeafClaim)
-	require.Equal(t, middle, step.StateClaim)
 	require.True(t, step.IsAttack)
 
-	_, err = solver.AttemptStep(middle, g)
+	_, err = solver.AttemptStep(middle)
 	require.Error(t, err)
 }