<!-- Source of truth: DOCS.md · canonical page: https://delfhos.com/docs/reference · v0.8.7 -->
<!-- This is the raw-markdown rendition for LLMs and tools. -->

# Reference

*Reference material is information-oriented. It is precise, complete, and structured for lookup rather than learning.*

---

## `Agent`

**Import:** `from delfhos import Agent`

### Constructor

```python
Agent(
    tools:            Optional[List[Union[Connection, Callable, Any]]] = None,
    chat:             Optional[Chat] = None,
    memory:           Optional[Memory] = None,
    llm:              Optional[str] = None,
    light_llm:        Optional[str] = None,
    heavy_llm:        Optional[str] = None,
    vision_llm:       Optional[str] = None,
    system_prompt:    Optional[str] = None,
    on_confirm:       Optional[Callable] = None,
    providers:        Optional[Dict[str, str]] = None,
    verbose:          bool = False,
    prefilter_mode:   str = "auto",
    retry_count:      int = 1,
    sandbox:          str = "auto",
    sandbox_config:   Optional[Dict[str, Any]] = None,
    files:            Optional[List[str]] = None,
    allowed_libs:     Optional[List[str]] = None,
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `tools` | `list` | `None` | Service connections, `@tool` functions, or both |
| `chat` | `Chat` | `None` | Session memory; enables conversation context |
| `memory` | `Memory` | `None` | Persistent semantic memory across runs |
| `llm` | `str` | `None` | Single model for all operations |
| `light_llm` | `str` | `None` | Fast model for prefiltering; requires `heavy_llm` |
| `heavy_llm` | `str` | `None` | Strong model for code generation; requires `light_llm` |
| `vision_llm` | `str` | `None` | Override model for image/multimodal tasks |
| `system_prompt` | `str` | `None` | Instructions injected into every LLM call |
| `on_confirm` | `callable` | `None` | Custom approval callback `fn(request) → bool \| None` |
| `providers` | `dict` | `None` | API key overrides `{"google": "...", "openai": "..."}` |
| `verbose` | `bool` | `False` | Print full execution traces |
| `prefilter_mode` | `str` | `"auto"` | Tool routing strategy: `"auto"` \| `"filter"` \| `"search"` \| `"off"`. `"auto"` picks the mode based on action count (<10 → off, 10–49 → filter, ≥50 → search). |
| `retry_count` | `int` | `1` | Max retries on non-fatal execution errors |
| `sandbox` | `str` | `"auto"` | Execution isolation mode: `"auto"` \| `"docker"` \| `"local"` |
| `rerun_count` | `int` | `2` | Max rerun iterations when generated code calls `rerun()`. Each iteration triggers a fresh code-generation pass. See [`rerun()` guide](#how-to-use-rerun-for-adaptive-replanning). |
| `sandbox_config` | `dict` | `None` | Resource limit overrides for Docker mode (see [sandbox guide](#how-to-configure-the-execution-sandbox)) |
| `files` | `list[str]` | `None` | Absolute host paths to inject as read-only workspace files. In Docker mode each file is bind-mounted at `/workspace/<filename>`; in local mode the original paths are used. See [input file guide](#how-to-pass-input-files-to-the-agent-workspace). |
| `allowed_libs` | `list[str]` | `None` | PyPI package names to add to the sandbox import allowlist (e.g. `["pandas", "numpy"]`). In Docker mode packages are pip-installed automatically inside the container. In local mode they must already be installed in the host environment. See [allowed libs guide](#how-to-allow-extra-python-libraries-in-the-sandbox). |

### Methods

| Method | Signature | Description |
|--------|-----------|-------------|
| `start` | `() → self` | Initialize and start the agent |
| `stop` | `()` | Shut down and free resources |
| `run` | `(task: str, timeout: float = 60.0) → Response` | Execute task (blocking) |
| `arun` | `async (task: str, timeout: float = 60.0) → Response` | Execute task (async/await) |
| `submit` | `(task: str) → str` | Submit task (background, non-blocking); returns `task_id` |
| `poll` | `(task_id: str) → StreamSnapshot` | Live snapshot of a submitted task. See [polling guide](#how-to-poll-a-running-request). |
| `serve` | `(host: str = "127.0.0.1", port: int = 8080, api_key=None, allow_origins=None) → None` | Expose the agent over HTTP (blocking). See [HTTP guide](#how-to-expose-the-agent-over-http). |
| `asgi_app` | `(api_key=None, allow_origins=None) → FastAPI` | Return the HTTP API as an ASGI app to mount in your own server |
| `run_chat` | `(timeout: float = 120.0)` | Launch interactive terminal chat |
| `get_pending_approvals` | `() → list[dict]` | List requests awaiting approval |
| `approve` | `(request_id: str, response: str = "Approved") → bool` | Approve a pending request |
| `reject` | `(request_id: str, reason: str = "Rejected") → bool` | Reject a pending request |
| `info` | `() → dict` | Current agent state |
| `get_llm_config_string` | `() → str` | Human-readable LLM configuration |

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `agent_id` | `str` | Unique agent identifier |
| `usage` | `TokenUsage` | Cumulative token and cost statistics |
| `chat` | `Chat \| None` | Attached Chat instance |
| `memory` | `Memory \| None` | Attached Memory instance |
| `retry_count` | `int` | Current retry setting |
| `rerun_count` | `int` | Max rerun iterations (set on `agent.orchestrator.rerun_count`) |

### Context manager

```python
with Agent(tools=[...], llm="...") as agent:
    agent.run("...")
