# Architecture > Detailed architecture documentation for Deep Researcher Agent. ## System Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ Deep Researcher Agent │ │ │ │ ┌─────────────┐ │ │ │ config.yaml │──→ Configuration for all components │ │ └─────────────┘ │ │ │ │ ┌──────────── Core Loop (loop.py) ────────────────────┐ │ │ │ │ │ │ │ ┌───────┐ ┌─────────┐ ┌─────────┐ │ │ │ │ │ THINK │───→│ EXECUTE │───→│ REFLECT │──→ repeat │ │ │ │ └───┬───┘ └────┬────┘ └────┬────┘ │ │ │ │ │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ │ │ ┌───────────────────────────────────────┐ │ │ │ │ │ Agent Dispatcher (agents.py) │ │ │ │ │ │ │ │ │ │ │ │ Leader ──→ Idea / Code / Writing │ │ │ │ │ └───────────────────────────────────────┘ │ │ │ │ │ │ │ │ │ │ │ ↓ ↓ ↓ │ │ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │ │ │ Memory │ │ Monitor │ │ Tools │ │ │ │ │ │ Manager │ │ (Zero$) │ │ Registry │ │ │ │ │ └──────────┘ └──────────┘ └──────────┘ │ │ │ └──────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────── GPU Layer ──────────────────────────────┐ │ │ │ detect.py │ keeper.py │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ ┌──────────── Skills Layer ───────────────────────────┐ │ │ │ daily-papers │ paper-analyze │ conf-search │ report │ │ │ └─────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ## Component Details ### 1. Core Loop (`core/loop.py`) The main orchestrator. Runs the THINK → EXECUTE → REFLECT cycle indefinitely. **Key design decisions:** - **Signal handling**: SIGTERM/SIGINT trigger graceful shutdown - **Cycle counter**: Persisted to `.cycle_counter` file (survives restarts) - **Smart cooldown**: Polls every N seconds instead of fixed sleep - **Directive consumption**: Human directives are archived after reading (no re-reads) - **Error backoff**: Doubles cooldown after errors to prevent burn loops ### 2. Agent Dispatcher (`core/agents.py`) **Leader-Worker pattern** where: - Leader persists conversation within a cycle (for coherent multi-step reasoning) - Workers are stateless (each dispatch is independent) - Only one worker runs at a time **Why this works:** - Leader sees the full picture without re-reading everything each step - Workers are cheap (no accumulated context) - Switching workers costs nothing (previous worker's context is gone) ### 3. Memory Manager (`core/memory.py`) **Two tiers with automatic compaction:** - **Tier 1 (Brief)**: Human-written, frozen. The "constitution" of the project. - **Tier 2 (Log)**: Agent-written, rolling. Milestones and decisions. **Compaction rules:** 1. Milestones: Drop oldest when section exceeds 1,200 chars 2. Decisions: Keep only last 15 entries 3. Total log: Hard cap at 2,000 chars (aggressive compaction if exceeded) ### 3b. Experiment Ledger & Research Journals (`core/ledger.py`, `core/journal.py`) The compacting two-tier memory keeps context small but **drops detail**. The v2 autonomy layer adds durable, append-only records that complement it: - **Experiment ledger** (`workspace/experiments.jsonl`): one JSON line per cycle (hypothesis, action, status, metrics, pid, conclusion). Append-only, crash-safe, queryable, zero LLM cost. Pure readers derive signals from it: - `summary(n)` — recent experiments block for the THINK context - `detect_stagnation(...)` — has the tracked metric stopped improving? - `check_phase_gate(...)` — does the best metric clear a configured bar? - **Research journals**: `DEAD_ENDS.md` (failed approaches — do not retry) and `INSIGHTS.md` (durable observations). Never compacted; rotated to a dated `.bak` when oversized, so history is preserved, not lost. These signals (plus a zero-cost violation scan from `core/safety.py`) are injected into the leader's THINK/REFLECT context by `loop._enrich_context`. They are **advisory and additive**: with default config they enrich planning without changing control flow. `core/safety.py` also provides the proactive anti-burn rate limiter (`seconds_until_allowed`) used by `loop._throttle_if_needed`. ### 4. Experiment Monitor (`core/monitor.py`) **The zero-cost innovation.** During training: - backend PID check — is process alive? (zero cost) - backend GPU query — `nvidia-smi` when available (zero cost) - backend file tail — last log lines (zero cost) No LLM API calls until training completes. ### 5. Execution Backend (`core/execution.py`) Execution is pluggable: - **LocalExecutionBackend**: current behavior, runs everything inside `project.workspace` - **SSHExecutionBackend**: keeps controller state local, but runs the tool-visible workspace, training commands, log reads, PID checks, and GPU queries on one remote host over SSH - **SlurmExecutionBackend**: for Slurm-managed clusters. Subclasses the SSH backend (file/repo/`run_command` ops run on the login node, which shares the NFS workspace) and only changes job handling: training is submitted with `sbatch --parsable` over one transient ssh call that exits immediately, so no process is ever left running on the login node. The Slurm job id is carried in the `pid` field, `sacct` is the sole liveness authority (Slurm enforces `--time`, so a job is guaranteed terminal by `--time` + a buffer), and GPU status is reported from the partition's `squeue` occupancy. The SSH/Slurm backends are intentionally narrow: - one remote host (the login node, in Slurm mode) - one remote workspace root - single-cluster Slurm submission; no multi-host orchestration ### 6. Tool Registry (`core/tools.py`) **Per-agent minimal tool sets** reduce token overhead: - Each tool definition is ~200 tokens in the API call - 15 tools = 3,000 extra tokens per call - 5 tools = 1,000 extra tokens per call - Over 100 API calls/day, that's still ~200K tokens saved Tools are grouped so each worker only carries what it needs: - **Codebase comprehension** (code/writing agents): `list_tree` (recursive, depth-limited repo map), `search_code` (regex grep across files), and `read_file` with optional line ranges for large files. - **Literature** (idea agent): `search_papers` (Semantic Scholar), `search_arxiv` (freshest preprints), and `get_paper` (full details plus reference/citation snowballing). ToolRegistry still owns command parsing and path safety. It validates relative paths and parses shell text into argv before delegating execution to the selected backend. The repo-reading tools skip `.git`, `__pycache__`, and similar noise directories and run identically in local and SSH modes. ### 7. Tool-Use Protocol (`core/agents.py::dispatch_worker`) Workers drive tool calls through a provider-agnostic text protocol rather than each SDK's native tool-use API: 1. The dispatcher renders the worker's tool schemas as a plain-text `## Tool-Use Protocol` section and appends it to the system prompt. 2. The worker emits zero or more `{"name": "...", "args": {...}}` blocks in its response. 3. For each block, the dispatcher calls `ToolRegistry.execute_tool` and packages the JSON result into a `...` block appended to the next user turn. 4. The loop iterates until the worker returns a message with no tool calls (the final answer) or `max_turns` is reached. Design rationale: - **Uniform behaviour across four providers.** The same protocol works whether the LLM is reached via the Anthropic SDK, the OpenAI SDK, the `claude` CLI, or the `codex` CLI. The execution loop contains no per-provider branching. - **Authoritative experiment hand-off.** `pid` and `log_file` flow from the `launch_experiment` tool result (structured JSON) to `_parse_worker_response`, which promotes them onto the top-level result dict read by `loop._monitor_experiment`. Regex-on-prose remains as a fallback only. - **CLI lock-down.** `claude_cli` is invoked with `--tools ""` so the Claude Code CLI cannot bypass the protocol using its built-in tools. `codex_cli` has no equivalent flag, so it may silently act on its own; `dispatch_worker` logs a warning when `codex_cli` is used as a worker provider, and the README compatibility table flags it accordingly. - **Fence stripping.** Tool-call blocks inside triple-backtick code fences are removed before parsing so that models illustrating the protocol in their prose do not trigger real side-effectful tool execution. - **Bounded execution.** `max_turns` is configured per-worker (`idea=12`, `code=40`, `writing=30`); on overflow the loop exits cleanly and the last response is returned with a warning. ### 8. GPU Utilities (`gpu/`) - **detect.py**: Auto-detect GPUs, check availability, reserve last GPU - **keeper.py**: Keep cloud instances alive with minimal GPU activity