A perpetual autonomous-agent control loop for Go: a guarded finite-state machine driving Plan -> Execute -> Evaluate -> Improve, wrapped with a threshold-driven budget enforcer that can force the loop into cooldown mid-cycle when spend runs hot.
Zero dependencies outside the Go standard library.
Long-running agent loops (an agent that plans, executes, evaluates its own output, and improves before looping back) need two things that are easy to get wrong by hand:
- Guarded transitions. Not every state can go to every other state.
agentloopencodes the valid transition table once and rejects anything else, so a bug elsewhere in your program can't accidentally skip evaluation or resume from a terminal state. - Budget-driven state coercion. An autonomous loop that spends money
(LLM tokens, compute, API calls) needs a way to notice it's running hot
and change its own behavior — not just log a warning after the fact.
agentloop'sBudgetEnforcermaps spend percentage to an action (continue/warn/cooldown/escalate/stop), andControllerwires that straight into a forcedCooldowntransition.
StateMachine— a thread-safe FSM overLoopState(Idle,Planning,Executing,Evaluating,Improving,Complete,Cooldown,Drain,Exit). Holds the standard transition table, records a full history of transitions, and notifies anOnChangecallback after every successful move.BudgetEnforcer— tracks cumulative spend against a global cap and reports the highest-priorityBudgetActioncrossed: 80% ->warn, 90% ->cooldown, 95% ->escalate, 100% ->stop. A zero or negative budget means "unlimited."Controller— the high-level API. Wires per-state callbacks (OnEnterEvaluate,OnEnterImprove,OnPhaseChange,OnError) onto the state machine, exposesStart/Advance/Complete/Cancelfor driving the happy path and graceful shutdown, and callsRecordSpendto feed the budget enforcer and forceCooldownwhen thresholds are breached.
go get github.com/hairglasses-studio/agentloopc := agentloop.NewController(100.0) // $100 budget
c.OnEnterEvaluate(func() error {
// run your evaluation step; return an error to report via OnError
return nil
})
c.OnEnterImprove(func() error {
// run your improvement step
return nil
})
c.OnPhaseChange(func(phase string) {
fmt.Println("now in phase:", phase)
})
c.OnError(func(state agentloop.LoopState, err error) {
log.Printf("callback failed in %s: %v", state, err)
})
if err := c.Start(); err != nil { // Idle -> Planning
log.Fatal(err)
}
for c.State() != agentloop.StateComplete {
// do the work for the current phase...
c.RecordSpend(1.25) // may force a transition into Cooldown
if c.State() == agentloop.StateCooldown {
// back off, then recover into Planning
if err := c.Start(); err != nil {
log.Fatal(err)
}
continue
}
if err := c.Advance(); err != nil {
break
}
}See example/main.go for a complete runnable program
that drives the FSM through a mock plan/execute/evaluate/improve cycle,
including a forced cooldown once the budget threshold is crossed.
go build ./...
go test ./...