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

152 lines
5.1 KiB
Markdown

# 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
```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 <arg>` format