MemoryRouterMemoryRouter

Historical Import

Agent-driven historical import - the import nudge, the versioned extraction prompt, per-platform import commands, pricing and approval, and idempotency guarantees.

Historical Import

MemoryRouter captures memories going forward, but a new vault usually sits next to months of existing local conversation history: Claude Code transcripts, Codex session rollouts, OpenClaw session files. Historical import brings that history into the vault with a strict, locked division of labor:

LayerOwnsNever does
ServerThe extraction prompt (versioned), pricing, idempotency, dedupeReads your disk
Your agentDiscovering and parsing its own local history, writing one JSONL extractUploading, touching HTTP, seeing your key
Connector CLITransport: validation, hashing, approval, idempotent batch upload, receiptParsing conversation history

The prompt handles discovery, shipped code handles transport. Because the extraction prompt lives server-side, it improves for every user instantly, with no package update, and the agent adapts at import time to whatever transcript format is actually on disk.

Supported platforms: claude-code, codex, openclaw (agent-driven), plus chatgpt (the ChatGPT archive CLI). All four ride the same idempotent imports API.

The flow

  1. The nudge. When a vault is new (fewer than 500 stored memories, no completed import, not dismissed), every retrieval response carries an additive import block, and the connector renders one line in injected context: [MemoryRouter] Historical import suggested: this machine has local conversation history not yet in your vault. Run: <import command> --instructions. It fires on every injection, exactly like the consolidation nudge, and clears server-side the moment an import completes or you dismiss.
  2. Instructions. The agent (or you) fetches the versioned extraction prompt from the server and executes it locally. The agent reads its own history files, excludes everything that is not a real user message or final assistant response, and writes one JSONL extract.
  3. Transport. The connector CLI validates the extract, computes deterministic identity (record hashes, content-derived import id, opaque vault fingerprint), shows the record count, date range, estimated tokens, and the server price quote, and requires explicit approval. Then it uploads idempotent batches and prints the receipt.

Commands per platform

# Claude Code
npx memoryrouter-claude import --instructions        # print the extraction prompt
npx memoryrouter-claude import --instructions --run  # run it headless via claude -p
npx memoryrouter-claude import --file extract.jsonl --yes
npx memoryrouter-claude import --dismiss

# Codex
memoryrouter-codex import --instructions             # print the extraction prompt
memoryrouter-codex import --instructions --run       # run it headless via codex exec
memoryrouter-codex import --file extract.jsonl --yes
memoryrouter-codex import --dismiss

# OpenClaw
openclaw mr import --instructions                    # print the prompt for the host agent
openclaw mr import --file extract.jsonl --yes
openclaw mr import --dismiss

Without --yes, --file asks interactively after showing the preview and quote. --dismiss records "user said no" permanently for that platform, and the nudge stops.

GET /v1/memory/imports/instructions

Returns the full server-authored extraction prompt for a platform. Normal bearer auth; read-only keys may read instructions (the write routes below reject them).

curl "https://api.memoryrouter.ai/v1/memory/imports/instructions?platform=claude-code" \
  -H "Authorization: Bearer mk_user-123"
ParamTypeRequiredDescription
platformstringYesclaude-code, codex, or openclaw.
output_pathstringNoSubstituted into the prompt as the extract destination.

Response:

{
  "import_prompt_version": 1,
  "platform": "claude-code",
  "prompt": "You are running a one-time historical memory extraction. ...",
  "next_command": "npx memoryrouter-claude import --file <path>",
  "record_shape": {
    "external_id": "claude-code:<conversation_id>:<message_id>:v1 (escape % as %25 then : as %3A in the id parts)",
    "content": "string, non-empty, max 1,000,000 chars",
    "timestamp": "number, original message time as unix epoch milliseconds (never the current time)",
    "role": "user or assistant"
  }
}

import_prompt_version bumps only when instruction wording or the record shape changes meaning, the same locked pattern as reflection_contract.

The extraction prompt contract

The prompt instructs the agent, precisely:

  • Extract only two kinds of messages: the user's actual typed message and the assistant's final visible response. Everything else is excluded by name: tool calls and results, thinking blocks, system prompts, injected <memory_context> blocks and [MemoryRouter] lines (so retrieval injections are never re-ingested), intermediate assistant steps, heartbeats, compaction summaries, and sub-20-character leftovers.
  • Adapt to the real format. The prompt tells the agent the format may have changed since the prompt was written: open files, inspect, adapt. No frozen parser assumptions.
  • Be deterministic. Re-running the extraction on the same files must produce byte-identical records: stable ids, original timestamps (epoch milliseconds, never the current time), no relative dates.
  • Stay offline and read-only. No network calls, no modification of source files, no credentials in the output.
  • Report. The agent finishes with a machine-parseable EXTRACT_RESULT summary line and tells the user the exact upload command.

The JSONL record shape

One JSON object per line, exactly four fields:

{"external_id": "claude-code:sess-uuid:0007:v1", "content": "the message text", "timestamp": 1700000000000, "role": "user"}

The CLI rejects anything else: extra fields, empty content, wrong platform prefix, second-resolution timestamps, roles other than user/assistant, and duplicate ids inside the file.

Pricing and approval

Creating an import is a free, read-only preflight. The server returns a price quote from your current billing state (free allotment coverage first, then your per-million rate), and nothing can be written until the CLI binds an explicit, price-bound approval digest. If the price changes between quote and approval, approval fails closed. Import tokens bill at the same rate as normal memory storage.

Idempotency guarantees

The imports API is built for interruption:

  • The import_id is derived from the extract content plus the destination vault fingerprint, so the same extract to the same vault is always the same import.
  • Every batch carries an Idempotency-Key. A batch that was already durably committed replays its stored response for free.
  • The server dedupes globally by external_id per vault: re-importing a record that already exists acknowledges it as duplicate at zero storage and zero billing.
  • Re-running the import command after any failure (network, crash, out of credits) resumes exactly where it left off. Nothing is ever double-stored or double-billed.
  • The final receipt is immutable and reconciles stored, duplicate, skipped, and failed counts against billing.

POST /v1/memory/imports/dismiss-suggestion

Permanently record that the user declined the import nudge for a platform.

curl -X POST https://api.memoryrouter.ai/v1/memory/imports/dismiss-suggestion \
  -H "Authorization: Bearer mk_user-123" \
  -H "Content-Type: application/json" \
  -d '{ "platform": "claude-code" }'

Response: { "dismissed": true, "platform": "claude-code", "dismissed_platforms": ["claude-code"] }

Any dismissal clears the vault's nudge; the state also clears automatically when any import completes.

The import block on retrieval responses

When the server decides a vault should be nudged, search and prepare responses carry an additive import block next to the reflection block:

{
  "import": {
    "suggested": true,
    "platform_hint": null,
    "suggestion": "Historical import suggested: this vault has no imported conversation history. Call the import instructions endpoint to begin."
  }
}

The field is absent entirely when not suggested, so existing response consumers are unaffected.

On this page