# agent.stop() called automatically
```

---

## `Response`

Returned by `agent.run()` and `agent.arun()`.

```python
@dataclass
class Response:
    text:       str                    # Final answer text
    status:     bool                   # True = success, False = failure
    error:      Optional[str]          # Error message if status is False
    cost_usd:   Optional[float]        # Estimated USD cost
    duration_ms: int                   # Wall-clock time in milliseconds
    trace:      Any                    # Full execution trace object
    files:      Dict[str, str]         # Output files: {label: absolute_host_path}
```

| Field | Type | Description |
|-------|------|-------------|
| `text` | `str` | Final answer text produced by the agent |
| `status` | `bool` | `True` if the task succeeded, `False` otherwise |
| `error` | `str \| None` | Error message when `status` is `False` |
| `cost_usd` | `float \| None` | Estimated USD cost for this task |
| `duration_ms` | `int` | Wall-clock execution time in milliseconds |
| `trace` | `Any` | Full execution trace object |
| `files` | `Dict[str, str]` | Output files saved during execution. Keys are logical labels (e.g. `"report"`, `"orders.csv"`); values are absolute host paths. Empty dict if no files were produced. See [output file guide](#how-to-extract-output-files-from-a-task-result). |

The `trace` object additionally carries `trace.tool_calls[i].description` (the
`desc=` passed at each tool call site) and `trace.utterances` (the lines the
agent printed, mirrored onto `trace.timeline` as `say` events).

---

## `StreamSnapshot` and `StreamEvent`

Returned by `agent.poll()`. A `StreamSnapshot` is a point-in-time view of a
request; see the [polling guide](#how-to-poll-a-running-request).

```python
@dataclass
class StreamSnapshot:
    task_id:       str
    state:         str               # "queued" | "running" | "done" | "error"
    task:          str
    elapsed_ms:    int
    events:        List[StreamEvent]  # unified live timeline
    output_so_far: str                # print() output captured so far
    result:        Optional[str]      # final answer once state == "done"
    error:         Optional[str]      # error message once state == "error"
    cost_usd:      Optional[float]
    tokens_used:   Optional[int]      # total tokens once terminal
    files:         Dict[str, str]
    trace:         Any                # full Trace once terminal

    @property
    def is_terminal(self) -> bool: ...  # True when state is "done" or "error"

    def to_dict(self) -> dict: ...      # JSON-serializable view (used by the HTTP API)

@dataclass
class StreamEvent:
    kind:        str               # "phase" | "tool" | "say"
    label:       str               # phase name, tool desc=, or printed line
    status:      str               # "running" | "success" | "error"
    started_at:  float             # unix epoch seconds
    duration_ms: Optional[int]
