Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions examples/openclaw-migration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# OpenClaw Migration

This tool imports existing OpenClaw data into OpenViking through two paths:

- `memory`: import native OpenClaw memory markdown files directly into OpenViking memory URIs
- `transcript`: replay historical OpenClaw jsonl transcripts into OpenViking sessions and commit them
- `all`: run both paths in one pass

By default it reads from `~/.openclaw` and connects to OpenViking over HTTP using the same config as `ovcli.conf`. You can also point it at an embedded local data path with `--ov-path`.

## Examples

```bash
python examples/openclaw-migration/migrate.py --mode memory --dry-run
```

```bash
python examples/openclaw-migration/migrate.py --mode all --wait
```

```bash
python examples/openclaw-migration/migrate.py \
--mode transcript \
--agent main \
--url http://127.0.0.1:1933 \
--api-key "$OPENVIKING_API_KEY"
```

```bash
python examples/openclaw-migration/migrate.py \
--mode memory \
--ov-path ~/.openviking
```

## Mapping

Native OpenClaw memory files map to deterministic OpenViking memory URIs:

- `workspace/MEMORY.md` -> `viking://user/memories/entities/openclaw-memory.md`
- `workspace/memory/YYYY-MM-DD.md` -> `viking://user/memories/events/openclaw-YYYY-MM-DD.md`
- `workspace/memory/YYYY-MM-DD-*.md` -> `viking://agent/memories/cases/openclaw-YYYY-MM-DD-*.md`

Deterministic URIs and session ids make reruns naturally resumable:

- existing memory targets are skipped unless `--overwrite` is set
- replayed transcript sessions use a stable `openclaw-<agent>-<session>` target id
124 changes: 124 additions & 0 deletions examples/openclaw-migration/migrate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#!/usr/bin/env python3
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
# SPDX-License-Identifier: AGPL-3.0
"""Migrate existing OpenClaw memory files and transcripts into OpenViking."""

from __future__ import annotations

import argparse
import json
from pathlib import Path
from typing import Any

import openviking as ov
from openviking.migration.openclaw import migrate_openclaw


def _build_client(args: argparse.Namespace) -> Any:
if args.ov_path:
client = ov.SyncOpenViking(path=args.ov_path)
client.initialize()
return client

client = ov.SyncHTTPClient(
url=args.url,
api_key=args.api_key,
account=args.account,
user=args.user,
agent_id=args.agent_id,
timeout=args.http_timeout,
)
client.initialize()
return client


def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--openclaw-dir",
default=str(Path.home() / ".openclaw"),
help="OpenClaw state directory (default: ~/.openclaw)",
)
parser.add_argument(
"--mode",
choices=("memory", "transcript", "all"),
default="memory",
help="What to migrate",
)
parser.add_argument(
"--agent",
action="append",
dest="agent_ids",
help="Only replay transcripts for the given OpenClaw agent id (repeatable)",
)
parser.add_argument(
"--category-override",
choices=("preferences", "entities", "events", "cases", "patterns", "tools", "skills"),
help="Override the inferred OpenViking memory category for native OpenClaw memory files",
)
parser.add_argument("--dry-run", action="store_true", help="Preview planned imports only")
parser.add_argument("--overwrite", action="store_true", help="Overwrite existing OV targets")
parser.add_argument(
"--no-wait", action="store_true", help="Do not wait for async queue/task completion"
)
parser.add_argument(
"--timeout",
type=float,
default=300.0,
help="Queue/task wait timeout in seconds (default: 300)",
)
parser.add_argument(
"--poll-interval",
type=float,
default=1.0,
help="Task poll interval in seconds (default: 1)",
)
parser.add_argument(
"--url",
help="OpenViking server URL. If omitted, SyncHTTPClient falls back to ovcli.conf",
)
parser.add_argument("--api-key", help="Optional OpenViking API key")
parser.add_argument("--account", help="Optional X-OpenViking-Account header")
parser.add_argument("--user", help="Optional X-OpenViking-User header")
parser.add_argument("--agent-id", help="Optional X-OpenViking-Agent header")
parser.add_argument(
"--http-timeout",
type=float,
default=60.0,
help="HTTP client timeout in seconds (default: 60)",
)
parser.add_argument(
"--ov-path",
help="Use embedded SyncOpenViking against a local data path instead of HTTP mode",
)
return parser.parse_args()


def main() -> int:
args = _parse_args()
if args.ov_path and any([args.url, args.api_key, args.account, args.user, args.agent_id]):
raise SystemExit("--ov-path cannot be combined with HTTP connection flags")

client = _build_client(args)
try:
result = migrate_openclaw(
client,
args.openclaw_dir,
mode=args.mode,
dry_run=args.dry_run,
overwrite=args.overwrite,
wait=not args.no_wait,
timeout=args.timeout,
poll_interval=args.poll_interval,
agent_ids=args.agent_ids,
category_override=args.category_override,
)
finally:
client.close()

print(json.dumps(result, indent=2, ensure_ascii=False))
return 0


if __name__ == "__main__":
raise SystemExit(main())
20 changes: 20 additions & 0 deletions openviking/async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,26 @@ async def write(
telemetry=telemetry,
)

async def import_memory(
self,
uri: str,
content: str,
mode: str = "replace",
wait: bool = False,
timeout: Optional[float] = None,
telemetry: TelemetryRequest = False,
) -> Dict[str, Any]:
"""Create or update a memory file and refresh semantics/vectors."""
await self._ensure_initialized()
return await self._client.import_memory(
uri=uri,
content=content,
mode=mode,
wait=wait,
timeout=timeout,
telemetry=telemetry,
)

async def ls(self, uri: str, **kwargs) -> List[Any]:
"""
List directory contents.
Expand Down
28 changes: 27 additions & 1 deletion openviking/client/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,33 @@ async def write(
execution.telemetry,
)

async def import_memory(
self,
uri: str,
content: str,
mode: str = "replace",
wait: bool = False,
timeout: Optional[float] = None,
telemetry: TelemetryRequest = False,
) -> Dict[str, Any]:
"""Create or update a memory file and refresh semantics/vectors."""
execution = await run_with_telemetry(
operation="content.import_memory",
telemetry=telemetry,
fn=lambda: self._service.fs.import_memory(
uri=uri,
content=content,
ctx=self._ctx,
mode=mode,
wait=wait,
timeout=timeout,
),
)
return attach_telemetry_payload(
execution.result,
execution.telemetry,
)

# ============= Search =============

async def find(
Expand Down Expand Up @@ -443,7 +470,6 @@ async def add_message(

If both content and parts are provided, parts takes precedence.
"""

from openviking.message.part import Part, TextPart, part_from_dict

session = self._service.sessions.session(self._ctx, session_id)
Expand Down
23 changes: 23 additions & 0 deletions openviking/migration/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Copyright (c) 2026 Beijing Volcano Engine Technology Co., Ltd.
# SPDX-License-Identifier: AGPL-3.0
"""Migration helpers for importing external data into OpenViking."""

from openviking.migration.openclaw import (
OpenClawMemoryArtifact,
OpenClawTranscriptMessage,
OpenClawTranscriptSession,
discover_openclaw_memory_artifacts,
discover_openclaw_transcript_sessions,
migrate_openclaw,
parse_openclaw_transcript,
)

__all__ = [
"OpenClawMemoryArtifact",
"OpenClawTranscriptMessage",
"OpenClawTranscriptSession",
"discover_openclaw_memory_artifacts",
"discover_openclaw_transcript_sessions",
"migrate_openclaw",
"parse_openclaw_transcript",
]
Loading
Loading