# pkg/agent > The main loop that runs an LLM agent with guardrails. ## Responsibility Coordinate the iterative cycle between the LLM and tool execution: ``` while iteration < MaxIterations: response = llm.Generate(messages, tools) if no tool calls: return response for tool_call in response.ToolCalls: if needs_approval: ask_user() result = execute(tool_call) append tool result to messages ``` ## Public API ```go type Loop interface { Run(ctx context.Context, input string, history ...llm.Message) (Response, error) RunStream(ctx context.Context, input string, history ...llm.Message) iter.Seq2[Chunk, error] } type Config struct { LLM llm.LLMClient Persona persona.Persona Tools tools.Registry Sandbox Sandbox MaxIters int Approver Approver // nil = auto-approve all OnIteration func(Iteration) // observability hook } type Response struct { Content string ToolCalls []tools.Call Iterations int Duration time.Duration TokenUsage llm.TokenUsage } ``` ## Guarantees - **Termination**: Always terminates (max iterations, error, or final response) - **Idempotency**: Re-running with the same input produces the same output (given the same LLM) - **Observability**: Each iteration emits an OpenTelemetry span - **Approval**: Destructive tools (`Ask` permission) require confirmation ## See also - [pkg/tools](../tools/README.md) — Tool execution - [pkg/llm](../llm/README.md) — LLMClient interface - [pkg/persona](../persona/README.md) — Persona assembly