Skip to content

Agents & Subagents

Krill's agent system has two levels: the main agent that handles user conversations, and subagents that run background tasks in parallel.

Main Agent

The main agent is configured via the Agent struct (built from krill.toml at startup). It owns:

  • An LLM provider (OpenAI or Gemini)

  • A tool registry (all registered tools)

  • Session state (history, memory, cancel scope)

  • Hooks (on_tool_call, on_tool_result, should_interrupt, on_turn_start, on_turn_end)

Each inbound message is processed as a turn: load history, call LLM, run tool loop (up to max_tool_iterations), save history, send response. Turns are FIFO within a session but concurrent across sessions.

Tool Loop

The core agentic loop in tool_loop.jl:

  1. Call the LLM with the current message + history + tools

  2. If the LLM returns tool calls, dispatch them via the tool registry

  3. Append tool results to the conversation

  4. Repeat from step 1 until the LLM responds with text only, or max_tool_iterations is reached

Tool calls within a single LLM response are executed sequentially. Results are cached by hash(name, arguments) within a turn to avoid duplicate dispatches.

Cooperative Cancellation

Users can send /stop to cancel a running turn. This sets a flag in SessionCancelScope which is checked between tool iterations. If set, the loop exits with whatever text the LLM has produced so far.

Subagents

Subagents are background tasks spawned by the main agent via the spawn tool. Each subagent:

  • Gets its own LLM conversation (fresh history, no shared state)

  • Runs in a Julia @spawn thread

  • Has access to the same tools as the parent (except spawn, cron, and message)

  • Reports results back to the originating chat when complete

Spawning Flow

User: "research X in the background"
  → Main agent calls spawn(task="research X")
  → SubagentManager checks concurrent limit (default 5)
  → Creates SubagentTask with unique ID
  → Spawns Julia thread running _run_subagent!()
  → Returns immediately: "Subagent started (id: abc123)"

Subagent Execution

_run_subagent!(mgr, task)
  → processor_factory() creates a fresh LLM processor
  → Builds minimal InboundMessage with task description
  → Calls processor(msg, empty_history)
  → LLM runs its own tool loop (up to max_iterations)
  → Result text extracted
  → _finish_task!() records status + result
  → _announce_result!() injects result as InboundMessage
    back to the origin session via the message hub
  → Main agent processes the announcement and relays to user

Subagent Tool Access

Subagents get the same tool set as the parent agent:

  • File operations (read_file, write_file, edit_file, etc.)

  • Web fetch (web_fetch)

  • Shell execution (exec, if enabled)

  • GitHub CLI (github)

  • Google Workspace (google_workspace, if enabled)

  • Coding agents (claude_code, codex, if enabled)

  • Provider-native search (OpenAI web_search / Gemini googleSearch)

  • MCP tools (from connected MCP servers)

  • Skills (read_skill)

Subagents do not get:

  • spawn / spawn_list / spawn_cancel (no recursive spawning)

  • cron_add / cron_list / cron_remove (no scheduling)

  • message (no direct messaging — results are announced via the hub)

Managing Subagents

The LLM can use these tools to manage running subagents:

  • spawn_list — shows all tasks with status and elapsed time

  • spawn_cancel — cancels a running task by ID (sends InterruptException to the thread)

On shutdown, all running subagents are cancelled automatically.

Configuration

Subagent behavior is controlled via SubagentConfig:

ParameterDefaultDescription
enabletrueEnable/disable subagent spawning
max_concurrent5Maximum running subagents at once
max_iterations15Tool loop iterations per subagent