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

# Session memory

> Save and read per-conversation state with ctx.memory: multi-step flows, carts, counters, and temporary data without an external database.

## What is `ctx.memory`?

`ctx.memory` is a per-session key-value store backed by Redis. It is automatically available when the request comes from an active conversation (has a `socketId`) and the company has a workflow API key configured.

Typical use cases:

* **Multi-step flows** — remember which step the user is on
* **Shopping carts** — accumulate products during the conversation
* **Counters** — limit login attempts, retry tracking
* **Temporary preferences** — language, format, filters during the session

## Quick start

```typescript index.ts theme={null}
import { define, z } from "@jelou/functions";

export default define({
  name: "registration-flow",
  description: "Multi-step registration with session memory",
  input: z.object({
    answer: z.string().optional(),
  }),
  handler: async (input, ctx) => {
    if (!ctx.memory.available) {
      return { error: "Memory not available outside of a conversation" };
    }

    const step = await ctx.memory.get("step", "start");

    if (step === "start") {
      await ctx.memory.set("step", "name", 3600);
      return { question: "What is your name?" };
    }

    if (step === "name") {
      await ctx.memory.set("name", input.answer || "", 3600);
      await ctx.memory.set("step", "email", 3600);
      return { question: "What is your email?" };
    }

    const name = await ctx.memory.get("name", "");
    await ctx.memory.delete("step");
    await ctx.memory.delete("name");
    return { completed: true, name, email: input.answer };
  },
});
```

## Check availability

```typescript theme={null}
if (!ctx.memory.available) {
  ctx.log("Memory not available — no socketId or workflow API key");
  return { error: "memory_unavailable" };
}
```

<Warning>
  `ctx.memory.available` is `false` when the `socketId` is missing (requests outside a conversation) or the workflow API key is not configured. Calling methods on an unavailable client throws an `Error`.
</Warning>

## Primitives vs JSON

Use `set()`/`get()` for simple values and `setJson()`/`getJson()` for objects:

```typescript theme={null}
await ctx.memory.set("step", "confirmation", 3600);
const step = await ctx.memory.get("step", "start");

await ctx.memory.setJson("cart", { items: [], total: 0 }, 86400);
const cart = await ctx.memory.getJson("cart", { items: [], total: 0 });
```

The return type of `get()` matches the type of the default value:

```typescript theme={null}
const name = await ctx.memory.get("name", "anonymous");   // string
const attempts = await ctx.memory.get("attempts", 0);     // number
const verified = await ctx.memory.get("verified", false); // boolean
```

## TTL (time to live)

All values expire automatically. TTL is specified in seconds.

| Method      | TTL          | Maximum      |
| ----------- | ------------ | ------------ |
| `set()`     | Optional     | 86,400 (24h) |
| `setJson()` | **Required** | 86,400 (24h) |

```typescript theme={null}
await ctx.memory.set("step", "payment");                  // no explicit TTL
await ctx.memory.set("step", "payment", 1800);            // expires in 30 min
await ctx.memory.setJson("cart", cart, 86400);            // expires in 24h (maximum)
```

## Limits

| Restriction           | Value                    |
| --------------------- | ------------------------ |
| Max length of `set()` | 255 characters           |
| Max TTL               | 86,400 seconds (24h)     |
| Scope                 | Per session (`socketId`) |

Values from `set()` that exceed 255 characters throw an `Error`. For larger data, use `setJson()`.

## Common patterns

<Tabs>
  <Tab title="Multi-step flow">
    ```typescript theme={null}
    handler: async (input, ctx) => {
      const step = await ctx.memory.get("step", "start");

      if (step === "start") {
        await ctx.memory.set("step", "data", 3600);
        return { next: "data" };
      }

      if (step === "data") {
        await ctx.memory.set("name", input.name, 3600);
        await ctx.memory.set("step", "confirm", 3600);
        return { next: "confirm", name: input.name };
      }

      const name = await ctx.memory.get("name", "");
      await ctx.memory.delete("step");
      await ctx.memory.delete("name");
      return { completed: true, name };
    }
    ```
  </Tab>

  <Tab title="Shopping cart">
    ```typescript theme={null}
    interface Cart {
      items: Array<{ id: string; name: string; price: number; quantity: number }>;
      total: number;
    }

    handler: async (input, ctx) => {
      const empty: Cart = { items: [], total: 0 };
      const cart = await ctx.memory.getJson<Cart>("cart", empty);

      cart.items.push({
        id: input.productId,
        name: input.name,
        price: input.price,
        quantity: input.quantity,
      });
      cart.total = cart.items.reduce(
        (sum, i) => sum + i.price * i.quantity, 0
      );

      await ctx.memory.setJson("cart", cart, 86400);
      return { cart };
    }
    ```
  </Tab>

  <Tab title="Attempt limit">
    ```typescript theme={null}
    handler: async (input, ctx) => {
      const attempts = await ctx.memory.get("pin_attempts", 0);

      if (attempts >= 3) {
        return { blocked: true, message: "Too many attempts" };
      }

      const valid = input.pin === "1234";
      if (!valid) {
        await ctx.memory.set("pin_attempts", attempts + 1, 1800);
        return { blocked: false, error: "Incorrect PIN" };
      }

      await ctx.memory.delete("pin_attempts");
      return { blocked: false, verified: true };
    }
    ```
  </Tab>
</Tabs>

## Error handling

```typescript theme={null}
import { MemoryApiError } from "@jelou/functions";

try {
  await ctx.memory.set("step", "payment", 3600);
} catch (err) {
  if (err instanceof MemoryApiError) {
    ctx.log("Memory API failed", { status: err.status, code: err.code });
    if (err.isRateLimit()) {
      return { error: "rate_limit", retryAfter: 2 };
    }
  }
  throw err;
}
```

## When to use `ctx.memory` vs a database?

|                 | `ctx.memory`                        | External database                     |
| --------------- | ----------------------------------- | ------------------------------------- |
| **Scope**       | Per session (socketId)              | Global                                |
| **Persistence** | Temporary (max 24h)                 | Permanent                             |
| **Setup**       | Zero — included                     | Requires connection string in secrets |
| **Ideal for**   | Conversation state, temporary cache | User data, history, config            |
