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

# Source-owned memories

> Upsert and delete source-of-record memories by (endUserId, source, externalId, version) with idempotent, crash-safe, out-of-order-safe delivery.

Use the lifecycle surface when memories mirror records your database owns — CRM rows, profile fields, tickets. Unlike append-only `save`, these memories have a durable identity and version history, so corrections and deletes from the source of truth apply exactly once, in order, no matter how messy delivery gets.

## The identity model

A source-owned memory is identified by `(endUserId, source, externalId)`. `version` must increase whenever the source record changes.

```ts theme={null}
await conare.memories.upsertSource({
  endUserId: "u_123",
  source: "crm-profile",
  externalId: "profile_123",
  version: 7,
  occurredAt: "2026-07-16T10:30:00Z",
  content: "User prefers smaller islands.",
  idempotencyKey: "profile_123:v7",
});
```

## Delivery guarantees

Mutations are synchronous, and the rules are simple:

* **Retrying the same version + payload** returns the same durable receipt. Re-sending after a crash is safe — that *is* the crash-recovery story.
* **A different payload for an existing version** returns `409`. Versions are immutable.
* **A delayed older version** returns `409` and never overwrites newer content or resurrects a deleted memory.

Deletes follow the same versioning:

```ts theme={null}
await conare.memories.deleteSource({
  endUserId: "u_123",
  source: "crm-profile",
  externalId: "profile_123",
  version: 8,
  occurredAt: "2026-07-17T09:00:00Z",
  idempotencyKey: "profile_123:v8",
});
```

The delete is durable: retries converge to the original receipt, and delayed upserts with lower versions cannot recreate the memory.

## Bootstrapping with an outbox

Bulk-mirror existing records through `lifecycleBatch` — up to 100 items and 1 MiB of aggregate content per call. There is no server-side import job; a client-side outbox that re-sends after a crash is the entire resume story:

```ts theme={null}
const result = await conare.memories.lifecycleBatch({
  endUserId: "u_123",
  items: [{
    source: "crm-profile",
    externalId: "profile_123",
    version: 7,
    occurredAt: "2026-07-16T10:30:00Z",
    content: "User prefers smaller islands.",
  }],
});

for (const [index, item] of result.items.entries()) {
  if (item.success || item.code === "stale_source_version") {
    outbox.markDone(index); // that version (or newer) is durable
  } else {
    outbox.scheduleRetry(index, item.code);
  }
}
```

<Note>
  `result.success` only means "the batch was processed" — always check per-item outcomes. Mark a row done on `success` **or** `stale_source_version` (both mean the version is already durable), then send the next batch.
</Note>

## Reading back

`GET /api/v1/memories/{externalId}` returns the memory and its latest applied receipt — useful for verifying convergence after a migration.
