# 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 ` 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 ```clojure {: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):** ```clojure {: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 ` format