# How to Import External Conversations into ai-memory: Complete Guide with CLI Examples

> Learn how to import external conversations into ai-memory using the ai-memory-importer CLI. Follow our guide with examples to easily add your chat history.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: how-to-guide
- Published: 2026-09-05

---

**Use the `ai-memory-importer` binary to replay any external conversation into ai-memory by preparing a JSON envelope and running the `external-conversation` sub-command with `--apply`.**

The **ai-memory** project provides a dedicated companion tool for importing conversations from external sources like ChatGPT, Claude, or custom LLM transcripts. This guide walks through the complete import pipeline based on the actual source code implementation in `akitaonrails/ai-memory`.

## The Import Architecture

The importer operates as a **deterministic replay system**. It converts external transcripts into native ai-memory hook events, which the server processes as if they were live conversation captures. This design allows imported conversations to participate fully in ai-memory's **handoff** and **recall** mechanisms.

Key design principles from the source:

- **Idempotent replay**: The same import run always produces identical session IDs
- **Safe dry-run mode**: Plan imports without network calls
- **Manifest verification**: Every run produces an auditable JSON record

## Step 1: Prepare Your Conversation JSON Envelope

The importer expects a file matching the `ConversationEnvelope` struct defined in [`companions/ai-memory-importer/src/main.rs`](https://github.com/akitaonrails/ai-memory/blob/main/companions/ai-memory-importer/src/main.rs) (lines 94-108).

Required structure:

| Field | Type | Description |
|-------|------|-------------|
| `project` | string | Target project name in ai-memory |
| `source` | string | Identifier for the external system (e.g., "chatgpt", "claude") |
| `session_id` | string | Stable external identifier for this conversation |
| `messages` | array | Ordered list with `role` (system/user/assistant) and `content` |

Example envelope file:

```bash
cat > sample.json <<'EOF'
{
  "project": "my-project",
  "source": "chatgpt",
  "session_id": "conv-12345",
  "messages": [
    {"role": "system", "content": "You are a helpful assistant."},
    {"role": "user", "content": "What is the capital of France?"},
    {"role": "assistant", "content": "Paris."},
    {"role": "user", "content": "What is its population?"},
    {"role": "assistant", "content": "Approximately 2.1 million in the city proper."}
  ]
}
EOF

```

## Step 2: Validate with Dry-Run Mode

Never import blindly. The dry-run mode invokes [`plan_external_conversation`](https://github.com/akitaonrails/ai-memory/blob/main/companions/ai-memory-importer/src/main.rs#L245-L295) to validate and display the planned events without network calls.

```bash
ai-memory-importer external-conversation \
  --file sample.json \
  --workspace DRY-RUN-WS \
  --manifest-out dry-run-manifest.json

```

**What happens during planning:**

1. **Validation**: Label lengths checked, secrets redacted via `secret_patterns` function
2. **Sanitization**: Control characters stripped from messages
3. **Size enforcement**: Hard limits prevent oversized payloads
4. **Stable ID generation**: `stable_external_session_id` (lines 91-106) creates deterministic session identifier from workspace + project + source + external session ID

The output shows each `PlannedHookEvent` with its ingest key, URL, and JSON body.

## Step 3: Execute the Real Import

Add `--apply` and a valid `--workspace` to POST events to your ai-memory server:

```bash
ai-memory-importer external-conversation \
  --file sample.json \
  --workspace production-ws \
  --apply \
  --manifest-out import-manifest.json

```

**Runtime flow** (implemented in [`main.rs`](https://github.com/akitaonrails/ai-memory/blob/main/main.rs)):

- **Hook event construction** (lines 298-322): `planned_hook_event` and `build_hook_url` generate fully-qualified URLs encoding event type, identifiers, and SHA-256 ingest keys
- **Event types generated**: `session-start`, `user-prompt`, `external.assistant-message`, `session-end`
- **Batch delivery** (`post_hook_batch`, lines 445-463): Single HTTP POST to `/hook/batch` endpoint
- **Acknowledgment handling**: Server response lists accepted events; any rejection aborts the import

Successful output resembles:

```

import complete: replayed 5 events into production-ws/my-project as session external-a1b2c3...

```

## Understanding the Import Manifest

Every run produces a JSON manifest (controlled by `--manifest-out`). The manifest structure (`conversation_manifest` / `write_conversation_manifest`, lines 266-277) contains:

- Source file SHA-256 hash
- Stable session ID
- Count of planned events
- Count of accepted events (0 in dry-run mode)
- Error details (if import aborted)

Use manifests to:

- Verify dry-run vs. actual import differences
- Audit which conversations were imported when
- Retry failed imports with identical parameters

## CLI Reference: External Conversation Sub-Command

| Flag | Required? | Default | Purpose |
|------|-----------|---------|---------|
| `--file <PATH>` | Yes | — | Path to JSON envelope |
| `--workspace <NAME>` | Yes with `--apply` | — | Target workspace |
| `--server-url <URL>` | No | `http://127.0.0.1:49374` | ai-memory server address |
| `--apply` | No | false | Actually send events; without this, dry-run only |
| `--manifest-out <PATH>` | Yes with `--apply` | — | Manifest output location |

## Security Considerations During Import

The importer applies the same protections as live capture:

- **Secret redaction**: Patterns matching API keys, tokens, and passwords are masked before transmission
- **Control character stripping**: Prevents injection attacks
- **Size limits**: Enforced per-message and per-batch

These protections are implemented in the planning phase before any network activity occurs.

## Post-Import: Using Your Conversation

Once imported, the conversation behaves like any native ai-memory session:

- Query via standard recall commands
- Include in handoff contexts for new sessions
- Search via consolidated indexing

See [`docs/usage.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/usage.md) for handoff mechanics and [`docs/architecture.md`](https://github.com/akitaonrails/ai-memory/blob/main/docs/architecture.md) for how imported data flows through the capture → consolidate → recall → handoff pipeline.

## Summary

- **Prepare** a JSON envelope with `project`, `source`, `session_id`, and `messages` array
- **Validate** with dry-run mode before network operations
- **Import** using `--apply` flag with mandatory workspace and manifest paths
- **Verify** via the generated manifest showing planned vs. accepted event counts
- **Access** imported conversations through normal ai-memory recall and handoff mechanisms

The `ai-memory-importer` binary transforms any external transcript into first-class ai-memory knowledge with full auditability and zero runtime dependencies beyond HTTP.

## Frequently Asked Questions

### What conversation formats can I import?

Any transcript that fits the `ConversationEnvelope` JSON schema. This includes ChatGPT exports, Claude conversation logs, custom LLM outputs, or synthesized training dialogues. The schema only requires `role` (system/user/assistant) and `content` per message.

### Why does the same import always produce the same session ID?

The `stable_external_session_id` function (lines 91-106) deterministically hashes workspace, project, source, and your external session identifier. This prevents duplicate conversations in the knowledge base and enables idempotent retry of interrupted imports.

### Can I import to a remote ai-memory server?

Yes. Use `--server-url` to target any reachable ai-memory instance. The default `http://127.0.0.1:49374` assumes local development. Ensure network connectivity and authentication (if configured) before running with `--apply`.