Import Local Agent Logs (openviking-server ingest)
openviking-server ingest reads local agent conversation logs, imports the messages into OpenViking sessions, and commits them for memory extraction. It supports one-time historical imports and ongoing log monitoring without changing the agent or installing a memory plugin.
Run it on the machine that holds the logs. It connects to a local or remote OpenViking server through the SDK. Ingestion is disabled by default, so installing OpenViking does not scan local logs; enable both the main switch and the configuration for each chosen agent.
Source: openviking/ingest
Off by default
The feature is doubly disabled and must be turned on explicitly:
- the master switch
ingest.enableddefaults tofalse; - each harness's
enableddefaults tofalse, and harnesses you do not list are never read; - backfill of existing logs is a manual command and supports
--dry-run(count only, write nothing) and--since(bound the time window) so you can verify first.
Supported harnesses
| harness | Status | Default log path | Notes |
|---|---|---|---|
claude_code | Supported | ~/.claude/projects/*/*.jsonl | append-only JSONL, byte-offset cursor |
codex | Supported | ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl | append-only JSONL |
workbuddy | Supported | ~/.workbuddy/projects/*/*.jsonl | append-only JSONL; injects system reminders into user turns — the adapter strips them and keeps only <user_query> |
hermes | Supported | ~/.hermes/sessions/*.jsonl | group-chat agent; user peer = original username |
openclaw | Supported | ~/.openclaw/agents/*/sessions/*.jsonl | group-chat agent; user peer = original username |
opencode | Experimental | ~/.local/share/opencode/opencode.db | SQLite, polled by (time, id); the legacy file-store is not supported |
mimo | Experimental | ~/.local/share/mimocode/mimocode.db | SQLite, polled by (time, id); skips part.synthetic text and agent_id != main |
cursor | Deferred | ~/Library/Application Support/Cursor/User/**/state.vscdb | undocumented, version-unstable KV blobs; not yet implemented |
"harness" (agent framework) here means a whole tool like Claude Code or Codex — distinct from OpenViking's "tool" (tool-call) concept.
Enable in ov.conf
Set these variables in the same shell that will run the ingest commands. This example targets a local dev server; for an authenticated server, replace the URL and use a tenant-bound user/admin API key:
export OPENVIKING_URL="http://localhost:1933"
export OPENVIKING_API_KEY="" # Local dev only; set a user/admin key for authenticated deployments.The config loader expands these variables. Unset variables remain literal strings, so do not skip this step when using the placeholders below.
Add an ingest section to ov.conf, listing the harnesses to import and their mode:
{
"ingest": {
"enabled": true,
"server_url": "$OPENVIKING_URL",
"api_key": "$OPENVIKING_API_KEY",
"account": "default",
"user": "default",
"harnesses": {
"claude_code": { "enabled": true, "mode": "both" },
"codex": { "enabled": true, "mode": "backfill" },
"workbuddy": { "enabled": true, "mode": "both" },
"opencode": { "enabled": false, "mode": "watch", "experimental": true },
"mimo": { "enabled": false, "mode": "watch", "experimental": true },
"hermes": { "enabled": false, "mode": "both", "user_field": "sender" },
"openclaw": { "enabled": false, "mode": "both", "user_field": "sender" }
}
}
}mode:off|backfill(one-shot import of existing logs) |watch(incremental) |both.paths: override the harness's default discovery roots (multiple allowed).user_field: for group-chat harnesses, the log key holding the original username, used as the user-side peer_id.commit: commit policy —commit_token_threshold,commit_idle_seconds,keep_recent_count.- Deploy-time toggles can also be overridden via env:
OPENVIKING_INGEST_ENABLED,OPENVIKING_INGEST_SERVER_URL,OPENVIKING_INGEST_API_KEY.
When server_url is empty it falls back to OPENVIKING_URL or http://localhost:1933, so it can target either a local or a remote server.
Usage
The openviking-server ingest command is installed together with OpenViking.
# Show registered harnesses and their config
openviking-server ingest list-sources
# Dry run first: count the sessions / messages that would be replayed, write nothing
openviking-server ingest backfill --dry-run
# Backfill one harness, only sessions started on/after a date
openviking-server ingest backfill --harness claude_code --since 2026-06-01
# Backfill existing logs for real
openviking-server ingest backfill
# Watch for new logs and replay incrementally (blocks in the foreground)
openviking-server ingest watch --harness claude_code
# Honor each harness's configured mode: backfill then watch
openviking-server ingest run
# Show how far each session has been ingested (read the cursor state)
openviking-server ingest status--reset deletes and recreates the OV session before replaying; it does not remove memories already extracted from earlier imports. Normal reruns resume from the persisted cursor and reconcile interrupted batches against the server message count. Keep the cursor database, avoid simultaneous replay processes or other writers to imported sessions, and do not reuse one cursor database for a different server or user.
peer_id
Every message carries a peer_id so OpenViking can profile both the human and the model:
- assistant turns:
{harness}/{model}(or{harness}/{provider}/{model}when the provider is meaningful), e.g.claude_code/claude-opus-4-8,opencode/bytedance_ark/doubao-...; - user turns: single-user dev harnesses (claude_code / codex / opencode) use the git identity of the session cwd repo (
user.email/user.name), falling back to the configuredingest.userwhen there is no git repo; group-chat harnesses (hermes / openclaw) use the original username from the log (selected byuser_field).
Identifiers containing any non-ASCII characters (for example, a CJK or mixed-script username) use a collision-free ext-<base64> form that encodes the complete identifier. The ext- namespace is reserved: an ASCII identity that would sanitize to an ext- id is also encoded so it cannot impersonate an encoded identity. New reads and writes use only the canonical id. Older versions may have collapsed multiple mixed-script identities—or a mixed-script identity and a real ASCII identity—into the same peer directory. OpenViking therefore does not attach those ambiguous directories as automatic aliases; operators must decide ownership before migrating existing data.
How it works
Each harness has a thin adapter that parses its logs into normalized messages and hands them to a replayer that runs ensure_session → batch add (<=100 per call) → commit. Memory extraction only runs on commit, server-side. OV session ids are import__{harness}__{native_session_id} — deterministic and idempotent.
- Backfill enumerates every session, replays from the cursor to the end, then commits once per session.
- Watch mirrors OpenViking's own
WatchScheduler: it uses interval polling (not filesystem events) driven by durable cursors, so a missed tick, a sleep, or a restart just reads cursor→end on the next tick and self-heals. JSONL uses a byte-offset cursor (with partial-line / truncation / rotation handling); SQLite uses a(time, id)cursor read read-only (WAL-aware).
Cursor state persists in ~/.openviking/ingest/state.db, so backfill and watch can resume after a restart under the same connection identity.
Cost and privacy
- Commit triggers memory extraction (an LLM call). Backfilling months of history at once can produce many calls — prefer
--dry-runfirst, narrow with--since, and enable harnesses in batches. - Logs may contain sensitive content (credentials, file contents). Use this in a trusted deployment and confirm
server_urlpoints at the server you intend. - The default import keeps user/assistant text and drops tool-call inputs and outputs. Use a runtime plugin when you need structured tool capture.
See also
- Capability Reference
- Overview — the per-harness memory plugins (real-time capture)
- Deployment guide → CLI —
ov.conf/ credential configuration