```

---

## `@tool` decorator

**Import:** `from delfhos import tool`

### Signature

```python
@tool(
    name:          Optional[str] = None,
    description:   Optional[str] = None,
    handle_error:  Union[bool, str, Callable, None] = True,
    confirm:       bool = True,
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `name` | `str` | function name | Override the tool name shown to the LLM |
| `description` | `str` | docstring | Override the description |
| `handle_error` | `bool \| str \| callable` | `True` | How to handle `ToolException`: `True` returns the message; a string returns that string; a callable receives the exception and returns a string |
| `confirm` | `bool` | `True` | Require human approval before execution |

### Auto-detected from the function

- **Tool name:** function name (snake_case preserved)
- **Description:** first docstring paragraph
- **Parameters:** Python type annotations including `TypedDict`
- **Return type:** return annotation

### `ToolException`

Raise inside a `@tool` function to send a recoverable error back to the LLM:

```python
from delfhos import ToolException

@tool
def find_order(order_id: str) -> dict:
    """Look up an order by ID."""
    if not order_id.startswith("ORD-"):
        raise ToolException("Order IDs must start with 'ORD-'. Please check the format.")
    ...
```

---

## `Gmail`

**Import:** `from delfhos import Gmail`

```python
Gmail(
    oauth_credentials: Optional[str] = None,    # Path to OAuth JSON file
    service_account:   Optional[str] = None,    # Path to Service Account JSON
    delegated_user:    Optional[str] = None,    # Email to impersonate (SA only)
    allow:             Optional[Union[str, List[str]]] = None,
    confirm:           Union[bool, List[str], None] = True,
    name:              str = "gmail",
    metadata:          Optional[Dict[str, Any]] = None,
)
```

| Action | Description |
|--------|-------------|
| `read` | List and read emails, threads, labels |
| `send` | Compose and send emails with optional attachments |

---

## `SQL`

**Import:** `from delfhos import SQL`

```python
SQL(
    url:      Optional[str] = None,             # Full connection string
    host:     Optional[str] = None,
    port:     Optional[int] = None,
    database: Optional[str] = None,
    user:     Optional[str] = None,
    password: Optional[str] = None,
    db_type:  str = "postgresql",               # "postgresql" | "mysql" | "mariadb"
    allow:    Optional[Union[str, List[str]]] = None,
    confirm:  Union[bool, List[str], None] = True,
    name:     str = "sql",
    metadata: Optional[Dict[str, Any]] = None,
)
```

| Action | Description |
|--------|-------------|
| `schema` | Inspect table schemas and column definitions |
| `query` | Execute SELECT queries |
| `write` | Execute INSERT, UPDATE, DELETE statements |

---

## `Sheets`

**Import:** `from delfhos import Sheets`

```python
Sheets(
    oauth_credentials: Optional[str] = None,
    service_account:   Optional[str] = None,
    delegated_user:    Optional[str] = None,
    allow:    Optional[Union[str, List[str]]] = None,
    confirm:  Union[bool, List[str], None] = True,
    name:     str = "sheets",
    metadata: Optional[Dict[str, Any]] = None,
)
```

| Action | Description |
|--------|-------------|
| `read` | Read cell values and ranges |
| `write` | Write or update cell values |
| `create` | Create new spreadsheets or sheets |
| `format` | Apply formatting (colors, fonts, borders) |
| `chart` | Create charts from data ranges |
| `batch` | Execute multiple operations in one request |

---

## `Drive`

**Import:** `from delfhos import Drive`

```python
Drive(
    oauth_credentials: Optional[str] = None,
    service_account:   Optional[str] = None,
    delegated_user:    Optional[str] = None,
    allow:    Optional[Union[str, List[str]]] = None,
    confirm:  Union[bool, List[str], None] = True,
    name:     str = "drive",
    metadata: Optional[Dict[str, Any]] = None,
)
```

| Action | Description |
|--------|-------------|
| `search` | Search files and folders by name, type, or metadata |
| `get` | Download or read file content |
| `create` | Upload or create files and folders |
| `update` | Update file content or metadata |
| `delete` | Permanently delete files or folders |
| `list_permissions` | List sharing permissions |
| `share` | Add sharing permissions |
| `unshare` | Remove sharing permissions |

---

## `Docs`

**Import:** `from delfhos import Docs`

```python
Docs(
    oauth_credentials: Optional[str] = None,
    service_account:   Optional[str] = None,
    delegated_user:    Optional[str] = None,
    allow:    Optional[Union[str, List[str]]] = None,
    confirm:  Union[bool, List[str], None] = True,
    name:     str = "docs",
    metadata: Optional[Dict[str, Any]] = None,
)
```

| Action | Description |
|--------|-------------|
| `read` | Read document content |
| `create` | Create a new document |
| `update` | Update document text or insert content |
| `format` | Apply formatting (headings, bold, lists) |
| `delete` | Delete a document |

---

## `Calendar`

**Import:** `from delfhos import Calendar`

```python
Calendar(
    oauth_credentials: Optional[str] = None,
    service_account:   Optional[str] = None,
    delegated_user:    Optional[str] = None,
    allow:    Optional[Union[str, List[str]]] = None,
    confirm:  Union[bool, List[str], None] = True,
    name:     str = "calendar",
    metadata: Optional[Dict[str, Any]] = None,
)
```

| Action | Description |
|--------|-------------|
| `list` | List events in a date range |
| `get` | Get details of a specific event |
| `create` | Create a new event with attendees |
| `update` | Update an existing event |
| `delete` | Delete an event |
| `respond` | Accept, decline, or tentatively accept an event |

---

## `WebSearch`

**Import:** `from delfhos import WebSearch`

```python
WebSearch(
    llm:      str,                              # REQUIRED — Gemini or OpenAI model
    api_key:  Optional[str] = None,            # Falls back to env var
    allow:    Optional[Union[str, List[str]]] = None,
    confirm:  Union[bool, List[str], None] = True,
    name:     str = "websearch",
    metadata: Optional[Dict[str, Any]] = None,
)
```

> **Important:** `llm` is required. Only Gemini and OpenAI models are supported. Claude/Anthropic models are not supported for WebSearch.

| Action | Description |
|--------|-------------|
| `search` | Search the web and return summarized results |

---

## `APITool`

**Import:** `from delfhos import APITool`

```python
APITool(
    spec:        str,                              # URL or file path to OpenAPI 3.x spec
    base_url:    Optional[str] = None,            # Override spec's servers[0].url
    headers:     Optional[Dict[str, str]] = None, # HTTP headers injected into every request
    params:      Optional[Dict[str, str]] = None, # Query params injected into every request
    path_params: Optional[Dict[str, str]] = None, # Path params injected into every request URL
    name:        Optional[str] = None,            # Override auto-derived tool name
    allow:       Optional[Union[str, List[str]]] = None,
    confirm:     Union[bool, List[str], None] = True,
    cache:       bool = False,                    # Cache compiled manifest to ~/delfhos/api_cache/
    enrich:      bool = False,                    # Use LLM to improve descriptions/schemas
    llm:         Optional[str] = None,            # Model for enrichment; defaults to Agent's light_llm
    sample:      bool = True,                     # Capture real response schemas in background
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `spec` | `str` | — | URL or file path to an OpenAPI 3.x JSON or YAML spec |
| `base_url` | `str` | `None` | Override for the API base URL; auto-extracted from spec if absent |
| `headers` | `dict` | `None` | HTTP headers injected into every request (e.g. `{"Authorization": "Bearer ..."}`, `{"X-API-Key": "..."}`) |
| `params` | `dict` | `None` | Query params appended to every request URL (e.g. `{"api_key": "..."}`) |
| `path_params` | `dict` | `None` | URL path variables injected into every request. Values are substituted into path templates like `/api/{globalCompanyId}/...` and stripped from the agent-visible function signature so the LLM never sees or passes them. |
| `name` | `str` | `None` | Custom label for this connection; auto-derived from spec title/hostname |
| `allow` | `list` | `None` | Restrict which endpoints the agent can use (function names from `inspect()`) |
| `confirm` | `bool \| list` | `True` | Require approval before listed endpoints execute |
| `cache` | `bool` | `False` | Reuse compiled manifest from disk; useful for large specs |
| `enrich` | `bool` | `False` | Run an LLM pass to improve endpoint descriptions and infer response schemas. Cached after first run — zero cost on subsequent runs. |
| `llm` | `str` | `None` | Model used for enrichment. If omitted, falls back to the Agent's `light_llm` (or `llm` in single-model mode). Only used when `enrich=True`. |
| `sample` | `bool` | `True` | After each successful API call, infer the response schema from real data and persist it to the cache. No LLM, no cost, zero latency impact. |

### Class methods

```python
APITool.inspect(spec: str, verbose: bool = False, base_url: str = None, cache: bool = False) → dict
```

Returns a dict with `tool`, `api`, `methods` (list of function names), and `total`. Pass `verbose=True` for method + path + description.

### Spec limits

By default, up to 100 endpoints are compiled from a spec. Use `allow=` to pick a subset from larger specs (e.g. Stripe, GitHub). The limit is bypassed automatically when `allow=` is set.

### Cache location

| File | Contents |
|------|----------|
| `~/delfhos/api_cache/{tool}_{hash}/manifest.json` | Compiled (and enriched) endpoint manifest |
| `~/delfhos/api_cache/{tool}_{hash}/sampled_schemas.json` | Real response schemas captured from live API calls |

---

## `Chat`

**Import:** `from delfhos import Chat`

```python
Chat(
    keep:           int = 10,
    summarize:      bool = True,
    persist:        bool = False,
    path:           Optional[str] = None,
    namespace:      str = "default",
    summarizer_llm: Optional[str] = None,
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `keep` | `int` | `10` | Max messages before auto-summarization; `0` = always summarize |
| `summarize` | `bool` | `True` | Enable automatic message compression |
| `persist` | `bool` | `False` | Save to SQLite (True) or keep in RAM (False) |
| `path` | `str` | `None` | Custom SQLite path; defaults to `~/delfhos/chat/{namespace}.db` |
| `namespace` | `str` | `"default"` | Isolates multiple chat histories |
| `summarizer_llm` | `str` | `None` | LLM for summarization; required when `summarize=True` |

### Methods

| Method | Signature | Description |
|--------|-----------|-------------|
| `append` | `(role: str, content: str)` | Add a message |
| `clear` | `()` | Remove all messages |
| `by_role` | `(role: str) → List[Message]` | Filter messages by role |
| `needs_compression` | `() → bool` | True if message count exceeds `keep` |

### Properties

| Property | Type | Description |
|----------|------|-------------|
| `messages` | `List[dict]` | All messages as `{"role": ..., "content": ...}` |
| `summary` | `str \| None` | The current auto-generated summary |

### Iteration and indexing

```python
len(chat)          # Message count
chat[0]            # First message
chat[0:5]          # Slice
for msg in chat:   # Iterate
    ...
```

---

## `LLMConfig`

**Import:** `from delfhos import LLMConfig`

Use `LLMConfig` to configure native providers (Google/OpenAI/Anthropic) and any OpenAI-compatible custom endpoint — local models (Ollama, LM Studio, vLLM), open-source providers (Groq, Together AI, Anyscale), or private enterprise servers. Pass a `LLMConfig` wherever a model string is accepted.

```python
LLMConfig(
    model:    str,                           # Model name as the endpoint expects it
    base_url: Optional[str] = None,          # Base URL of the OpenAI-compatible API
    api_key:  Optional[str] = None,          # API key; falls back to OPENAI_API_KEY env var
    headers:  Optional[Dict[str, str]] = None, # Extra HTTP headers for every request
    settings: Optional[Dict[str, Any]] = None, # Per-model generation params
    provider: str = "auto",                  # auto|google|openai|anthropic
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `model` | `str` | — | Model identifier (e.g. `"llama3.2"`, `"mistral-7b-instruct"`) |
| `base_url` | `str` | `None` | API base URL; defaults to `OPENAI_BASE_URL` env var, then `https://api.openai.com/v1` |
| `api_key` | `str` | `None` | Bearer token; defaults to `OPENAI_API_KEY`. Pass `"local"` for auth-free local servers |
| `headers` | `Dict[str, str]` | `None` | Extra HTTP headers sent with every request. Use for enterprise servers that require tenant IDs, session tokens, or multiple auth values |
| `settings` | `Dict[str, Any]` | `None` | Per-model generation settings applied everywhere this config is used. Supported keys: `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop` |
| `provider` | `str` | `"auto"` | Provider routing mode: `"auto"`, `"google"`, `"openai"`, `"anthropic"` |

### `settings` examples

```python
# Dict style
LLMConfig(
    model="gemini-3.5-flash",
    settings={
        "temperature": 0.8,
        "top_k": 40,
        "max_tokens": 1200,
    },
)

# Custom endpoint style (OpenAI-compatible)
LLMConfig(
    model="llama3.2",
    base_url="http://localhost:11434/v1",
    settings={"temperature": 0.2},
)

# Fluent style
cfg = LLMConfig(model="gemini-3.5-flash")
cfg.with_settings(temperature=0.8, top_k=40, max_tokens=1200)
```

Aliases like `"top-k"` and `"max-tokens"` are normalized automatically.

You can use native model strings directly, or use `LLMConfig` when you want per-model `settings` while still using native providers.

---

## `Memory`

**Import:** `from delfhos import Memory`

```python
Memory(
    guidelines:      Optional[str] = None,
    path:            Optional[str] = None,
    namespace:       str = "default",
    embedding_model: str = "all-MiniLM-L6-v2",
)
```

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `guidelines` | `str` | `None` | Preamble prepended to retrieved context |
| `path` | `str` | `None` | Custom SQLite path; defaults to `~/delfhos/memory/{namespace}.db` |
| `namespace` | `str` | `"default"` | Isolates memory across agents or users |
| `embedding_model` | `str` | `"all-MiniLM-L6-v2"` | Any sentence-transformers model for semantic search |

Any model from [sbert.net/docs/pretrained_models.html](https://www.sbert.net/docs/pretrained_models.html) or HuggingFace is accepted. Popular choices:

| Model | Size | Notes |
|-------|------|-------|
| `all-MiniLM-L6-v2` | ~90 MB | Default — good balance of speed and quality |
| `all-mpnet-base-v2` | ~420 MB | Higher quality |
| `nomic-embed-text` | ~270 MB | Instruction-based, auto-detected prefixes |
| `bge-small-en-v1.5` | ~130 MB | BGE family, compact |
| `sentence-transformers/all-MiniLM-L6-v2` | ~90 MB | Full HuggingFace ID also accepted |

### Methods

| Method | Signature | Description |
|--------|-----------|-------------|
| `save` | `(content: str)` | Store one or more facts (split by newline) |
| `add` | `(content: str)` | Store text or read from a `.txt` / `.md` file path |
| `search` | `(query: str, top_k: int = 5, threshold: float = 0.3) → List[Fact]` | Semantic similarity search |
| `retrieve` | `(query: str, top_k: int = 5, threshold: float = 0.3) → str` | Same as `search`, returns a string |
| `context` | `() → str` | All facts for this namespace as a string |
| `clear` | `()` | Delete all facts in this namespace |
| `backfill_embeddings` | `()` | Generate embeddings for any facts missing them |

### `Fact` dataclass

```python
@dataclass
class Fact:
    content:  str        # The fact text
    saved_at: datetime   # When it was stored
    namespace: str       # Memory namespace
```

### Iteration and indexing

```python
len(memory)          # Fact count
memory[0]            # First fact
memory[0:5]          # Slice
for fact in memory:  # Iterate
    ...
```

---

## Error classes

**Import:** `from delfhos import <ErrorClass>` or `from delfhos.errors import <ErrorClass>`

All errors extend `DelfhosConfigError` and display a structured message with an error code and resolution hint.

| Error class | Code prefix | When raised |
|-------------|-------------|-------------|
| `ModelConfigurationError` | `ERR-MODEL-*` | Invalid or missing LLM configuration |
| `AgentConfirmationError` | `ERR-AGENT-*` | Invalid `confirm` or `on_confirm` value |
| `MemorySetupError` | `ERR-MEM-001` | Memory database initialization failure |
| `MemoryRetrievalError` | `ERR-MEM-002` | Embedding-based memory retrieval failed at runtime |
| `ToolExecutionError` | `ERR-TOOL-001` | Unhandled error during tool execution |
| `ToolDefinitionError` | `ERR-TOOL-002` | `@tool` function has an invalid schema |
| `SQLSchemaError` | `ERR-TOOL-003` | SQL schema introspection failed for a connection |
| `EnvironmentKeyError` | `ERR-ENV-*` | Required environment variable missing |
| `ConnectionConfigurationError` | `ERR-CONN-001` | Invalid connection parameters (also raised by `APITool`) |
| `ConnectionFileNotFoundError` | `ERR-CONN-002` | Required credentials file not found |
| `LLMExecutionError` | `ERR-LLM-001` | LLM API call failed |
| `CodeGenerationError` | `ERR-LLM-002` | LLM returned no executable code for the task |
| `PrefilterError` | `ERR-LLM-003` | Prefilter LLM call failed; tool selection unavailable |
| `ConversationCompressionError` | `ERR-LLM-004` | Chat-history summarization/compression failed |
| `SandboxExecutionError` | `ERR-SANDBOX-001` | Generated Python code failed inside the execution sandbox |
| `OptionalDependencyError` | `ERR-REQ-*` | Optional package not installed |
| `ApprovalRejectedError` | `ERR-APPROVAL-*` | Human rejected the approval request |

---

## Supported LLM models

Pass a model name string for native providers, or a `LLMConfig` for any OpenAI-compatible endpoint.

| Family | Examples | Notes |
|--------|---------|-------|
| Google Gemini | `gemini-3.5-flash`, `gemini-3.1-flash-lite`, `gemini-3.1-pro` | Recommended; requires `GOOGLE_API_KEY` |
| OpenAI | `gpt-5.5`, `gpt-4o-mini`, `o3`, `o4-mini` | Requires `OPENAI_API_KEY` |
| Anthropic Claude | `claude-opus-4-8`, `claude-sonnet-4-6`, `claude-3-haiku` | Requires `ANTHROPIC_API_KEY` |
| Any OpenAI-compatible | `LLMConfig(model=..., base_url=...)` | Ollama, vLLM, Groq, Together AI, LM Studio, etc. |

> Claude models are not supported as the `llm` for `WebSearch`.

> `LLMConfig` requires `pip install openai`.

---

## Environment variables

| Variable | Used by | Description |
|----------|---------|-------------|
| `GOOGLE_API_KEY` | Agent, WebSearch | Google Gemini API key |
| `OPENAI_API_KEY` | Agent, LLMConfig | OpenAI API key (also default for LLMConfig custom endpoints) |
| `ANTHROPIC_API_KEY` | Agent | Anthropic Claude API key |
| `OPENAI_BASE_URL` | LLMConfig | Default base URL for OpenAI-compatible endpoints |

Delfhos loads `.env` files automatically via `python-dotenv`.

---

## File locations

| Path | Purpose |
|------|---------|
| `~/delfhos/pricing.json` | Token pricing configuration (auto-created) |
| `~/delfhos/memory/{namespace}.db` | Persistent Memory SQLite database |
| `~/delfhos/chat/{namespace}.db` | Persistent Chat SQLite database |
| `~/.config/oauth_gmail.json` | Gmail OAuth token cache |
| `~/.config/oauth_sheets.json` | Sheets OAuth token cache |
| `~/.config/oauth_drive.json` | Drive OAuth token cache |
| `~/.config/oauth_docs.json` | Docs OAuth token cache |
| `~/.config/oauth_calendar.json` | Calendar OAuth token cache |

---

## Public API surface

Everything exported from `delfhos`:

```python
from delfhos import (
    # Core
    Agent,
    LLMConfig,

    # Service connections
    Gmail,
    SQL,
    Sheets,
    Drive,
    Docs,
    Calendar,
    WebSearch,
    APITool,

    # Custom tools
    tool,
    ToolException,
    DelfhosToolWarning,

    # Memory
    Chat,
    Memory,
)
```

---

---
