Files
2026-02-23 11:00:20 -05:00

5.1 KiB

agent0 Architecture Outline

Core Overview

agent0 is an agentic TUI built with Babashka (Clojure) that interacts with local LLMs via Ollama. It features an Elm-architecture UI with background agent processing.


File Structure & Responsibilities

File Responsibility
app.clj TUI layer - Elm architecture, event handling, rendering
core.clj Agent brain - LLM calls, tool execution, async loop
context.clj Project context loading, skill parsing/expansion
markdown.clj Markdown → ANSI-styled terminal output
web_search.clj Web research subagent configuration
clojuredocs.clj ClojureDocs integration (not shown, imported)

Data Flow

User Input → Skill Expansion → Agent Loop → LLM → Tools → Events → UI
  1. Input → Optional skill expansion (/name args)
  2. Conversation → Added to history
  3. Agent Loop (async future) → Calls LLM, executes tools, appends results
  4. Events → Pushed to event queue every 100ms
  5. UI → Polls queue and re-renders

Key Functions

app.clj (TUI Layer)

Function Purpose
view Elm view - renders state as hiccup UI
update-fn Elm update - handles events, user input, agent polling
process-agent-event Pushes LLM responses to messages list
format-messages Converts messages to display lines with ANSI styling
wrap-line / wrap-text Word-wrap with ANSI awareness
-main Entry point - parses args, loads context/skills, starts TUI

core.clj (Agent Brain)

Function Purpose
run-agent-loop! Main async loop - iterates: call LLM → execute tools → repeat (max 50)
call-llm* HTTP POST to Ollama /api/chat
call-llm Wrapper with tool definitions
execute-tool Runs tool function, extracts diff for UI
tool-call-label Human-readable label for tool calls (displayed as "⚙")
detect-stuck-loop Detects infinite loops (exact repeats + research loops)
run-subagent! Subagent execution (e.g., web_search)
delegate Tool to invoke subagents
skills-tool Tool for listing/running skills
Session persistence: save-session!, load-session, new-session-id
Tools: read-file, list-files, edit-file, create-file, run-shell-command, glob-files, grep-files File & shell operations

context.clj (Context & Skills)

Function Purpose
load-project-context Loads .agent0/context.md, CLAUDE.md, etc.
load-skills Loads skills from ~/.claude/skills/, ~/.config/agent0/skills.md, .agent0/skills.md
parse-skills Parses /name <arg> templates with {param} substitution
expand-skill Expands skill invocations like /test --watch
format-skill-list Formats skill list for display

markdown.clj (Markdown Rendering)

Function Purpose
render-markdown Converts markdown to ANSI-styled lines
apply-inline Bold, italic, strikethrough, links
protect-code-spans / restore-code-spans Preserves inline code
wrap-words ANSI-aware word wrapping

Event Types

{:type :text    :content "..."}   ; Assistant response
{:type :tool    :label "..."}     ; Tool being called (displayed as "⚙")
{:type :diff    :content "..."}   ; File edit preview (diff format)
{:type :error   :message "..."}   ; Error message
{:type :done    :conversation [...]} ; Loop finished

Tool Pattern

Tools that modify files return {:message ".." :diff ".."}:

  • :message → sent to LLM as tool result
  • :diff → pushed as separate :diff event to UI

Agent Loop Algorithm

1. Call LLM with conversation + tool definitions
2. If tool_calls in response:
   a. Push tool labels to UI
   b. Execute each tool function
   c. Push diffs to UI (if any)
   d. Append tool results to conversation
   e. Repeat (stuck-loop detection at ~3 repeats)
3. If no tool_calls:
   a. Push final assistant text
   b. Signal :done

Stuck-loop detection:

  • Exact repeat: same tool calls 3x in a row → hard stop
  • Research loop: web_search called 3+ times with varying args → inject system nudge, stop at 6

State Management

Model state (UI):

{:messages [...]          ; Display messages
 :input ""              ; User input field
 :conversation [...]    ; LLM conversation history
 :event-queue (atom []) ; Background-to-UI events
 :session-id ...        ; For persistence
 :agent-running? bool   ; Spinner state
 :agent-handle ...      ; Future + cancel atom
 :skills {...}          ; Available skills}

Sessions saved to: ~/.local/share/agent0/sessions/{uuid}.edn
Logs saved to: ~/.local/share/agent0/logs/agent-{timestamp}.log


Key Patterns

  1. Async agent loop returns {:future f :cancel! atom} for interruption
  2. TUI uses clojure-tui hiccup-like syntax with ANSI escape codes
  3. Polling UI checks event queue every 100ms (drains and processes)
  4. Dynamic binding *log-file* passes log context to subagents
  5. Skills use {param} substitution from /name <arg> format