---
sidebar_position: 8
title: Chat memory
---

# Chat memory skill

Persistent agent memory across sessions — facts, decisions, preferences, task history. **mem0-backed by default** (embedding-based semantic recall, persists across cloud tasks); falls back to the self-contained Dolt backend automatically when the embedding proxy isn't available, or set `dolt` explicitly.

- **ID:** `chat-memory`
- **Runs in-process** — no MCP spawn

For test-run history (selectors, page models, prior runs) see [Memory](./memory.md).

## Tools provided

| Tool | What it does |
|---|---|
| `memory_store` | Save a fact/decision/preference. Categories: `fact`, `decision`, `context`, `insight`, `preference`, `credential`, `url`, `error`, `workaround`. Tiers: `short` (24h), `mid` (default), `long` (permanent). Optional `memoryKey` for upserts. Optional `infer` (mem0 only — see [the `infer` toggle](#the-infer-toggle)) |
| `memory_recall` | Search by `query`, `category`, `ticketKey`, or `tier`. Ranked by relevance × recency |
| `memory_brief` | Compact briefing — recent sessions + top long/mid-tier memories. Call at conversation start |
| `memory_end_session` | Save a session summary + key facts (semicolon-separated) for future recall |
| `task_log` | Record a completed task (`test_run`/`generate`/`analysis`/`research`/`other`) with status |
| `task_history` | Query past tasks by `ticketKey`, `type`, `status` |

## Setup

**mem0 (default).** `zibby init` configures mem0 out of the box and writes the right deps (`mem0ai@npm:@zibby/mem0ai@^3.0.5` + `better-sqlite3`) into your project. In Zibby cloud runs the embedding/LLM calls are proxied and billed through the agent run — no OpenAI key of your own needed. For local runs, point it at any OpenAI-compatible endpoint:

```bash
ZIBBY_MEM0_OPENAI_BASE_URL=https://api.openai.com/v1
ZIBBY_MEM0_API_KEY=sk-...
ZIBBY_MEM0_LLM_MODEL=gpt-4.1-mini
ZIBBY_MEM0_EMBEDDER_MODEL=text-embedding-3-small
ZIBBY_MEM0_EMBEDDING_DIMS=1536
```

mem0 mode uses embedding search for `memory_store` / `memory_recall`; `memory_end_session`, `task_log`, and `task_history` still write to Dolt for cross-session continuity. mem0's SQLite vector store lives under `.zibby/memory/mem0/` and is carried across ephemeral cloud tasks by the tenant-scoped memory tarball sync.

**Graceful degradation.** If the embedding proxy is unreachable (or a local run has no `ZIBBY_MEM0_API_KEY`), memory ops automatically fall back to the Dolt backend per-op rather than failing the run — you get structured memory instead of an error.

**Dolt (self-contained, no embedding dependency).** Set `ZIBBY_MEMORY_BACKEND=dolt` (or `memory.backend: 'dolt'`, or `zibby init --memory-backend dolt`) to opt out of mem0 entirely. Install Dolt; the skill auto-creates `.zibby/memory/` on first use:

```bash
brew install dolt
```

### The `infer` toggle

mem0 can either store memories raw (embed-only, free) or run an LLM fact-extraction pass that distills and dedupes facts before storing (~7.7k tokens per call, costs money). This is the `infer` flag, and it **defaults to `false`** (embed-only).

Resolution precedence (first match wins):

1. **Per-call tool arg** — pass `infer: true` to `memory_store`
2. **Env toggle** — `ZIBBY_MEM0_INFER=true`
3. **Project config** — `memory.infer: true` in `.zibby.config.mjs`
4. Default — `false` (embed-only, no LLM call)

```js
// .zibby.config.mjs
export default {
  memory: {
    backend: 'mem0',
    infer: false,        // default — store raw + embed, never call the LLM
  },
};
```

Turn `infer` on when you want mem0 to consolidate noisy inputs into clean facts; leave it off (the default) for free, deterministic embed-and-store.

### Cloud persistence

On Zibby Cloud, mem0 state **persists across Fargate tasks**. mem0's SQLite vector + history stores are rooted under the workspace's tenant-scoped `.zibby/memory/` tree, which is tarball-synced between executions — so a memory written in one run is recallable in the next, even though each run is a fresh, ephemeral container. mem0 user IDs are workspace-scoped (`workspace:<name>`, overridable via `ZIBBY_MEMORY_USER_ID`), keeping each project's memory isolated.

## Use in an agent

```js
import { WorkflowAgent, WorkflowGraph } from '@zibby/core';
import { SKILLS } from '@zibby/skills';

export class ChatAgent extends WorkflowAgent {
  buildGraph() {
    const graph = new WorkflowGraph();
    graph.addNode('respond', {
      agent: 'claude',
      skills: [SKILLS.CHAT_MEMORY, SKILLS.JIRA],
      prompt: (state) => `At the start of this turn, call memory_brief to load
      context. If you learn anything durable about the user's setup (e.g. their
      default Jira project), call memory_store with category="preference", tier="long".
      When the task is done, call memory_end_session with a 1-sentence summary.`,
    });
    return graph;
  }
}
```

The `memory_store` tool dedupes by normalized content — re-storing the same fact promotes its tier/relevance instead of inserting a duplicate. Long-tier rows decay 2%/session, mid-tier 10%, short-tier 30%, and short-tier rows older than 24h are deleted on next `memory_brief`.

## Output example

`memory_brief`:

```json
{
  "recentSessions": [
    { "session_id": "session_a1b2", "summary": "Reviewed SCRUM-123, added tests", "tickets": "SCRUM-123" }
  ],
  "topMemories": [
    { "category": "preference", "tier": "long", "content": "Default Jira board: SCRUM" },
    { "category": "fact", "tier": "mid", "content": "Auth login page is at /auth/login" }
  ],
  "taskStats": [
    { "type": "test_run", "status": "passed", "cnt": 14 }
  ]
}
```

## Implementation notes

`resolve()` returns `null` — this skill never spawns an MCP server. Tool calls are dispatched in-process via `handleToolCall(name, args, context)`, where `context.options.workspace` controls the Dolt directory.

The skill also implements `buildPromptContext(context, args)`, which the strategy calls at node start. It runs `memory_brief` internally and returns a markdown-formatted "Memory Context" block that gets prepended to the system prompt — so the model sees recent sessions and durable facts on every turn without needing an explicit tool call. The same call returns `debugPreview` for transcript logging.
