# Failure behaviour

> When we cannot be reached, the SDK builds the request from a local tail of recent turns and your model call goes ahead. Memory never fails the model call.

Memory must never be the reason your agent stops answering. The SDKs are built around that.

## decorate falls back to the local tail

The SDK keeps a **local tail** for each memory, in your process: your instructions and the newest turns of the last request, up to a size you set. When `decorate` cannot get an answer from us, it returns a request built from that tail plus the new messages, and your model call goes ahead.

It falls back on:

- a network error or a timeout (15 seconds by default, `timeoutMs` / `timeout`);
- a `5xx` response;
- `429` (rate limited, or the plan's quota used up) and `408`.

Every fallback is reported to your `onError` callback (`on_error` in Python) with the operation and memory id, so you can log it.

It does **not** fall back on other `4xx` responses. A malformed request, a revoked key or an unknown memory id is a bug for you to see, so `decorate` throws a `ThreadlessError` with the status, a `code`, a `message` and a `hint` saying what to do about it.

```ts
const memory = new Threadless({
  timeoutMs: 15_000,
  tailTokens: 60_000,
  onError: (error, { operation, memoryId }) => log.warn({ error, operation, memoryId }),
});
```

```python
memory = Threadless(
    timeout=15.0,
    tail_tokens=60_000,
    on_error=lambda error, context: log.warning("threadless %s %s", context, error),
)
```

## What the tail holds

- The latest instructions you sent.
- The newest history that fits in `tailTokens` (60,000 estimated tokens by default), cut at a user message so a tool call is never separated from its output.
- If not even one turn fits, the newest user message onward.

The tail is refreshed from every successful `decorate`, so it mirrors what we last sent you, facts block and recall included. It lives in memory only: a process that restarts has no tail, and the first fallback after a restart carries just the new messages and instructions. The SDK keeps tails for the 200 most recently used memories.

While the fallback is in use, nothing is forgotten, no facts are added and nothing is recalled; the history we hold resumes from the next successful call. Messages sent during an outage are not stored by us.

## report never throws

`report` runs in the background. If it fails, `onError` is told and the call is not retried; your agent is never interrupted. If a report is still in flight when the next `decorate` on the same memory starts, the SDK waits up to two seconds for it, so the next request sees the reply.

## Errors from the HTTP API

Every error has one shape:

```json
{ "error": { "code": "not_found", "message": "…", "hint": "Use an id that POST /v1/memories returned." } }
```

| Code | Status | Meaning |
|---|---|---|
| `invalid_request` | 400 | The body does not match the schema. |
| `unauthorized` | 401 | The key is missing, revoked or not a project key. |
| `not_found` | 404 | No memory with this id in this key's project. |
| `rate_limited` | 429 | Too many requests for this key. |
| `quota_exceeded` | 429 | The plan's allowance is used up. |
| `internal` | 500 | Something went wrong on our side. |
