Skip to content

Aider Architecture Index

  • Repository: references/aider
  • Commit SHA: b9050e1d5faf8096eae7a46a9ecc05a86231384b
  • Indexed scope: Full aider/ source tree
  1. System Overview
  2. RepoMap & Context Selection
  3. Coders Subsystem (Edit Formats)
  4. LLM Interaction Layer
  5. Commands System
  6. Terminal I/O
  7. Git Repository Management
  8. File Watching
  9. Linting
  10. Chat History & Summarization
  11. Main Entry Point & Bootstrap
  12. Supporting Modules
  13. Cross-Cutting Patterns

Aider follows a classical MVC pattern:

  • Model: LLM abstraction (models.py), git repo state (repo.py), file content tracking.
  • View: InputOutput class in io.py — terminal I/O with Rich formatting, prompt_toolkit completions.
  • Controller: Commands (commands.py) for slash commands, Coder subclasses (coders/) for edit strategies.

Key architectural decisions:

  • Strategy pattern for edit formats (14+ registered coder subclasses).
  • Exception-based control flow for model/format switching (SwitchCoder).
  • Lazy LLM import to keep startup fast (~1.5s saved by deferring litellm).
  • Background thread file watching with AI-comment detection.
  • Multi-layer caching (disk-backed tags, in-memory maps, git tree file lists).

Aider’s repo map is a token-budgeted structural synopsis of files not currently in-chat. It is not a plain file list. It performs:

  • Symbol extraction (tree-sitter query captures per language).
  • Reference/definition graph ranking (PageRank over referencer->definer edges).
  • Mention-aware personalization (current prompt words + filename/identifier hints).
  • Token-fit packing (binary search for a map that fits a target context budget).
  • Multi-layer caching (disk-backed tags cache + in-memory map cache keyed by inputs/refresh mode).

This is the core context-efficiency mechanism that lets Aider expose whole-repo structure without blasting full file contents.

Runtime Entry Path (How RepoMap Gets Into Prompts)

Section titled “Runtime Entry Path (How RepoMap Gets Into Prompts)”
  • Aider constructs the coder object with repo map knobs (map_tokens, map_refresh, map_multiplier_no_files).
  • If --cache-prompts is enabled and refresh is auto, it rewrites refresh mode to files to favor stable cached prompts.
  • --show-repo-map calls coder.get_repo_map() directly and prints the generated map.

2) Coder initialization (aider/coders/base_coder.py)

Section titled “2) Coder initialization (aider/coders/base_coder.py)”
  • RepoMap(...) is instantiated only when all are true:
    • repo map is enabled (use_repo_map),
    • running inside a repo,
    • prompt template supports repo map prefix (gpt_prompts.repo_content_prefix).
  • Constructor wiring passes model/token context and policy:
    • map_tokens, root, main_model, io, prompt prefix, verbosity,
    • max_input_tokens, map_mul_no_files, map_refresh.

3) Per-turn map request (BaseCoder.get_repo_map)

Section titled “3) Per-turn map request (BaseCoder.get_repo_map)”

Flow:

  1. Build hint sets from current user text:
    • filename mentions from get_file_mentions(...).
    • identifier mentions from regex split (get_ident_mentions).
    • filename stem matches from identifier matches (get_ident_filename_matches).
  2. Partition files:
    • chat_files = editable in-chat files + repo files added as read-only.
    • other_files = all repo files minus chat_files.
  3. Ask RepoMap.get_repo_map(chat_files, other_files, mentioned_fnames, mentioned_idents, force_refresh).
  4. Fallback strategy if map returns empty:
    • try global map with no chat files but same mention hints,
    • then try completely unhinted global map.

4) Prompt embedding (BaseCoder.get_repo_messages)

Section titled “4) Prompt embedding (BaseCoder.get_repo_messages)”
  • Repo map is injected as a user message plus a fixed assistant acknowledgment:
    • user: repo map content
    • assistant: “Ok, I won’t try and edit those files without asking first.”
  • This keeps model behavior aligned: map files are context, not implicit edit targets.
  • Cache version is tied to parser backend (USING_TSL_PACK):
    • v3 normally, v4 with tree-sitter-language-pack.
  • Tags cache directory: .aider.tags.cache.v{CACHE_VERSION} in repo root.
  • In-memory runtime caches:
    • tree_cache: rendered snippets keyed by (rel_fname, lines_of_interest, mtime).
    • tree_context_cache: parsed TreeContext keyed by filename + mtime.
    • map_cache: final map strings keyed by file sets and options.
  • Early outs: disabled map budget or empty other_files.
  • Dynamic budget expansion when no files are in chat:
    • computes enlarged target from max_map_tokens * map_mul_no_files,
    • clamps against model context window minus 4096 safety padding,
    • uses enlarged budget only in no-chat scenario.
  • Calls get_ranked_tags_map(...) and prepends optional prompt prefix.
  • Uses sampled token counting for large strings (token_count):
    • exact count for short text,
    • line-sampling estimate for long text to avoid repeated expensive tokenization.

get_tags(...) (mtime-gated cached extraction)

Section titled “get_tags(...) (mtime-gated cached extraction)”
  • Uses file mtime as validity check.
  • On cache hit with same mtime, returns stored tags.
  • On miss, runs get_tags_raw(...), stores {mtime, data} in disk cache.
  • Any SQLite/diskcache errors trigger tags_cache_error(...):
    • attempts cache directory rebuild,
    • if rebuild fails, degrades to in-memory dict cache.

get_tags_raw(...) (tree-sitter + fallback lexer)

Section titled “get_tags_raw(...) (tree-sitter + fallback lexer)”

