RUSTSEC-2026-0285 (rustls <0.23.45, TLS1.3 wrong-encryption-level handshake accept; GHSA-2mjx-qc3c-rqvc / referenced by GHSA-wc7c-cm87-463c). The main and relay lockfiles were already at 0.23.45; the importer companion sat at 0.23.41 because CI never audited companions/ai-memory-importer/Cargo.lock. Bump it and add that lockfile to the cargo-audit step so the drift class is caught. Also drop the now-stale RUSTSEC-2026-0194/0195 (quick-xml) ignores — quick-xml is in none of the three lockfiles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MDbhmszrjG9s5MrPrTuNtm
ai-memory-importer
Standalone optional companion for importing external memory corpora into a
running ai-memory server. This crate is deliberately isolated from the root
workspace: its Cargo.toml has its own [workspace], uses only crates.io
dependencies, and is not included in root cargo test --workspace.
Supported sources
OMC wiki directory
The original importer supports oh-my-claudecode / OMC flat Markdown wiki
directories. It reads only top-level *.md files, skips index.md and
session-log-* by default, and writes deterministic destination paths under
omc/<slug>.md.
Generic external conversations
external-conversation accepts one deliberately small interchange format:
{
"project": "my-project",
"source": "chatgpt",
"session_id": "exported-conversation-id",
"messages": [
{ "role": "user", "content": "What evidence supports this claim?" },
{ "role": "assistant", "content": "The source supports only part of it." }
]
}
Roles are system, user, or assistant. Product-specific ChatGPT, Claude
Desktop, Markdown, or other export adapters intentionally stay outside this
repository; they only need to emit the generic envelope.
The importer replays the conversation through ai-memory's public hook pipeline:
one session-start, the ordered messages, and one session-end. User messages
use the canonical user-prompt event. Assistant/system messages use the
validated ai-memory-importer extension vocabulary so their role and body stay
available to later consolidation. The whole sequence uses /hook/batch, whose
inline processing preserves order and does not return until the session page
and other SessionEnd effects have landed.
Imported sessions use the dedicated agent=external-import wire identity,
never a live codex or claude-code identity. Core's tolerant unknown-agent
boundary stores it in the closed other bucket, while the SessionStart
observation names external:<source> and assistant/system observations retain
extension provenance. A reader can therefore distinguish an explicit import
and its source without adding product-specific agent kinds to core.
Claude memory graph and Qdrant imports remain roadmap items; there are no code stubs for them.
Safety contract
- Default mode is dry-run; live mode requires
--apply. - Live mode requires explicit
--workspace,--project, and--manifest-out <path>. - Live writes use only
POST /admin/write-page; the importer never opens ai-memory SQLite or wiki files directly and never deletes pages. - The destination workspace/project must already exist unless
--create-destinationis passed. - Existing destination pages abort the import unless
--overwriteis passed. The importer also re-checks each page immediately before writing. This is best-effort protection: a concurrent writer could still race between the check and/admin/write-page, so avoid running competing import/write jobs into the same destination. - It stops on the first live-write error and updates the manifest with completed writes and the failed checkpoint.
- Path handling fails closed: absolute paths,
.., unsafe destination paths, and reserved/internal destination prefixes are rejected. Duplicate generated destination paths abort planning. - Dry-run output does not print full page bodies unless
--show-bodyis passed. - Only endpoint-supported metadata is mapped:
title,kind,tier,tags,pinned, andbody. Unknown frontmatter is ignored. - Auth comes only from
AI_MEMORY_AUTH_TOKEN; there is intentionally no CLI token argument. - External conversations are fully parsed, schema-checked, bounded, and sanitized before any manifest or HTTP request is created. Unknown fields and roles fail closed. Limits are 2 MiB per file, 128 messages, and 1 MiB total message content. Oversized individual messages are UTF-8-safely truncated to the same durable hook caps: 16 KiB for user prompts and 2,000 bytes for extension messages. The dry-run and manifest report the truncation count.
- Conversation bodies receive client-side credential redaction before replay and then cross ai-memory's normal server sanitizer as a second boundary.
- Stable session IDs derive from
(workspace, project, source, session_id). Each replay event has a stableingest_key; a changed transcript gets a new terminal generation key, so rerunning an interrupted import is safe and appending to an export re-runs SessionEnd after the new messages. - A live conversation import is one ordered hook batch. If the server accepts only a prefix, the manifest is marked failed with its accepted count; rerun the same source to resume via the stable keys.
- There is no inbox or watch-folder mode. Import is an explicit one-shot operation.
Usage
Dry-run with a summary:
cargo run --manifest-path companions/ai-memory-importer/Cargo.toml -- \
omc-wiki --dir /path/to/omc/wiki --workspace default --project my-project
Dry-run with a manifest:
cargo run --manifest-path companions/ai-memory-importer/Cargo.toml -- \
omc-wiki --dir /path/to/omc/wiki --workspace default --project my-project \
--manifest-out /tmp/omc-import-manifest.json
Live import:
AI_MEMORY_AUTH_TOKEN=... \
cargo run --manifest-path companions/ai-memory-importer/Cargo.toml -- \
omc-wiki --dir /path/to/omc/wiki --workspace default --project my-project \
--apply --manifest-out /tmp/omc-import-manifest.json
Options:
--server-url URL: ai-memory server URL; defaults tohttp://127.0.0.1:49374, orAI_MEMORY_SERVER_URLwhen set. A URL path is treated as the base path.--create-destination: allow/admin/write-pageto auto-create the workspace/project after the read preflight fails.--overwrite: replace existing destination pages.--include-session-logs: includesession-log-*pages.--show-body: print full page bodies during dry-run.--pinned: pin all imported pages.
External conversation usage
Dry-run (the default):
cargo run --manifest-path companions/ai-memory-importer/Cargo.toml -- \
external-conversation --file /path/to/conversation.json \
--workspace default
Show the sanitized event bodies in the dry-run:
cargo run --manifest-path companions/ai-memory-importer/Cargo.toml -- \
external-conversation --file /path/to/conversation.json \
--workspace default --show-body
Live replay:
AI_MEMORY_AUTH_TOKEN=... \
cargo run --manifest-path companions/ai-memory-importer/Cargo.toml -- \
external-conversation --file /path/to/conversation.json \
--workspace default --apply \
--manifest-out /tmp/conversation-import-manifest.json
The envelope's project and the CLI's --workspace are both explicit. The
destination must already exist unless --create-destination is passed. Source
files are read once; adapters should write their completed generic JSON export
before invoking the importer.
Validation
Run these from the repository root:
cargo fmt --check --manifest-path companions/ai-memory-importer/Cargo.toml
cargo test --manifest-path companions/ai-memory-importer/Cargo.toml
cargo clippy --manifest-path companions/ai-memory-importer/Cargo.toml --all-targets -- -D warnings
Root hygiene checks remain separate:
cargo fmt --check
git diff --check
Roadmap
- Claude Code memory graph export import.
- Qdrant collection import with user-supplied schema mapping.
- Optional deterministic normalization passes after OMC import is stable.