# Lilith Agent — Architecture
Three views: **system** (entry → graph → outputs), **ReAct graph** (state machine), **tool belt** (tool taxonomy + dependencies).
## System overview
```mermaid
flowchart TB
subgraph Entry["Entry points"]
TUI["tui.py
(lilith CLI)"]
CLI["scripts/dev_run_gaia.py
(batch runner)"]
APP["app.py
(Gradio · HF Space)"]
end
subgraph Config["Config & models"]
CFG["config.py
Config.from_env()"]
MDL["models.py
cheap / strong / extra_strong
+ NoThink / Retry wrappers"]
ENV[".env
API keys · provider+model · caveman"]
ENV --> CFG --> MDL
end
subgraph Core["ReAct core"]
BUILD["app.py :: build_react_agent(cfg)"]
GRAPH["LangGraph
StateGraph(AgentState)"]
RUN["runner.py
run_agent_on_questions()"]
BUILD --> GRAPH
RUN --> GRAPH
end
subgraph Data["GAIA data sources"]
DS["gaia_dataset.py
GaiaDatasetClient
(HF dataset)"]
SCORE["ScoringApiClient
(agents-course-unit4-
scoring.hf.space)"]
end
subgraph Obs["Observability"]
LOG[".lilith/session-*.log"]
TRACE[".lilith/session-*.jsonl
JsonlTraceCallback"]
ARIZE["Arize AX
(optional)"]
LS["LangSmith
(optional)"]
end
TUI --> BUILD
TUI --> Obs
CLI --> BUILD
CLI --> RUN
CLI --> DS
APP --> BUILD
APP --> RUN
APP --> SCORE
MDL --> BUILD
GRAPH --> Obs
CKPT[".checkpoints/.json"]
RUN --> CKPT
```
## ReAct graph (state machine)
```mermaid
stateDiagram-v2
[*] --> model
model --> tools: AIMessage has tool_calls
AND iterations < limit
AND calls < BUDGET_HARD_CAP (25)
model --> fail_safe: iterations ≥ recursion_limit − 2
OR calls ≥ BUDGET_HARD_CAP
model --> [*]: no tool_calls (final answer)
tools --> model: results appended as ToolMessages
fail_safe --> [*]: emergency summary
note right of model
compact old ToolMessages
(keep last 4 verbatim,
truncate older to 300 chars)
inject BUDGET WARNING at 15 calls
apply caveman prompt wrapper
end note
note left of tools
per-call guards:
1. exact (name,args) dedup
2. semantic dedup (Jaccard ≥ 0.5) for tavily
3. per-tool error cooldown (3 fails → freeze)
end note
```
## Tool belt
```mermaid
flowchart LR
subgraph Web["Web & search"]
WS["web_search
(DDG → Tavily fallback)"]
FU["fetch_url
(trafilatura)"]
end
subgraph Code["Code & files"]
RP["run_python
(sandboxed subprocess)"]
RF["read_file"]
LS_T["ls · grep · glob_files · write_file"]
end
subgraph Media["Media"]
AUD["transcribe_audio
(faster-whisper)"]
PDF["inspect_pdf"]
VIS["inspect_visual_content
(Gemini → FAL → cross-provider)"]
YT_T["youtube_transcript"]
YT_F["youtube_frame_at
(ffmpeg)"]
end
subgraph Academic["Academic"]
AX["arxiv_search"]
CR["crossref_search"]
CJ["count_journal_articles"]
FE["filter_entities"]
end
subgraph Plan["Planning"]
WT["write_todos"]
MD["mark_todo_done"]
end
REG["tools/__init__.py
build_tools(cfg)"]
REG --> Web
REG --> Code
REG --> Media
REG --> Academic
REG --> Plan
VIS -. fallback chain .-> VIS
YT_F --> VIS
CR --> FE
```
## Key sources
| Concern | File |
|---|---|
| ReAct graph + routing + dedup | [src/lilith_agent/app.py](src/lilith_agent/app.py) |
| Batch runner + checkpoints | [src/lilith_agent/runner.py](src/lilith_agent/runner.py) |
| Config loader | [src/lilith_agent/config.py](src/lilith_agent/config.py) |
| Model provider factory | [src/lilith_agent/models.py](src/lilith_agent/models.py) |
| Tool registry | [src/lilith_agent/tools/__init__.py](src/lilith_agent/tools/__init__.py) |
| Logging + Arize + JSONL trace | [src/lilith_agent/observability.py](src/lilith_agent/observability.py) |
| HF dataset client | [src/lilith_agent/gaia_dataset.py](src/lilith_agent/gaia_dataset.py) |
| Gradio Space entry | [app.py](app.py) |
| Interactive TUI | [src/lilith_agent/tui.py](src/lilith_agent/tui.py) |
| GAIA batch CLI | [scripts/dev_run_gaia.py](scripts/dev_run_gaia.py) |