rony-llm-agent/pkg/agent
Victor Vargas 2e23216932 fix(agent): last-iteration false failure, cached schemas, usage in tool rounds
- Run() reported "max iterations reached" even when a valid final answer
  arrived exactly on the last allowed iteration, throwing the response
  away; a completed flag now distinguishes success from budget exhaustion.
- Tool schemas are marshaled once per Run/RunStream instead of once per
  loop iteration — they never change between iterations.
- RunStream's content gate (which hides raw deltas during a tool-call
  round) also swallowed that round's token usage, so callers only ever saw
  the final round's count and context tracking lagged exactly when the
  context grew fastest. Usage is now forwarded in its own chunk.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 16:14:15 -07:00
..
integration_test.go Fix tool call tracking and streaming assembly for all providers 2026-07-08 16:11:57 -07:00
loop.go fix(agent): last-iteration false failure, cached schemas, usage in tool rounds 2026-07-12 16:14:15 -07:00
loop_test.go fix(agent): RunStream never surfaced tool calls to callers 2026-07-10 00:04:44 -07:00
README.es.md feat: add history messages, reasoning content, and adaptive language support 2026-07-05 16:17:37 -07:00
README.md feat: add history messages, reasoning content, and adaptive language support 2026-07-05 16:17:37 -07:00
subagent.go feat(agent): add SubAgent runtime for nested, specialized agent loops 2026-07-09 12:08:32 -07:00
subagent_test.go feat(agent): add SubAgent runtime for nested, specialized agent loops 2026-07-09 12:08:32 -07:00

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

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