Flow:

  1. Detect language from filename (grep_ast.filename_to_lang).
  2. Load tree-sitter language/parser (get_language, get_parser).
  3. Load tags query file (get_scm_fname(lang)), preferring tree-sitter-language-pack and falling back to tree-sitter-languages.
  4. Parse file code and run query captures.
  5. Emit Tag(rel_fname, fname, line, name, kind) for captures:
    • name.definition.* -> kind="def"
    • name.reference.* -> kind="ref"
  6. If parser yields defs but no refs, backfill refs using Pygments Token.Name lexing (line = -1).

Compatibility detail:

  • _run_captures supports both old and new tree-sitter Python APIs (query.captures vs QueryCursor(query).captures).
  • defines[ident] -> set(defining files)
  • references[ident] -> list(referencing files)
  • definitions[(file, ident)] -> set(Tag defs)
  • personalization[file] -> float for PageRank personalization vector.
  • Base per-file weight from chat file membership.
  • Mentioned file paths get prioritized.
  • Mentioned identifiers matching path components/basenames increase weight.
  • Graph type: networkx.MultiDiGraph.
  • Self-edge fallback (weight=0.1) added for symbols with defs but no refs.
  • For each (referencer -> definer) symbol relation, edge weight combines:
    • symbol-level multiplier (mul):
      • x10 if ident explicitly mentioned,
      • x10 if likely semantic name (snake/kebab/camel and len>=8),
      • x0.1 if private-like (_prefix),
      • x0.1 if symbol defined in many files (>5) to de-noise broad/common names,
    • chat proximity boost (x50 if referencer is in chat),
    • frequency scaling (sqrt(num_refs)) to damp high-frequency symbol spam.
  • Runs nx.pagerank(..., weight="weight", personalization=..., dangling=...) when personalization exists.
  • If personalization path zero-divides, retries plain weighted PageRank.
  • Node rank is redistributed over out-edges proportionally by edge weight.
  • Accumulates rank into (destination_file, ident) pairs and sorts descending.
  • Converts ranked pairs into ordered Tag list, skipping files already in chat.
  • Ensures non-tagged files are still represented by appending (fname,) entries.
  • Cache key includes sorted chat/other files and token budget.
  • In auto refresh mode, key also includes mentioned filenames/identifiers.
  • Refresh mode behavior:
    • manual: return last_map unless forced.
    • always: bypass map cache every call.
    • files: use map cache (stable for same file sets).
    • auto: use cache only if previous uncached computation took >1s.

Flow:

  1. Compute ranked tags.
  2. Prepend important project root files (filter_important_files from aider/special.py) that were not already ranked.
  3. Binary search over prefix length of ranked items to fit max_map_tokens.
  4. Render each candidate via to_tree(...) and estimate token count.
  5. Accept best under budget, or early-stop if within 15% of target.

This is the key throughput trick: large repos get near-optimal budget utilization without linear trial-and-error.

F) Rendering stage (to_tree + render_tree)

Section titled “F) Rendering stage (to_tree + render_tree)”
  • Group tags by file and render each file once.
  • If file has symbol lines-of-interest, render focused context via TreeContext with:
    • no color,
    • no line numbers,
    • no extra child/top-of-file parent context,
    • explicit LOI expansion using add_context().
  • If file has no selected tags, include filename-only entry.
  • Hard truncate every output line to 100 chars to avoid minified-file explosions.
  • Maintains root-level “important file” allowlist (README*, LICENSE*, lockfiles, Docker/CI configs, etc.).
  • Repo map prepends these files so project intent/config remains visible even when symbol ranking is sparse.
  • Language-specific capture patterns for name.definition.* and name.reference.*.
  • Dual query trees support both parser backends.

Key invariants covered:

  • map contains all files in simple fixture repos,
  • map excludes files already added to chat,
  • refresh modes (files, auto, force refresh) behave correctly,
  • multi-language symbol extraction works across broad fixture matrix,
  • sample-code-base output is deterministic against golden file.
  • Tree-sitter API churn required compatibility shim for query captures.
  • Disk cache can fail (SQLite/path/permissions); robust fallback to memory cache is mandatory.
  • Definition-only grammars need lexical fallback refs (Pygments) or ranking quality collapses.
  • Without damping (sqrt(num_refs)) and heuristic multipliers, noisy high-frequency identifiers dominate graph rank.
  • Token-fit via binary search is materially better than fixed top-N slicing in heterogeneous repos.
  • Refresh policy materially changes UX/cost: manual/files improve stability; always improves freshness at higher cost.

The coders subsystem implements the Strategy pattern for edit formats. Each coder type defines how LLM responses are parsed into file edits and what prompt templates instruct the model.

Coder (base_coder.py:88) — Abstract base, ~2400 lines
├─ EditBlockCoder (editblock_coder.py:18) — edit_format="diff"
│ ├─ EditBlockFencedCoder (editblock_fenced_coder.py:9) — edit_format="diff-fenced"
│ └─ EditorEditBlockCoder (editor_editblock_coder.py:7) — edit_format="editor-diff"
├─ WholeFileCoder (wholefile_coder.py:13) — edit_format="whole"
│ └─ EditorWholeFileCoder (editor_whole_coder.py:7) — edit_format="editor-whole"
├─ UnifiedDiffCoder (udiff_coder.py:49) — edit_format="udiff"
│ └─ UnifiedDiffSimpleCoder (udiff_simple.py:12) — edit_format="udiff-simple"
├─ EditorDiffFencedCoder (editor_diff_fenced_coder.py:8) — edit_format="editor-diff-fenced"
├─ PatchCoder (patch_coder.py:217) — edit_format="patch"
├─ SingleWholeFileFuncCoder (single_wholefile_func_coder.py:9) — edit_format="func"
├─ AskCoder (ask_coder.py:8) — edit_format="ask" (read-only, no edits)
├─ HelpCoder (help_coder.py:9) — edit_format="help"
├─ ArchitectCoder (architect_coder.py:7) — edit_format="architect" (two-stage: plan then implement)
└─ ContextCoder (context_coder.py:8) — edit_format="context" (context-only, no edits)

