# Thread Memory Storage Migration: `memory_md` -> `memory_json`

## Summary

Per-thread memory now uses `thread_memory.memory_json` as the primary storage format.

- New writes persist structured JSON into `memory_json`.
- Reads prefer `memory_json`.
- Runtime no longer depends on `memory_md`.

## Why

`memory_md` stores structured state inside Markdown fenced blocks. This is readable for humans, but costly for:

- querying and analytics
- schema evolution
- migration reliability

`memory_json` keeps the same logical payload while making storage machine-friendly.

## Runtime behavior

- Read path uses `memory_json` only.
- Write path uses `memory_json` only.

## Auto migration behavior

- SQLite: on startup, adds `memory_json` column when missing.
- MySQL: on startup, adds `memory_json` column when missing.
No destructive migration is required for existing data.

## One-shot operational backfill (legacy command)

For faster cleanup in production, run:

```bash
cd backend
UV_CACHE_DIR=/tmp/uv-cache uv run python scripts/backfill_thread_memory_json.py --dry-run
UV_CACHE_DIR=/tmp/uv-cache uv run python scripts/backfill_thread_memory_json.py
```

Current codebase keeps this command for compatibility. In fully migrated environments it returns zero legacy rows.

## Final cleanup: drop `memory_md` column

After confirming all environments are migrated, run:

```bash
cd backend
UV_CACHE_DIR=/tmp/uv-cache uv run python scripts/drop_thread_memory_md_column.py --dry-run
UV_CACHE_DIR=/tmp/uv-cache uv run python scripts/drop_thread_memory_md_column.py
```

Notes:

- SQLite migration rebuilds `thread_memory` table and preserves data.
- MySQL migration runs `ALTER TABLE ... DROP COLUMN memory_md`.

## Follow-up (optional)

After all active environments have fully migrated and no legacy rows remain:

1. backfill any remaining rows that still rely on `memory_md`
2. remove `memory_md` column from schema
3. remove Markdown parsing fallback code
