Experimental Research fork — APIs and behaviour may change without notice. Read disclaimer →

The agent loop is picobot's core — a ReAct (Reason + Act) cycle that alternates between LLM reasoning and tool execution.

How It Works

Each call to process_direct() runs this cycle:

  1. Build system prompt from workspace templates + KB context + TODO + skills
  2. Send messages to the LLM via LiteLLM
  3. If tool calls returned, execute them, feed results back to the LLM, go to step 2
  4. If text returned, that's the response — save session and exit
  5. If budget exceeded, compress history and continue

The loop runs until the LLM produces a final text response, hits the tool iteration limit (maxToolIterations, default 20), or exhausts the token budget.

Sessions

Each invocation creates an episodic session by default. Sessions persist as JSONL files in ~/.picobot/sessions/.

Start fresh (default):

picobot agent -m "hello"

Resume the most recent session:

picobot agent --session latest

Resume a specific session:

picobot agent --session abc123

Session IDs are channel:uuid internally (e.g., cli:a1b2c3d4-20240222-144530). The cli: prefix is added automatically if you pass a bare ID.

History Management

Sessions can grow large. Two mechanisms keep them bounded:

  • maxHistoryMessages (default 50): When saving, only the most recent N messages are retained.
  • History trimming: Keeps the system message + first user/assistant exchange (for coherence) + the most recent messages that fit.

Streaming

picobot agent --stream

Tokens appear as the LLM generates them. Combine with --show-tools to see tool calls inline:

picobot agent --stream --show-tools

In streaming mode, tool call arguments are reconstructed from deltas as they arrive. You'll see each tool call start and complete in real time.

Token Budget

The tokenBudget config (default 128,000) controls when history compression kicks in. The budget system:

  1. Estimates tokens using chars/4 approximation (fast, no tokenizer dependency)
  2. Triggers at 80% of budget — when estimated tokens exceed this threshold
  3. Compresses by keeping system message + first 2 user/assistant exchanges + as many recent messages as fit in 75% of budget
  4. Truncates tool results to 8,000 characters to prevent oversized responses from consuming the context window

This means the agent can run indefinitely without hitting context limits. Early context is preserved for coherence, and the most recent exchanges are always available.

Tuning the Budget

{
  "agents": {
    "defaults": {
      "tokenBudget": 128000,
      "maxHistoryMessages": 50
    }
  }
}
  • Lower budget (e.g., 32000): More aggressive compression, lower cost per session
  • Higher budget (e.g., 200000): More context retained, better for complex multi-step work
  • Lower maxHistoryMessages: Sessions save faster, less disk usage

Error Recovery

The loop handles three categories of failure:

Transient LLM Errors

Rate limits, timeouts, and connection errors are retried once with a 2-second backoff. If the retry also fails, the error propagates.

Detected patterns: rate_limit, timeout, connection, 429, 503, overloaded.

Consecutive Errors

If the LLM fails 3 times in a row (across turns, not retries), the loop stops. This prevents infinite loops when the model is consistently failing.

Stuck Detection

If the agent calls the same tool with identical arguments twice in a row, it's stuck. The loop injects a system message:

"You appear stuck — you just called the same tool with the same arguments. Try a different approach."

This nudges the LLM to try something different rather than repeating the same action.

Structured Tracing

The Tracer class records per-turn metrics:

  • Turn number, timing (start/end/duration)
  • Token counts (prompt, completion, total)
  • Cost estimate (from LiteLLM)
  • Tool calls with individual timing
  • Final outcome

Traces are stored as JSONL in ~/.picobot/data/traces/traces.jsonl. Use --logs to see runtime log output during a session.

CLI Reference

Flag Description
-m, --message TEXT Send a single message (non-interactive)
-s, --session ID Session ID to resume (latest for most recent)
--stream / --no-stream Stream LLM tokens live
--show-tools / --no-show-tools Show tool calls inline
--markdown / --no-markdown Render markdown in output
--logs / --no-logs Show runtime logs
--max-tokens INT Override max tokens per LLM response
--json Use JSON-over-stdio protocol (see JSON Channel)