Each coder has a paired *_prompts.py file containing the system/user prompt templates.

Factory: Coder.create() (base_coder.py:128-200)

Section titled “Factory: Coder.create() (base_coder.py:128-200)”
  • Resolves edit_format from: explicit arg > from_coder.edit_format > main_model.edit_format.
  • Iterates all registered coder classes in aider.coders.__all__, matching on edit_format attribute.
  • Raises UnknownEditFormat if no match.
  • from_coder parameter transfers state (chat history, file list, repo) between coder instances.

Base Coder Core Attributes (base_coder.py)

Section titled “Base Coder Core Attributes (base_coder.py)”
abs_fnames = set() # Editable files (absolute paths)
abs_read_only_fnames = set() # Context-only files
repo = None # GitRepo instance
main_model = None # Primary LLM
weak_model = None # Summarization/commit LLM
cur_messages = [] # Current turn messages
done_messages = [] # Completed turn history
partial_response_content = "" # Streaming accumulator
multi_response_content = "" # Multi-response accumulator
edit_format = None # Subclass sets this
gpt_prompts = None # Prompt template object
stream = True # Streaming responses
auto_lint = True # Lint after edits
auto_test = False # Test after edits
auto_commits = True # Git commit after edits
dirty_commits = True # Commit dirty files before edits

Main Loop: run(with_message=None) (base_coder.py)

Section titled “Main Loop: run(with_message=None) (base_coder.py)”

The main conversation loop:

  1. Get user input (or use with_message for programmatic invocation).
  2. Check for / commands — dispatch to Commands.
  3. Build full message context via format_chat_chunks().
  4. Call send_message(inp) which: a. Formats all chat chunks into a message list. b. Calls send(messages, model, functions) to hit the LLM. c. Processes the streaming/non-streaming response. d. Calls apply_updates() to parse and apply edits.
  5. Post-edit operations: auto-lint, auto-test, auto-commit.
  6. Handle reflected messages (error feedback to LLM for retry).

Message Construction: format_chat_chunks() (base_coder.py:1226)

Section titled “Message Construction: format_chat_chunks() (base_coder.py:1226)”

Builds the full prompt in sections:

  1. System prompt: gpt_prompts.main_system with optional system_prompt_prefix from model.
  2. Example messages: Optional few-shot examples (gpt_prompts.example_messages).
  3. Done messages: Summarized history of previous turns.
  4. Repo map: Injected via get_repo_messages().
  5. Read-only file content: Files in abs_read_only_fnames.
  6. Editable file content: Files in abs_fnames.
  7. System reminder: Appended to last message or as separate system message depending on reminder policy (sys, user, or disabled).
  8. Current user message: The actual user input.

Edit Application Pipeline: apply_updates() (base_coder.py:2296)

Section titled “Edit Application Pipeline: apply_updates() (base_coder.py:2296)”

Flow:

  1. get_edits() — subclass-specific parsing of LLM response into edit tuples.
  2. apply_edits_dry_run(edits) — validates edits can be applied.
  3. prepare_to_edit(edits) — checks permissions, dirty-commits files before editing.
  4. apply_edits(edits) — writes changes to disk.
  5. On ValueError (malformed response): increments num_malformed_responses, sets reflected_message for retry.

File Permission: allowed_to_edit(path) (base_coder.py:2191)

Section titled “File Permission: allowed_to_edit(path) (base_coder.py:2191)”
  • If file is in abs_fnames: allowed (check for dirty commit first).
  • If file is git-ignored: skip with warning.
  • If file doesn’t exist: prompt user to create.
  • If file exists but not in chat: prompt user to add.
  • Handles need_to_add for untracked files via repo.git.add().

EditBlock Coder Detail (editblock_coder.py + search_replace.py)

Section titled “EditBlock Coder Detail (editblock_coder.py + search_replace.py)”

The most commonly used edit format. LLM produces search/replace blocks:

<<<<<<< filename.py
original code
=======
replacement code
>>>>>>> filename.py

Parsing: find_original_update_blocks() (search_replace.py)

Section titled “Parsing: find_original_update_blocks() (search_replace.py)”
  • Regex-based parser for fenced edit blocks.
  • Extracts filename, original text, replacement text.
  • Handles edge cases: empty originals (new file content), shell commands.

When exact match fails, applies strategies in order:

  1. Exact match: Direct line-by-line comparison.
  2. Whitespace-normalized: Ignore blank line differences.
  3. Indent-corrected: Add/remove leading whitespace to align.
  4. Edit distance: Levenshtein distance for closest match (finds most similar block in file).

This is critical because LLMs often produce slightly different whitespace or context lines than the actual file.

Simplest format: LLM returns complete file content in a fenced code block. Parser extracts filename from fence header and replaces entire file.

LLM produces standard unified diff format with ---/+++ headers and @@ hunks. Parser applies hunks with context matching.

Two-stage edit process:

  1. Plan stage: LLM generates a plan (what to change and why).
  2. Implementation stage: Creates a secondary coder (EditBlock or other) to execute the plan. Uses a separate editor model if configured (--editor-model).

Model Configuration (aider/models.py, ~1324 lines)

