Skip to main content

Overview

The agent loop is the heart of klaw. It orchestrates the conversation between user input, LLM decisions, and tool execution, continuing until a task is complete.

The Loop

┌────────────────────────────────────────────────────────────────┐
│                        AGENT LOOP                               │
│                                                                 │
│  ┌──────────┐    ┌──────────┐    ┌──────────┐    ┌──────────┐ │
│  │ Receive  │ → │   LLM    │ → │  Execute │ → │  Format  │  │
│  │  Input   │    │ Decision │    │  Tools   │    │ Results  │  │
│  └──────────┘    └──────────┘    └──────────┘    └──────────┘  │
│       ▲                                               │        │
│       │                                               │        │
│       └───────────────────────────────────────────────┘        │
│                    (Repeat until complete)                     │
└────────────────────────────────────────────────────────────────┘

Step-by-Step Flow

1

Receive Input

User sends a message through a channel (CLI, Slack, API).
msg, _ := channel.Receive(ctx)
history = append(history, Message{Role: "user", Content: msg})
2

Send to LLM

Agent sends conversation history to the LLM provider.
events, _ := provider.Stream(ctx, history)
3

Process Stream

Agent collects streaming events: text chunks, tool calls, stop signals.
for event := range events {
    switch event.Type {
    case "text":
        channel.Send(ctx, Message{Content: event.Text, Partial: true})
    case "tool_use":
        toolCalls = append(toolCalls, event.ToolCall)
    case "stop":
        break
    }
}
4

Execute Tools

If the LLM requested tools, execute them with timeout.
for _, call := range toolCalls {
    ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
    result, _ := tools.Execute(ctx, call.Name, call.Input)
    cancel()

    toolResults = append(toolResults, result)
}
5

Format Results

Tool results are formatted and added to history.
history = append(history, Message{
    Role: "assistant",
    ToolCalls: toolCalls,
})

for i, result := range toolResults {
    history = append(history, Message{
        Role: "tool",
        ToolCallID: toolCalls[i].ID,
        Content: result,
    })
}
6

Loop or Complete

If tools were called, loop back to send results to LLM. If no tools, mark response as complete.
if len(toolCalls) > 0 {
    continue // Back to Step 2
}

channel.Send(ctx, Message{Content: text, Done: true})

Stream Events

The provider returns a stream of events:
Event TypeDescription
textText chunk to stream to user
tool_useLLM wants to call a tool
tool_resultResult of a tool call
errorError occurred
stopGeneration complete

Tool Execution

Timeout

Each tool has a 2-minute timeout by default:
ctx, cancel := context.WithTimeout(ctx, 2*time.Minute)
defer cancel()

result, err := tool.Execute(ctx, input)

Result Formatting

Tool results are formatted for display:
╭──────────────────────────────────────────╮
│ Tool: bash                               │
│ Command: git status                      │
├──────────────────────────────────────────┤
│ On branch main                           │
│ Your branch is up to date                │
│ nothing to commit, working tree clean    │
╰──────────────────────────────────────────╯

Error Handling

Tool errors are captured and fed back to the LLM:
result, err := tool.Execute(ctx, input)
if err != nil {
    result = fmt.Sprintf("Error: %v", err)
}
// LLM sees the error and can adjust strategy

Conversation History

Each agent maintains conversation history:
type Agent struct {
    history map[string][]Message  // Per-conversation
}
For thread-aware channels (Slack), each thread has its own history.

Memory Integration

Before each LLM call, workspace context is loaded:
System Prompt = Base Instructions
             + SOUL.md content
             + AGENTS.md content
             + TOOLS.md content
             + Skill prompts

Performance Considerations

FactorImpactMitigation
Tool execution timeBlocks loopTimeout, async tools
Large outputsToken costOutput truncation
Many tool callsLatencyBatching where possible
Long historyContext limitsHistory pruning

Next Steps