# Development Guide

This document covers Aleph's architecture and development workflow.

## Overview

Aleph is an MCP server implementing the [Recursive Language Model](https://arxiv.org/abs/2512.24601) (RLM) paradigm for document analysis. Instead of stuffing context into prompts, Aleph stores documents in a sandboxed Python REPL and provides tools for iterative exploration.

## Project Structure

```
aleph/
├── core.py              # Main Aleph class, RLM loop, message handling
├── types.py             # Dataclasses: Budget, AlephResponse, TrajectoryStep, etc.
├── config.py            # AlephConfig, create_aleph() factory
├── cli.py               # CLI entry points (aleph-rlm install/doctor)
├── mcp/
│   ├── local_server.py  # MCP server (main entry point)
│   └── server.py        # Compatibility entry point (aliases local_server)
├── repl/
│   ├── sandbox.py       # REPLEnvironment - sandboxed code execution
│   └── helpers.py       # 80+ helper functions (peek, search, extract_*, etc.)
├── sub_query/
│   ├── __init__.py      # SubQueryConfig, detect_backend()
│   ├── cli_backend.py   # Claude/Codex CLI spawning
│   └── api_backend.py   # OpenAI-compatible API calls
├── providers/
│   ├── base.py          # LLMProvider protocol
│   ├── anthropic.py     # Anthropic provider
│   └── openai.py        # OpenAI provider
└── prompts/
    └── system.py        # Default system prompt template
```

## Development Setup

```bash
# Clone and install in development mode
git clone https://github.com/Hmbown/aleph.git
cd aleph
pip install -e '.[dev,mcp]'

# Run tests
python3 -m pytest -q

# Run MCP server locally (with action tools enabled)
aleph --enable-actions --tool-docs concise
```

## Architecture

### Core Loop (`core.py`)

The `Aleph` class implements the RLM execution loop:

1. Context is stored in a sandboxed REPL namespace (`ctx`)
2. LLM receives metadata about context (format, size, preview) — not the full content
3. LLM writes Python code blocks to explore via helper functions
4. Aleph executes code, feeds truncated output back
5. Loop continues until LLM emits `FINAL(answer)` or `FINAL_VAR(variable_name)`

### MCP Server (`mcp/local_server.py`)

The primary entry point for IDE integration. Exposes tools:

- **Context management:** `load_context`, `peek_context`, `search_context`
- **Code execution:** `exec_python`, `get_variable`
- **Sub-queries:** `sub_query` (RLM-style recursive calls)
- **Reasoning:** `think`, `evaluate_progress`, `summarize_so_far`
- **Output:** `finalize`, `get_evidence`, `get_status`
- **Actions:** `run_command`, `read_file`, `write_file`, `run_tests`

### Sandbox (`repl/sandbox.py`)

The `REPLEnvironment` provides a sandboxed Python execution environment:

- **AST validation:** Blocks dunder access, forbidden builtins
- **Import whitelist:** `re`, `json`, `csv`, `math`, `statistics`, `collections`, `itertools`, `functools`, `datetime`, `textwrap`, `difflib`, `random`, `string`, `hashlib`, `base64`, `urllib.parse`, `html`
- **Output truncation:** Prevents token explosions
- **Helper injection:** 80+ functions for document analysis

The sandbox is best-effort, not hardened. For untrusted input, use container isolation.

### Sub-Query System (`sub_query/`)

Enables RLM-style recursive reasoning:

```python
# Backend detection priority (when backend="auto"):
# 1. ALEPH_SUB_QUERY_BACKEND env var (explicit override)
# 2. API (if ALEPH_SUB_QUERY_API_KEY or OPENAI_API_KEY set)
# 3. claude CLI (if installed)
# 4. codex CLI (if installed)
# 5. gemini CLI (if installed)
```

**CLI backend:** Spawns subprocess, passes prompt via stdin or temp file
**API backend:** OpenAI-compatible HTTP calls (any provider with `/v1/chat/completions`)

### Budget System (`types.py`)

`Budget` dataclass controls resource limits:

```python
@dataclass
class Budget:
    max_tokens: int = 100_000
    max_cost_usd: float = 1.0
    max_iterations: int = 100
    max_depth: int = 5
    max_wall_time_seconds: float = 300.0
    max_sub_queries: int = 50
```

`BudgetStatus` tracks consumption and is checked at each iteration.

### Provider Protocol (`providers/base.py`)

Custom providers must implement:

```python
class LLMProvider(Protocol):
    def complete(self, messages, model, **kwargs) -> tuple[str, int, int, float]:
        """Returns (response_text, input_tokens, output_tokens, cost_usd)"""

    def count_tokens(self, text: str, model: str) -> int: ...
    def get_context_limit(self, model: str) -> int: ...
    def get_output_limit(self, model: str) -> int: ...
```

## Testing

```bash
# Run all tests
pytest

# Run with coverage
pytest --cov=aleph --cov-report=term-missing

# Run specific test file
pytest tests/test_sub_query.py

# Run tests matching pattern
pytest -k "test_search"
```

## Code Style

- Python 3.10+ with type hints
- Formatted with `black` and `isort`
- Linted with `ruff`

```bash
# Format
black aleph tests
isort aleph tests

# Lint
ruff check aleph tests
```

## Adding a New Tool

1. Add the tool function in `mcp/local_server.py` inside `_register_tools()`
2. Decorate with `@self.server.tool()`
3. Include comprehensive docstring (shown to AI users)
4. Update `_Session` if tool needs state tracking
5. Add tests in `tests/`

Example:

```python
@self.server.tool()
async def my_new_tool(
    arg1: str,
    arg2: int = 10,
    context_id: str = "default",
) -> str:
    """One-line description.

    Longer description of what this tool does.

    Args:
        arg1: Description
        arg2: Description (default: 10)
        context_id: Session identifier

    Returns:
        Description of return value
    """
    session = self._sessions.get(context_id)
    if not session:
        return f"Error: No context loaded with ID '{context_id}'"

    # Implementation
    result = do_something(arg1, arg2)

    return f"## Result\n\n{result}"
```

## Adding a New Helper

1. Add the function in `repl/helpers.py`
2. Add to `HELPER_FUNCTIONS` dict at bottom of file
3. Add tests in `tests/test_helpers.py`

Example:

```python
def my_helper(ctx: str, arg: int = 5) -> list[str]:
    """One-line description.

    Args:
        ctx: The context string
        arg: Description (default: 5)

    Returns:
        List of results
    """
    # Implementation using ctx
    return results

# At bottom of file:
HELPER_FUNCTIONS = {
    # ... existing helpers ...
    "my_helper": my_helper,
}
```

## Environment Variables

| Variable | Purpose |
|----------|---------|
| `ALEPH_SUB_QUERY_BACKEND` | Force sub-query backend: `api`, `claude`, `codex`, `gemini` |
| `ALEPH_SUB_QUERY_API_KEY` | API key (fallback: `OPENAI_API_KEY`) |
| `ALEPH_SUB_QUERY_URL` | API base URL (fallback: `OPENAI_BASE_URL`) |
| `ALEPH_SUB_QUERY_MODEL` | Model name (required for API backend) |
| `ALEPH_MAX_ITERATIONS` | Iteration limit |
| `ALEPH_MAX_COST` | Cost limit in USD |

## Release Process

1. Update version in `pyproject.toml`
2. Sync versioned files: `python scripts/sync_versions.py`
3. Update `CHANGELOG.md`
4. Run full test suite: `pytest`
5. Build: `python -m build`
6. Upload to PyPI: `twine upload dist/*`
7. Tag release: `git tag v0.x.0 && git push --tags`

## Related Documentation

- [docs/prompts/aleph.md](docs/prompts/aleph.md) — workflow prompt + tool reference
- [README.md](README.md) — User documentation
- [CHANGELOG.md](CHANGELOG.md) — release notes