Section titled “Model Configuration (aider/models.py, ~1324 lines)”
@dataclass
class ModelSettings:
name: str
edit_format: str = "whole"
weak_model_name: Optional[str] = None
use_repo_map: bool = False
reasoning_tag: Optional[str] = None # "thinking", "think", etc.
streaming: bool = True
use_temperature: Union[bool, float] = True
cache_control: bool = False
use_system_prompt: bool = True
extra_params: Optional[dict] = None
accepts_settings: Optional[list] = None
editor_edit_format: Optional[str] = None
reminder: str = "sys" # "sys", "user", or disabled
examples_as_sys_msg: bool = False
lazy: bool = False
system_prompt_prefix: Optional[str] = None
  1. YAML settings (aider/resources/model-settings.yml, ~900 lines): Per-model overrides loaded via importlib.resources.
  2. Canonical aliases (MODEL_ALIASES dict, models.py:87-111): User-friendly shortcuts (e.g., "sonnet""claude-4-6-sonnet").
  • Primary: Fetch from LiteLLM’s model_prices_and_context_window.json.
  • Fallback: OpenRouter API scraping for "openrouter/" prefixed models.
  • Cache: 24-hour TTL at ~/.aider/caches/model_prices_and_context_window.json.
  • Token limits: max_chat_history_tokens = min(max(max_input_tokens / 16, 1024), 8192).

Generic Model Settings (models.py:421-584)

Section titled “Generic Model Settings (models.py:421-584)”

