Skip to content

backend selection

Ary Rabelo edited this page Jul 22, 2026 · 1 revision

LLM Backend Selection

Relevant source files

  • src/repodocs/backend.py
  • README.md

Overview

RepoDocs delegates all page-planning and page-writing work to an external LLM CLI rather than calling a model API directly. Which CLI is invoked, and with what model, is controlled by two environment variables, REPODOCS_BACKEND and REPODOCS_MODEL, read by src/repodocs/backend.py. The module supports exactly three backends — claude, omp, and codex — each invoked as a read-only subprocess with no session persistence.

Sources: src/repodocs/backend.py:L14-L14, README.md:L130-L138

Selecting a backend: REPODOCS_BACKEND

backend_name() reads REPODOCS_BACKEND from the environment, lowercases and strips it, and defaults to "claude" when unset. If the value is not one of BACKENDS = {"omp", "claude", "codex"}, it raises ValueError. require_backend() wraps this and calls die() with exit code 2 on an invalid value, so CLI entry points fail fast with a clear message instead of propagating a traceback.

Sources: src/repodocs/backend.py:L14-L39

The README documents the same three choices and the login step each requires: Claude Code needs claude installed and /login once; OMP needs repodocs setup plus omp --profile=repo-docs /login; Codex needs codex login.

Sources: README.md:L88-L93, README.md:L136-L138

Selecting a model: REPODOCS_MODEL

effective_model(backend) implements the precedence: an explicit REPODOCS_MODEL environment value always wins regardless of backend. If it's unset, the claude backend falls back to DEFAULT_CLAUDE_MODEL = "claude-sonnet-5", while omp and codex fall back to None, letting those CLIs use their own built-in default model.

Sources: src/repodocs/backend.py:L17-L17, src/repodocs/backend.py:L42-L50

llm_label() composes a human-readable string for logging/UI, e.g. "claude/claude-sonnet-5" or "omp default model", by combining backend_name() and effective_model().

Sources: src/repodocs/backend.py:L78-L81

Invocation contract shared by all backends

Before dispatch, backend_contract(prompt) selects a system-prompt contract based on the prompt's opening text: prompts starting with "Plan the wiki pages" load agents/wiki-planner.md, prompts starting with "Write the wiki page" load agents/wiki-writer.md, both prefixed with the vendored AGENTS.md; any other prompt gets a minimal deterministic-subprocess instruction. Loaded contracts are cached in _CONTRACT_CACHE per mode.

Sources: src/repodocs/backend.py:L20-L23, src/repodocs/backend.py:L53-L75

run_llm(repo, prompt) dispatches to one of three subprocess command builders based on backend_name(), using a timeout from REPODOCS_TIMEOUT (default 600 seconds).

Sources: src/repodocs/backend.py:L120-L127

Claude

claude -p --safe-mode --no-session-persistence --permission-mode dontAsk \
  --tools Read,Grep,Glob --append-system-prompt <contract> [--model <model>]

The prompt is piped via input=prompt with cwd=repo, so Claude Code operates directly against the target repository's working directory, restricted to the Read,Grep,Glob tool set.

Sources: src/repodocs/backend.py:L140-L150

OMP

omp --profile=repo-docs -p --no-session --cwd <repo> \
  --config <isolation.yml> --no-rules --no-skills --no-extensions \
  --tools=read,grep,glob --approval-mode=write \
  --append-system-prompt <contract> [--model=<model>] <prompt>

OMP is invoked with an explicit isolation config file (PROFILE_SOURCE / "isolation.yml"), rules/skills/extensions disabled, and the tool set constrained to read,grep,glob; the prompt is passed as a trailing positional argument rather than via stdin.

Sources: src/repodocs/backend.py:L128-L139

Codex

codex exec --ephemeral --sandbox read-only --skip-git-repo-check \
  --cd <tempdir> --output-last-message <tempfile> [--model <model>] -

The Codex path first prints a one-time stderr warning via _warn_codex_read_boundary(), then creates a temporary directory, symlinks the target repo into it as repo_view, and runs codex exec from that temp directory with the prompt (contract + repo-root note + original prompt) piped over stdin (-). Codex writes its final answer to --output-last-message, which run_llm reads back into a CompletedProcess; if the file is missing, it synthesizes a failed result from stderr.

Sources: src/repodocs/backend.py:L100-L117, src/repodocs/backend.py:L151-L179

Security note: --sandbox read-only only blocks writes, not reads. The code's own comment states this restriction does not stop an adversarial repository (e.g. a hostile README or source comment) from causing the model to read and exfiltrate files outside the repo through generated docs, and that a real fix would need a container/VM-level read boundary, which is out of scope for this CLI. The README repeats this as "Ephemeral read-only sandbox" without further caveat, so the fuller warning lives only in code and in the runtime stderr message.

Sources: src/repodocs/backend.py:L100-L117, README.md:L138-L138

Parallelism and timeouts

jobs_count() reads REPODOCS_JOBS (default 4), clamped to the range 1–16, falling back to 4 if the value isn't a valid integer. parallel_llm() uses this to size a ThreadPoolExecutor that submits run_llm for each (key, prompt) pair; with REPODOCS_JOBS=1 this becomes effectively serial. Each REPODOCS_TIMEOUT-bounded subprocess call is independent, so a hung backend call fails only that page rather than the whole run.

Sources: src/repodocs/backend.py:L182-L205, README.md:L152-L154

Backend comparison

Backend Env value Login step Tool restriction Model override Prompt delivery
Claude Code (default) claude claude then /login --tools Read,Grep,Glob --model (default claude-sonnet-5) stdin
OMP omp repodocs setup, omp --profile=repo-docs /login --tools=read,grep,glob, --no-rules --no-skills --no-extensions, isolation config --model= trailing arg
Codex codex codex login --sandbox read-only (write-only boundary, see security note) --model stdin, output via --output-last-message

Sources: src/repodocs/backend.py:L120-L179, README.md:L88-L93, README.md:L136-L138

Clone this wiki locally