---
tags: [errors, exceptions, fail-safe, resilience, savepoints]
modules: [src/arandu/write/, src/arandu/read/, src/arandu/background/]
applies_to: [handlers, services, pipelines]
confidence: inferred
---
# Pattern: Fail-Safe Error Handling

<!-- vibeflow:auto:start -->
## What
The SDK never raises exceptions from pipeline operations. All failures degrade gracefully to empty/safe defaults. A custom exception hierarchy exists for explicit SDK errors, but pipeline internals catch everything.

## Where
- `src/arandu/exceptions.py` — Exception hierarchy
- All pipeline modules (write, read, background)

## The Pattern
1. **Exception hierarchy**: `MemoryError` base → `ExtractionError`, `ResolutionError`, `ReconciliationError`, `RetrievalError`, `UpsertError`. All support `cause` for chaining.
2. **Pipeline fail-safe**: Top-level pipeline functions catch `Exception` and return empty results rather than propagating.
3. **LLM fail-safe**: LLM call failures return `None`; callers check for `None` and use defaults.
4. **Reconciliation fail-safe**: On LLM failure, default to `ADD` (prefer duplicates over lost data).
5. **Savepoint isolation**: DB operations in `session.begin_nested()` — individual failures don't break the pipeline.
6. **Event survives**: Write pipeline creates the MemoryEvent first (flushed), then wraps remaining stages in try/except so the event persists even if extraction/upsert fails.

## Rules
- Pipeline functions catch all exceptions and return safe defaults — never let exceptions propagate to the caller.
- LLM-calling helpers return `None` on failure; callers check `if raw is None: return []`.
- Reconciliation defaults to `ADD` on any failure (losing nothing is safer than losing data).
- Use `logger.warning()` for recoverable failures, `logger.debug()` for expected/non-critical issues.
- Single-pass extraction retries once on failure before returning empty.

## Examples from this codebase
File: src/arandu/exceptions.py
```python
class MemoryError(Exception):
    def __init__(self, message: str, *, cause: Exception | None = None) -> None:
        super().__init__(message)
        if cause is not None:
            self.__cause__ = cause

class ExtractionError(MemoryError): ...
class ResolutionError(MemoryError): ...
```

File: src/arandu/write/pipeline.py
```python
try:
    extraction = await extract_from_message(...)
    async with session.begin_nested():
        entity_map = await resolve_entities(...)
        decisions = await reconcile_facts(...)
        upsert_result = await execute_upsert(...)
except Exception as e:
    logger.warning("write_pipeline: failed after event creation: %s", e)
    return {"event_id": event_id, "facts_added": [], ...}  # safe default
```
<!-- vibeflow:auto:end -->