Auto-detected patterns by model name prefix:

  • o1-*: No system prompt, no temperature, no streaming.
  • deepseek-r1/*: diff format, repo map, reasoning_tag: "think".
  • claude-4-6-sonnet/*: diff format, accepts thinking_tokens.
  • qwq-32b/*: diff format, reasoning_tag: "think", use_temperature: 0.6.

Thinking/Reasoning Token Configuration (models.py:823-889)

Section titled “Thinking/Reasoning Token Configuration (models.py:823-889)”
  • Parses values: 8096, "8k", "10.5k", "0.5M".
  • Sets extra_params["thinking"] = {"type": "enabled", "budget_tokens": N}.
  • OpenRouter format: extra_params["extra_body"]["reasoning"]["max_tokens"].

Lazy LiteLLM Loading (aider/llm.py, 48 lines)

Section titled “Lazy LiteLLM Loading (aider/llm.py, 48 lines)”
class LazyLiteLLM:
_lazy_module = None
def __getattr__(self, name):
self._load_litellm()
return getattr(self._lazy_module, name)
def _load_litellm(self):
if self._lazy_module is not None:
return
self._lazy_module = importlib.import_module("litellm")
self._lazy_module.suppress_debug_info = True
self._lazy_module.set_verbose = False
self._lazy_module.drop_params = True

Defers import litellm (~1.5s) until first actual LLM call.

Environment vars set:

  • LITELLM_MODE = "PRODUCTION"
  • OR_SITE_URL = "https://aider.chat"
  • OR_APP_NAME = "Aider"

API Call Layer: send_completion() (models.py:970-1022)

Section titled “API Call Layer: send_completion() (models.py:970-1022)”

Request construction:

  1. Base kwargs: model, stream.
  2. Temperature: skip if use_temperature=False; use float value if set; default to 0.
  3. Function/tool formatting: wraps single function schema into OpenAI tools format.
  4. Extra parameters deep merge from self.extra_params.
  5. Ollama auto-context: num_ctx = int(token_count(messages) * 1.25) + 8192.
  6. GitHub Copilot token exchange headers if GITHUB_COPILOT_TOKEN set.
  7. Request signature SHA1 for deduplication.
  8. Default timeout: 600s.
  9. Final call: litellm.completion(**kwargs).

Retry Logic: simple_send_with_retries() (models.py:1024-1067)

Section titled “Retry Logic: simple_send_with_retries() (models.py:1024-1067)”
  • Starting delay: 125ms, doubles each retry.
  • Max delay before giving up: 60s.
  • Retryable errors: APIConnectionError, RateLimitError, InternalServerError, ServiceUnavailableError, etc.
  • Non-retryable: AuthenticationError, ContextWindowExceededError, NotFoundError.
  • Uses LiteLLMExceptions class (aider/exceptions.py) with ExInfo(name, retry, description) dataclass.

Message Validation (aider/sendchat.py, 62 lines)

Section titled “Message Validation (aider/sendchat.py, 62 lines)”
  • sanity_check_messages(): validates user/assistant alternation, last message is user.
  • ensure_alternating_roles(): auto-fixes by inserting empty opposite-role messages. Used for deepseek-reasoner, deepseek-r1, o1 models.

Streaming Response Processing (base_coder.py:1900-1972)

Section titled “Streaming Response Processing (base_coder.py:1900-1972)”
  • Iterates completion generator chunk by chunk.
  • Extracts chunk.choices[0].delta.content and delta.reasoning_content.
  • Wraps reasoning content in <thinking-content-...> tags.
  • Live display via self.mdstream.update() (Rich markdown stream) or raw sys.stdout.write().
  • Raises FinishReasonLength if finish_reason == "length" (context limit hit).

Token & Cost Tracking (base_coder.py:1994-2100)

Section titled “Token & Cost Tracking (base_coder.py:1994-2100)”
  • Uses completion.usage when available (prompt_tokens, completion_tokens).
  • Handles Anthropic cache fields: cache_read_input_tokens, cache_creation_input_tokens.
  • Handles DeepSeek cache: prompt_cache_hit_tokens.
  • Falls back to manual model.token_count() if usage not available.
  • Cost calculation: tries litellm.completion_cost() first, then manual input_cost_per_token * tokens.

Reasoning Tags (aider/reasoning_tags.py, 83 lines)

Section titled “Reasoning Tags (aider/reasoning_tags.py, 83 lines)”
  • REASONING_TAG = "thinking-content-" + unique_hash — internal marker.
  • remove_reasoning_content(res, tag): strips <tag>...</tag> from response. Handles unclosed tags.
  • replace_reasoning_tags(text, tag): converts to formatted display with ► THINKING / ► ANSWER headers.
  • format_reasoning_content(content, tag): wraps reasoning in tags for injection.

Architecture (aider/commands.py, ~1700 lines)

Section titled “Architecture (aider/commands.py, ~1700 lines)”

Slash commands are dispatched via method reflection: every cmd_<name> method on the Commands class becomes a /name command. Underscores convert to dashes (cmd_chat_mode/chat-mode).

Custom exception carrying kwargs for creating a new coder instance:

class SwitchCoder(Exception):
def __init__(self, placeholder=None, **kwargs):
self.kwargs = kwargs
self.placeholder = placeholder

Caught in main.py event loop to create a new Coder while preserving session state.

  • cmd_model(args): Switch LLM model. Raises SwitchCoder(main_model=Model(args)).
  • cmd_chat_mode(args): Switch edit format (editblock, whole, udiff, etc.). Raises SwitchCoder.
  • cmd_editor_model(args): Switch secondary editor LLM.
  • cmd_weak_model(args): Switch summarization/commit LLM.
  • cmd_reasoning_effort(args): Set reasoning effort level.
  • cmd_think_tokens(args): Set thinking token budget.
  • cmd_add(args): Add files to editable scope. Supports glob patterns, git-filtered.
  • cmd_drop(args): Remove files from editable scope.
  • cmd_read_only(args) / cmd_read(args): Add files as read-only context.
  • cmd_no_read(args): Remove read-only context.
  • cmd_ls(): List tracked files and their chat status.
  • cmd_lint(fnames=None): Run linter on files. Creates temporary lint_coder to auto-fix.
  • cmd_test(test_cmd): Run tests. On failure, feeds output back to LLM for fixing.
  • cmd_run(args): Run arbitrary shell command. Output available as context.
  • cmd_commit(args): Create git commit with AI-generated or provided message.
  • cmd_undo(): Rollback last aider commit (git checkout HEAD~1 -- files && git reset --soft HEAD~1). Validates commit was made by aider and not pushed.
  • cmd_diff(): Show diff of current changes.
  • cmd_web(args): Scrape URL and add content as context.
  • cmd_map(): Show current repo map.
  • cmd_map_refresh(): Force repo map refresh.
  • cmd_tokens(): Show token usage report.
  • cmd_editor(args): Open file in external editor ($VISUAL / $EDITOR).
  • cmd_ask(args): Switch to ask mode (no edits, just discussion).
  • cmd_code(args): Switch back to code editing mode.
  • cmd_architect(args): Switch to architect mode (plan then implement).
  • cmd_clear(): Clear chat history.
  • cmd_reset(): Clear and drop all files.
  • cmd_copy(args): Copy last assistant message to clipboard.
  • cmd_paste(): Paste clipboard as user message.
  • cmd_save(fname) / cmd_load(fname): Save/load commands to file.
  • cmd_settings(): Show current settings.
  • cmd_help(args): Show help.
  • cmd_quit() / cmd_exit(): Exit aider.

Each command can have completions_<cmd>() (returns list) and/or completions_raw_<cmd>() (returns path completions) methods. These feed into AutoCompleter in io.py.

  • parse_quoted_filenames(args) (commands.py:1680): Regex r'"(.+?)"|(\\S+)' for quoted paths with spaces.
  • glob_filtered_to_repo(pattern) (commands.py:765): Glob expansion filtered to git-tracked files.
  • expand_subdir(file_path) (commands.py:1669): Recursively yields files from directories.

Manages all terminal I/O with Rich formatting, prompt_toolkit completions, and history.

Extends prompt_toolkit.completion.Completer.

Tokenization (io.py:127-146):

  • Lazy-loads on first completion call.
  • Parses all chat files with Pygments lexer.
  • Extracts tokens marked with Token.Name.
  • Caches word/backtick-word pairs.

Completion logic (io.py:186-227):

  1. Tokenize files on first use.
  2. If starts with /: command completion from Commands.
  3. Else: word completion from code tokens.
  4. Requires >= 3 character prefix.
  5. Case-insensitive partial matching.

Core I/O manager.

Key parameters:

  • pretty: Rich formatting (disabled if NO_COLOR env var).
  • yes: Auto-answer prompts.
  • input_history_file: Persisted readline history (~/.aider_history).
  • chat_history_file: Markdown log of all interactions.
  • multiline_mode: Toggle Enter/Alt+Enter behavior.
  • code_theme: Syntax highlighting theme.
  • editingmode: VI or EMACS editing mode.

Uses prompt_toolkit.PromptSession with dumb-terminal detection fallback.

Main Input Loop: get_input() (io.py:523-734)

Section titled “Main Input Loop: get_input() (io.py:523-734)”
  1. Ring bell if LLM response finished.
  2. Build prompt showing file list.
  3. Enter multiline loop:
    • { starts multiline mode.
    • {tag starts tagged multiline (closed with tag}).
    • } closes multiline.
  4. Display user input to chat log.
  5. Return accumulated text.
  • Ctrl+Z: Suspend to background.
  • Ctrl+Space: Insert space literally.
  • Ctrl+Up/Down: History navigation.
  • Ctrl+X, Ctrl+E: Open external editor.
  • Enter: Submit in normal mode / newline in multiline.
  • Alt+Enter: Opposite behavior.
  • tool_output(text) (io.py:995-1012): Informational messages, optional bold, colored.
  • tool_error(text) (io.py:988-990): Red error messages, increments error counter.
  • tool_warning(text) (io.py:992-993): Orange warning messages.
  • assistant_output(message) (io.py:1023-1041): Renders markdown with syntax highlighting via Rich.
  • get_assistant_mdstream() (io.py:1014-1021): Returns MarkdownStream for streaming display.

Confirmation: confirm_ask() (io.py:806-925)

Section titled “Confirmation: confirm_ask() (io.py:806-925)”
  • Group-based batch operations (Y/N, (A)ll, (S)kip all, (D)on’t ask again).
  • Persistent “Don’t ask again” per question.
  • Bell rings before prompting.
  • read_text(filename) (io.py:453-476): Safe read with encoding detection. Returns base64 for images.
  • write_text(filename, content) (io.py:478-507): Exponential backoff (max 5 retries) for locked files. Respects dry_run.
  • append_chat_history() (io.py:1117-1136): Appends to markdown chat log with blockquote wrapping.
  • log_llm_history() (io.py:754-765): Separate raw LLM request/response log with ISO timestamps.

GitPython wrapper for Aider-specific git operations.

  • Finds git repo from provided filenames or explicit git_dname.
  • Searches parent directories for .git.
  • Validates all files in same repo.
  • Stores attribution flags: attribute_author, attribute_committer, attribute_co_authored_by.

The commit() method handles authorship:

  • aider_edits=True (AI changes):
    • If co-authored-by: Don’t modify author/committer (trailer sufficient).
    • Else: Modify both author and committer to "You (aider)".
  • aider_edits=False (user changes via /commit):
    • Never modify author.
    • Always modify committer (aider is running git).

Implementation:

  1. Generate commit message (LLM or provided).
  2. Set GIT_AUTHOR_NAME and GIT_COMMITTER_NAME env vars via context manager.
  3. Call git commit -a -m.
  4. Return (commit_hash, commit_message).

Commit Message Generation (repo.py:326-373)

Section titled “Commit Message Generation (repo.py:326-373)”
  • Builds prompt with diffs and conversation context.
  • Tries each model in order (weak model first).
  • Checks token limits before sending.
  • Strips quotes from LLM response.
  • get_diffs(fnames) (repo.py:375-417): git diff HEAD for files. Handles initial commit (no HEAD) with git diff --cached + working dir diff.
  • diff_commits(from, to) (repo.py:419-431): git diff from_commit to_commit.
  • get_tracked_files(): Traverses HEAD commit tree for blobs. Caches per commit. Adds staged files from index. Filters with ignored_file().
  • path_in_repo(path) (repo.py:567-574): Checks tracked files set.
  • get_dirty_files() (repo.py:580-595): Returns staged + unstaged files.
  • refresh_aider_ignore() (repo.py:500-521): Watches .aiderignore for changes, recompiles PathSpec. Rate-limits to 1x/second.
  • ignored_file(fname) (repo.py:532-540): Checks both git ignore and .aiderignore. Caches results.
  • git_ignored_file(path) (repo.py:523-530): Uses GitPython repo.ignored().
  • ANY_GIT_ERROR tuple (repo.py:9-36): GitPython exceptions + generic errors. Used in try/except throughout.
  • set_git_env(var, value, original) (repo.py:39-49): Context manager for temporary env var changes.

Background file monitor that detects AI comments and triggers prompts.

ai_comment_pattern = re.compile(
r"(?:#|//|--|;+) *(ai\b.*|ai\b.*|.*\bai[?!]?) *$", re.IGNORECASE
)

Matches: # ai, // AI, /* ai! */, ai?, etc.

  • References coder instance for file tracking.
  • root: directory to watch.
  • gitignore_spec: loaded PathSpec patterns.
  • changed_files: set of detected changes.

