5.1 KiB
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
- Input → Optional skill expansion (
/name args) - Conversation → Added to history
- Agent Loop (async future) → Calls LLM, executes tools, appends results
- Events → Pushed to event queue every 100ms
- 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:diffevent 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_searchcalled 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
- Async agent loop returns
{:future f :cancel! atom}for interruption - TUI uses clojure-tui hiccup-like syntax with ANSI escape codes
- Polling UI checks event queue every 100ms (drains and processes)
- Dynamic binding
*log-file*passes log context to subagents - Skills use
{param}substitution from/name <arg>format