> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nora.my/llms.txt
> Use this file to discover all available pages before exploring further.

# Errors & failure model

> Typed error classes, the four failure layers, structured logging

The SDK splits failures into four layers with a strict rule about which throw and which come back as a result. Use typed error classes (not string matching) to branch, and let the structured logger surface retries so nothing is silent.

## Typed errors

Branch on error class, not on `.message`. Every error carries a stable `.code`, the run's `.traceId` (when applicable), and a server-adjudicated `.retryable` flag.

```ts theme={null}
import { NoraAuthError, NoraProviderError } from "@conscience-technology/nora-sdk";

try {
  await nora.improvements.approve("imp_1");
} catch (e) {
  if (e instanceof NoraAuthError) {
    // Auth / scope / subject issues.
    // Example: a `read` token calling approve → e.code === "auth.scope_denied"
  } else if (e instanceof NoraProviderError) {
    // Upstream LLM provider issue (may be retryable — check e.retryable)
  }
}
```

**Hierarchy**:

```
NoraError (base)
  ├── NoraAuthError       — 401 / scope denied / missing onBehalfOf
  ├── NoraRequestError    — flow not found, not published, missing required scope
  ├── NoraAccessError     — resource-level access rule refused
  ├── NoraProviderError   — upstream LLM provider (retryable per .retryable)
  └── NoraPlatformError   — Nora server 5xx, transport failure
```

## The four failure layers

| Layer                 | Examples                                                                         | SDK behaviour                                                      |
| --------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------ |
| **Transport / auth**  | Network drop · 401 · scope denied · missing `onBehalfOf` subject                 | **Throws**                                                         |
| **Request**           | Flow slug doesn't exist · Flow not published · required scope missing on the PAT | **Throws**                                                         |
| **Execution result**  | Turn budget exhausted · cost cap hit · a tool failed · `needs_review`            | **Returned as result** (`traceId` + partial `outputs` + `failure`) |
| **Constraint notice** | Tool-call cap reached · retrieval returned nothing · output truncated            | **Not a failure** — carried on `notices[]`                         |

**Read this rule carefully.** If a Flow runs but fails mid-execution, the SDK does **not** throw — it returns `{ status: "failed", traceId, outputs: <partial>, failure: {...}, notices: [...] }`. Silent try/catch that swallows only exceptions will miss these. Always check `.status` on the return value of `flows.run`.

### Why the split

Anything that means "the request didn't reach the graph" throws, so your caller code fails loud and early. Anything that means "the graph ran but the answer isn't clean" comes back as a result, so you get:

* the `traceId` for the trace UI and for `feedback` / `signals.report`,
* whatever partial `outputs` were produced,
* a machine-readable `failure.code` for retry / degrade decisions,
* constraint `notices[]` that shaped the run (surface these in your admin UI, not to end users).

## Logging

Structured and redaction-first by default — tokens, secrets, `onBehalfOf` values, and request/response bodies **do not** land in logs unless you turn `debug: true` on.

```ts theme={null}
const nora = createClient({
  // …
  logger: pino(),                       // pipe SDK logs into your stack
  onLog: (e) => metrics.count(e.event), // observe SDK internal events (retries, circuit, fallback)
  debug: false,                         // true = include request/response bodies
});
```

Retries are always logged — each attempt records `attempt`, the failure `code`, and the decision. No silent retries.

Recommended:

* Route `logger` at Nora's stream (pino / winston / bunyan) so log level and destination match the rest of your service.
* Wire `onLog` into your metrics — retry rate, circuit-open rate, and provider-fallback frequency are the leading indicators when something upstream is degrading.
* Keep `debug` off in production. Turn it on per-request via a scoped clone when reproducing an incident.

## Common patterns

### Retry only on `retryable`

```ts theme={null}
try {
  return await nora.improvements.approve(id);
} catch (e) {
  if (e instanceof NoraProviderError && e.retryable) {
    // The SDK already retried within maxRetries — retry at your application layer
    // only if you have more context (e.g. de-dupe key, longer backoff).
    return await withBackoff(() => nora.improvements.approve(id));
  }
  throw e;
}
```

### Degrade on partial

```ts theme={null}
const r = await nora.flows.run("support", input);

if (r.status === "failed") {
  return { text: fallbackAnswer, traceId: r.traceId, failure: r.failure };
}
if (r.status === "needs_review") {
  await queueForReview(r.traceId);
  return { text: r.outputs.answer, review: true };
}
return { text: r.outputs.answer };
```

### Distinguish `feedback` from `signals.report`

```ts theme={null}
// Answer quality (what the user thought of the reply)
await nora.feedback(traceId, { rating: "dislike", correction: { before, after } });

// Business outcome (what actually happened downstream)
await nora.signals.report(traceId, { outcome: "reopened", reason: "asked again next day" });
```

Mixing these means detectors and clusters get noisy — keep the intents separate.