Loaded via load_gitignores():

  • Editor temp: *~, *.swp, *.bak, .#*.
  • Build artifacts: __pycache__/, .pytest_cache/, *.pyc.
  • IDE config: .vscode/, .idea/, .sublime-*.
  • Environment: .env, .venv/, node_modules/, vendor/.
  • OS: .DS_Store, Thumbs.db.
  • Special: .aider*, .git.

Uses pathspec.GitWildMatchPattern for glob semantics.

  1. Check path is under root.
  2. Apply gitignore rules.
  3. Skip files > 1MB.
  4. Search for AI comment pattern.
  5. Return True if AI comments found.
  • Daemon thread running watch_files().
  • Uses watchfiles library for efficient OS-level file monitoring.
  • stop_event = threading.Event() for clean shutdown.
  • On change: sets changed_files, calls io.interrupt_input(), returns to stop watching.
  1. For each changed file: extract AI comments via get_ai_comments().
  2. If file not in chat: auto-add to coder.abs_fnames.
  3. Detect action: "!" (execute immediately) or "?" (ask question).
  4. Build TreeContext for each file showing lines with AI comments.
  5. Return formatted prompt using watch_code_prompt or watch_ask_prompt.

Watch Prompts (aider/watch_prompts.py, 13 lines)

Section titled “Watch Prompts (aider/watch_prompts.py, 13 lines)”
  • watch_code_prompt: “Find AI comments in shared files, follow their instructions, then remove the comments.”
  • watch_ask_prompt: “/ask\nFind the AI comments below… They contain my questions…”

Pluggable linting using tree-sitter for syntax errors and language-specific tools.

  1. Read file content.
  2. Detect language from extension.
  3. Resolve linter: explicit cmd > all_lint_cmd > per-language lookup > basic_lint (tree-sitter).
  4. Execute linter (callable or shell command).
  5. Convert errors to LintResult(text, lines).
  6. Wrap with TreeContext for display (marks error lines with ).

Three-stage pipeline:

  1. basic_lint(): Tree-sitter syntax errors (traverse AST for ERROR nodes).
  2. lint_python_compile(): compile(code, fname, "exec") for SyntaxError.
  3. flake8_lint(): Fatal errors only — --select=E9,F821,F823,F831,F406,F407,F701,F702,F704,F706.
  • Parse code to AST.
  • Traverse tree for ERROR or missing nodes.
  • Extract line numbers.
  • Skips TypeScript (unreliable parser).
  • /lint runs linter, shows issues, optionally creates lint_coder to auto-fix.
  • Auto-lint after every edit if --auto-lint enabled.

Architecture (aider/history.py, 144 lines)

Section titled “Architecture (aider/history.py, 144 lines)”

Summarization Algorithm (history.py:33-96)

Section titled “Summarization Algorithm (history.py:33-96)”

Adaptive splitting:

  • If total_tokens <= max_tokens: return unchanged.
  • If len(messages) <= 4 or depth > 3: summarize entire history.
  • Else:
    1. Calculate half_max_tokens = max_tokens // 2.
    2. Find split_index keeping recent messages <= half_max_tokens.
    3. Ensure split ends with assistant message.
    4. Summarize old messages.
    5. Combine summary + recent messages.
    6. If still too big: recurse with depth + 1.

Summarization Execution (history.py:98-123)

Section titled “Summarization Execution (history.py:98-123)”
  • Extract user + assistant messages.
  • Build "# USER" / "# ASSISTANT" sections.
  • Use model.simple_send_with_retries() with summarization system prompt.
  • Prepend summary_prefix (“I spoke to the user previously…”).
  • Wrap result in user message.
  • base_coder.py maintains cur_messages (current turn) and done_messages (completed turns).
  • When done_messages token count exceeds max_chat_history_tokens, summarizer reduces them.
  • Multiple models tried in fallback order.

Bootstrap Sequence (main(), lines 451-1181)

Section titled “Bootstrap Sequence (main(), lines 451-1181)”
  • Parse CLI args with configargparse.
  • Config file precedence: ~/.aider.conf.yml.aider.conf.yml (git root) → .aider.conf.yml (cwd) → --config.
  • Load .env files (including ~/.aider/oauth-keys.env).
  • Environment variable prefix: AIDER_*.
  • Register LLM models from .aider.model.settings.yml.
  • Initialize analytics (PostHog or file-based).
  • Setup git repo (create if missing with user confirmation).
  • Create InputOutput handler.
  • Select main model, weak_model, editor_model.
  • Validate model metadata (context window, pricing, features).
  • Sanity check models via models.sanity_check_models().
  • Create GitRepo instance.
  • Create Commands instance.
  • Create Coder subclass via Coder.create().
  • Create FileWatcher if --watch-files enabled.
while True:
try:
coder.run()
return
except SwitchCoder as switch:
kwargs = dict(io=io, from_coder=coder)
kwargs.update(switch.kwargs)
coder = Coder.create(**kwargs)
  • load_slow_imports(): Defers heavy imports (httpx, litellm, networkx) to background thread.
  • is_first_run_of_new_version(): Checks ~/.aider/installs.json to skip deferred loading on first run.
  • setup_git(git_root, io): Creates repo with dummy user.name/user.email if needed.
  • check_gitignore(git_root, io): Adds .aider* patterns to .gitignore.
  • sanity_check_repo(repo, io): Validates git index version (only v1 & v2 supported).

External editor integration for /editor command.

  • Discovery chain: editor_override > $VISUAL > $EDITOR > platform default (vim/vi/notepad).
  • Workflow: write temp file → subprocess.call(editor, shell=True) → read back → cleanup.

Streaming diff visualization.

  • diff_partial_update(): Shows meaningful diffs mid-stream by finding last non-deleted line.
  • Progress bar: [███░░░░░░] 30% based on lines processed.
  • Code fence wrapping with backtick count avoidance.

Web content ingestion for /web command.

  • Dual-path: Playwright (JS-rendered content) → httpx (static).
  • Playwright: Chromium with realistic User-Agent, 5s networkidle timeout.
  • HTML cleanup: BeautifulSoup strips SVGs, images, data URIs. Keeps only href.
  • Markdown conversion: pypandoc (auto-downloads pandoc) → fallback to cleaned HTML.

Shell command execution.

  • Unix: pexpect.spawn() with interactive TTY, output streaming, user can type input.
  • Windows: subprocess.Popen() batch mode, unbuffered output. Auto-wraps PowerShell commands.
  • Used by: /run, auto-lint, auto-test.
  • IgnorantTemporaryDirectory: Suppresses cleanup errors on Windows.
  • ChdirTemporaryDirectory: Auto-chdir into temp dir.
  • GitTemporaryDirectory: Creates initialized git repo in temp dir (used heavily in tests).
  • split_chat_history_markdown(text): Parses markdown chat log into structured messages (#### = user, > = tool, unmarked = assistant). Used for --restore-chat-history.
  • check_pip_install_extra(): Prompt user to install optional dependencies.

CLI argument parsing via configargparse. Major groups:

  • Main Model: --model, --weak-model, --editor-model, --edit-format, --thinking-tokens.
  • API Keys: --openai-api-key, --anthropic-api-key, --api-key provider=key.
  • Cache: --cache-prompts, --cache-keepalive-pings.
  • Repomap: --map-tokens, --map-refresh, --map-multiplier-no-files.
  • History: --chat-history-file, --input-history-file, --restore-chat-history.
  • Output: --dark-mode, --pretty, --stream, --code-theme.
  • Git: --auto-commits, --dirty-commits, --watch-files, --attribute-author.
  • Lint/Test: --auto-lint, --lint-cmd, --auto-test, --test-cmd.

Minimal prompt templates:

  • commit_system: System prompt for commit message generation.
  • undo_command_reply: Message for reverting changes.
  • added_files: Template for added file notification.
  • run_output: Template for command output context.
  • summarize: System prompt for chat history summarization.
  • summary_prefix: Prefix for continued conversations.

Model-specific prompts live in coders/*_prompts.py files.

LiteLLM exception handling with retry classification.

  • ExInfo(name, retry, description) dataclass.
  • Retryable: APIConnectionError, RateLimitError, InternalServerError, ServiceUnavailableError, etc.
  • Non-retryable: AuthenticationError, ContextWindowExceededError, NotFoundError.
  • Provider-specific detection: boto3 missing, OpenRouter down, insufficient credits.
  • Important file allowlist: README*, LICENSE*, lockfiles, Docker/CI configs.
  • filter_important_files(): Returns subset of files matching the allowlist.

Each *_coder.py + *_prompts.py pair forms a self-contained strategy. Adding a new format means: subclass Coder, set edit_format, implement get_edits() + apply_edits(), create prompt templates. No registration code needed beyond __init__.py.

2. Exception-Based Control Flow (SwitchCoder)

Section titled “2. Exception-Based Control Flow (SwitchCoder)”

Model/format switching uses non-local jumps. Commands.cmd_model() raises SwitchCoder, caught in main.py event loop, which creates a new Coder while preserving all state via from_coder.

Every cmd_<name> method on Commands becomes a slash command. Completions via completions_<cmd>() / completions_raw_<cmd>(). No manual registration.

  • LazyLiteLLM: defers heavy import until first API call.
  • AutoCompleter: tokenizes files on first completion, not on init.
  • load_slow_imports(): background thread for httpx/networkx.
  • Tags: disk-backed SQLite + in-memory fallback.
  • Map: in-memory keyed by file sets + budget + refresh mode.
  • Git tree: cached per commit hash.
  • Ignore files: cached per file path.
  • Playwright → httpx (web scraping).
  • pexpect → subprocess (shell commands).
  • Exact match → whitespace-normalized → indent-corrected → edit distance (code matching).
  • LiteLLM cost API → manual token calculation (cost tracking).
  • Tree-sitter → Pygments lexer (reference extraction).

Context managers (set_git_env()) handle GIT_AUTHOR_NAME and GIT_COMMITTER_NAME without side effects.

Adaptive recursive strategy. Recent messages kept verbatim, older batches summarized by LLM. Recurses if still over budget. Multiple model fallback.


OpenOxide Blueprint (Rust Implementation Plan)

Section titled “OpenOxide Blueprint (Rust Implementation Plan)”
  • crate::context::repomap: RepoMapService, TagExtractor, Ranker, MapPacker, MapRenderer.
  • crate::coders: Trait-based coder hierarchy. CoderTrait with get_edits(), apply_edits(), format_messages(). Concrete types: EditBlockCoder, WholeFileCoder, UdiffCoder, ArchitectCoder.
  • crate::llm: Model configuration, lazy provider loading, streaming response handling, retry logic.
  • crate::commands: Command dispatch (macro-based or explicit match arms instead of Python’s getattr).
  • crate::io: Terminal I/O via ratatui/crossterm. Async completions. History management.
  • crate::git: Repository management via git2-rs.
  • crate::watch: File watching via notify crate with AI-comment detection.
  • crate::lint: Tree-sitter syntax checking + external linter dispatch.
  • crate::history: Chat summarization with token budgeting.
  • Parsing: tree-sitter + per-language grammar crates or dynamic bundles.
  • Graph ranking: petgraph + custom weighted PageRank.
  • Cache: rusqlite with schema {path, mtime, tags_blob} and safe in-memory fallback.
  • Token estimation: Provider tokenizer wrapper + sampled estimator.
  • Terminal: ratatui + crossterm for TUI rendering.
  • HTTP: reqwest with streaming support for LLM calls.
  • Git: git2-rs instead of shelling out.
  • File watching: notify crate with debouncing.
  • Config: serde + TOML/YAML parsing.
  • CLI args: clap with derive macros.
  • Tag { rel_path, abs_path, line, symbol, kind } where kind in {Def, Ref}.
  • RepoMapRequest { chat_files, other_files, mentioned_paths, mentioned_idents, force_refresh }.
  • RepoMapResponse { text, estimated_tokens, sources_included }.
  • EditBlock { path, original, replacement }.
  • LlmMessage { role, content }.
  1. Preserve mention-aware personalization and chat-file proximity boost.
  2. Preserve fallback-to-global behavior when disjoint file set yields empty map.
  3. Preserve binary-search budget fit and 100-char line truncation guard.
  4. Preserve refresh policies (auto, always, files, manual) semantics.
  5. Preserve fuzzy edit matching chain (exact → whitespace → indent → edit distance).
  6. Preserve adaptive chat summarization with recursive depth control.
  7. Preserve AI-comment detection pattern for file watching.
  • Persist cache with content hash + mtime to avoid false-stale on coarse timestamp filesystems.
  • Parallel tag extraction over file batches with bounded concurrency (tokio::spawn).
  • Precompile query sets and share parser pools per language to reduce warm-up latency.
  • Add deterministic ranking tests with frozen graph fixtures to catch drift from heuristic edits.
  • Use async-trait for coder hierarchy to enable concurrent operations.
  • Use tower middleware pattern for retry/timeout/rate-limiting on LLM calls.
  • Compile-time verification of edit format registration via proc macros.