Archive AI coding-agent sessions (Claude Code, Codex, Gemini, Cline, OpenCode, Hermes) as index-safe Markdown notes in an Obsidian vault.
Project description
agent-vault
Archive your AI coding-agent sessions as clean, index-safe Markdown notes in an Obsidian vault.
If you use tools like Claude Code or Codex daily, your session history piles up in undocumented JSONL files and SQLite databases scattered across your machine. agent-vault sweeps those sources on a schedule and renders every session as a readable, searchable, wikilink-connected Markdown note — without ever freezing Obsidian's indexer, no matter how pathological the transcript.
Why not just dump sessions to Markdown?
Because real agent transcripts are hostile to Obsidian. A single session can be 12 MB, contain 100,000-character physical lines, or embed NUL bytes — any of which can stall Obsidian's indexing for the whole vault. agent-vault was built after exactly that happened, and its pipeline is designed around preventing it:
- Text normalization — NULs removed, C0 control characters replaced, line endings normalized, physical line length capped (default 8,000 chars) with explicit continuation markers instead of silent truncation. Idempotent by construction.
- Multipart splitting — sessions are planned as a document set: one stable primary note plus
Part 001,Part 002, … part notes, grouped by complete message sections against a 750 KiB target with a 1 MiB hard ceiling (max_note_bytes). Wikilinks always target the primary note. - Deterministic, idempotent sync — a SQLite manifest tracks every generated file by content hash. Re-running an import produces zero writes. Interrupted runs converge on the next run. Files the tool didn't create are never touched or deleted.
Supported providers
| Provider | Source format |
|---|---|
| Claude Code | ~/.claude/projects JSONL session logs (including subagents) |
| Codex | ~/.codex/sessions rollout logs |
| Gemini (Antigravity) | ~/.gemini/antigravity/brain |
| Cline | ~/.cline task history |
| OpenCode | ~/.local/share/opencode session storage |
| Hermes | ~/.hermes state.db SQLite databases |
Additional agents can be installed as third-party Python adapter packages
through the documented agentvault.adapters entry-point contract. Bundled
adapters always take precedence, conflicting plugins are rejected, and
agent-vault --no-plugins <command> ... disables plugin loading. See
docs/ADAPTERS.md.
Multiple roots per provider are supported (e.g. a Windows path and a WSL //wsl.localhost/... path); identical sessions found in more than one place are deduplicated by content hash.
A note on fragility: these session formats are undocumented and change without notice as the upstream tools evolve. The adapters were last validated against real session data in July 2026, specifically: Claude Code 2.1.201, Codex CLI 1.17.15, and Hermes Agent 0.18.0 (plus Gemini/Antigravity, Cline, and OpenCode session stores current at that date). If an import reports
failedorunsupportedsessions after a tool update, the adapter likely needs a refresh — failures print per-file diagnostics to stderr and are itemized in the--report-jsonoutput. Issues and PRs welcome.
Install
Requires Python 3.11+. No runtime dependencies outside the standard library.
pip install obsidian-agent-vault
Quickstart
-
Let
agent-vaultdetect your installed agent stores and create a configuration:agent-vault initThe guided flow shows every existing standard provider source directory it finds and asks for your vault path before writing
agent-vault.json. For scripts and unattended setup, provide the choices explicitly:agent-vault init --vault /path/to/vault --yes
Use repeatable
--source provider=/custom/patharguments to add nonstandard locations, and--no-detectwhen you want only explicit sources. Existing configurations are never replaced unless you pass--force. -
Validate the config without touching anything:
agent-vault check --config agent-vault.json
-
Preview what an import would do — same planned paths and collision decisions as a real run, zero writes:
agent-vault audit --config agent-vault.json --dry-run --report-json report.json
-
Run it for real:
agent-vault audit --config agent-vault.json
Useful flags: --provider <name> scopes a run to one provider; --full re-imports sessions even if their sources appear unchanged; --report-json <path> writes run metrics.
Exit codes: 0 success, 1 source/output failures or unsupported providers, 2 invalid usage or config.
Configuration
{
"vault": "/path/to/your/obsidian/vault",
"sources": {
"claude": ["~/.claude/projects"],
"codex": ["~/.codex/sessions"],
"hermes": ["~/.hermes"]
},
"redact_patterns": [],
"max_tool_output": 1000,
"max_note_bytes": 1048576,
"max_line_chars": 8000
}
redact_patterns— regex patterns scrubbed from all content before rendering and before content hashes are computed.max_tool_output— character cap for individual tool-output bodies.max_note_bytes— hard byte ceiling for any generated note (default 1 MiB).max_line_chars— maximum physical Markdown line length (default 8,000; minimum 80).
All limits are validated before any audit starts; ~ is expanded in all paths.
What the output looks like
Notes land under 01_Transcripts/<Provider>/ in your vault with readable, date-prefixed, ID-disambiguated names:
01_Transcripts/Hermes/2026-06-30 Optimizing AI Setup (355c876a)/
├── 2026-06-30 Optimizing AI Setup (355c876a).md ← primary (index) note
├── 2026-06-30 Optimizing AI Setup (355c876a) Part 001.md
├── ...
- Every note carries frontmatter:
provider,session_id,title,project,started_at/ended_at,source, and tags. - Sessions that spawned subagents get a directory, with child-session notes nested inside and two-way parent/child wikilinks.
- Multipart notes add
document_kind(primary/part),part, andpart_count— filterdocument_kind != "part"in Obsidian Bases/Dataview views so each session shows once. - Small single-file sessions stay single files; the directory/part machinery only appears when needed.
Scheduling
The CLI can install, inspect, and remove its own managed schedule:
agent-vault schedule install --config agent-vault.json --interval 6h --dry-run
agent-vault schedule install --config agent-vault.json --interval 6h
agent-vault schedule status
agent-vault schedule remove --dry-run
agent-vault schedule remove
install validates the configuration before touching the scheduler and
prints the exact Task Scheduler command or cron line it will use. Use
--dry-run to preview without changing the system. agent-vault manages only
its own single marked entry (AgentVaultArchive on Windows or
# agent-vault:managed in cron), and remove never touches unrelated work.
The initial automation supports Windows Task Scheduler and cron-compatible
systems; macOS launchd is not yet supported directly.
You can also author the scheduler entry yourself. A nightly run is one line:
# cron (Linux/macOS)
0 2 * * * agent-vault audit --config /path/to/config.json --report-json /path/to/nightly.json
# Windows Task Scheduler action
agent-vault audit --config config.json --report-json nightly.json
Before scheduling, run once manually and open the vault in Obsidian to confirm indexing completes.
Recovering from pathological notes
If a note (typically one generated before you adopted this tool, or by an older version) stalls Obsidian's indexer:
- Move it out — don't delete it — to a quarantine folder outside the vault. Obsidian recovers immediately.
- Re-run the importer scoped to that provider. The session regenerates from its original source under the current normalization and splitting rules.
- Check the run report:
largest_note_bytesandlargest_line_charsshould be within your configured ceilings, and generated files should contain noU+0000. - Delete the quarantined original only after confirming the regenerated note indexes cleanly.
Finding leftovers: sessions deduplicated against an already-imported copy are never re-rendered, so a stale pre-existing note for such a session won't be cleaned up automatically — the tool refuses to delete files it doesn't currently manage. Use doctor (below) to find and quarantine them.
The doctor command
doctor scans everything actually on disk under 01_Transcripts/ — not just what the last run touched — and reports:
- orphaned — files that look like generated transcripts (frontmatter with
providerandsession_id) but that no manifest row owns: leftovers from older importer versions or deduplicated sessions. Hand-written notes are left alone. - oversized_line / oversized_note / nul_bytes — violations of your configured index-safety limits, wherever they came from.
agent-vault doctor --config my-config.json --report-json doctor.json
# review the findings, then optionally:
agent-vault doctor --config my-config.json --quarantine-to /path/outside/vault/quarantine
agent-vault doctor --config my-config.json --quarantine-to /path/outside/vault/quarantine --only-unsafe
--quarantine-to moves only orphaned files (preserving their relative paths) — a hand-written note is never moved, even if it also trips oversized_line or another safety check, and manifest-owned files are never moved either, because the importer owns their lifecycle; regenerate those by re-running audit. Add --only-unsafe to narrow that further: only orphaned files that also carry a safety finding (oversized_line, oversized_note, or nul_bytes) get quarantined, so orphans that are merely harmless duplicates stay put for you to review. --only-unsafe requires --quarantine-to and exits 2 otherwise. Exit code is 1 when findings exist, 0 when clean, so it works as a scheduled health check.
The reconcile command
reconcile compares what's still reachable from your configured sources against what the vault actually has, and reports drift — it never writes, moves, or deletes anything.
agent-vault reconcile --config my-config.json
agent-vault reconcile --config my-config.json --format json
Findings fall into exactly four categories:
- stale-managed — a manifest-owned note whose source session no longer exists at any configured source (deleted, moved, or its provider removed from the config).
- content-duplicate — two or more sessions, possibly under different providers or paths, whose normalized message content is identical.
- identity-collision — two different sessions that would resolve to the same vault note path.
- orphan-unowned — a file under
01_Transcripts/that no manifest row owns, hand-written or otherwise (same ownership checkdoctoruses).
Every report opens with a line stating plainly that nothing was changed. Acting on a finding — regenerating a stale note, deleting one side of a duplicate, quarantining an orphan — is left to you; reconcile only reports. Exit code 1 means findings exist (useful for scheduled drift checks), 0 is clean, 2 is a config error.
Optional AI enrichment
agent-vault enrich writes derived summary notes alongside your transcripts. It is entirely optional: nothing else in this project needs it, no runtime dependency is added, and no network code lives in the core. Enrichment shells out to an external command you configure — usually an AI CLI you already have installed — with a redacted transcript excerpt on that command's stdin, and captures its stdout as the summary.
{
"vault": "/path/to/your/obsidian/vault",
"sources": { "claude": ["~/.claude/projects"] },
"enrichment": {
"command": ["claude", "-p"],
"model_label": "claude-cli (claude -p), model unspecified by caller",
"max_input_chars": 24000,
"timeout_seconds": 120
}
}
command is an argv list, not a shell string — no {prompt} templating, because the transcript excerpt is piped to the process's stdin rather than substituted into an argument. Both claude -p (no prompt argument, so it reads stdin) and ollama run llama3 (reads stdin when stdin isn't a TTY) work this way. model_label is a free-text provenance string you choose — record whatever actually identifies what generated the note, since agent-vault has no way to verify it. max_input_chars (default 24,000) truncates the transcript excerpt sent to the command, with an explicit truncation marker rather than silent cutoff. timeout_seconds (default 120) bounds how long agent-vault waits for the command.
agent-vault enrich --config my-config.json # generate/refresh sidecars
agent-vault enrich --config my-config.json --dry-run # preview only, never invokes the command
agent-vault enrich --config my-config.json --force # regenerate even if content is unchanged
agent-vault enrich --config my-config.json --session <id> # scope to one session
What you get, and the guarantees behind it:
- Sidecar notes, never edits. Output lands as new files under
enrichment/<primary-note-stem>.summary.md— one per already-archived session. The transcript note under01_Transcripts/that a sidecar links to is never opened for writing. - Mandatory provenance frontmatter on every sidecar: source session id and provider, a real Obsidian wikilink back to the primary transcript note, the
model_labelyou configured, the exact command that was invoked, a content hash of the transcript text it summarized, and a generation timestamp. A sidecar's frontmatter tells you exactly what produced it and from what. - Idempotent by content hash. Re-running
enrichskips any session whose current transcript hash matches its existing sidecar's recorded hash — unchanged sessions are never re-summarized, so scheduled runs are cheap. Pass--forceto regenerate regardless. - Only archives sessions
audithas already written.enrichlooks up each session in the audit manifest so a sidecar's wikilink always points at the transcript note's real, on-disk filename; sessions not yet archived are skipped with askipped-not-archiveddiagnostic (runauditfirst). - Failure-isolated. A missing or failing command, a non-zero exit, empty output, or a timeout skips only that session with a diagnostic on stderr; nothing else is blocked or corrupted. Exit code
1signals partial failure,0is clean,2is a config error (including a config with noenrichmentsection). - Enrichment is optional and derived — see
CONTEXT.md. It's never required to create or read an archive,auditnever invokes it implicitly, and theenrichment/folder is safe to delete at any time; re-runningenrichregenerates it from your still-intact transcripts.
Development
See CONTRIBUTING.md for dev setup, lint/type-check commands, and PR expectations.
pip install -e ".[dev]"
python -m pytest tests/ -q
ruff check .
mypy
The suite covers adapters (with synthetic fixtures for every provider), text normalization, oversized-message chunking, document-set planning, manifest migration, transactional sync (including one↔many part transitions and interruption recovery), CLI behavior, and end-to-end index-safety regressions modeled on real failure shapes (12 MB transcripts, 136k-char lines, embedded NULs).
Adapter compatibility and fixtures
See docs/COMPATIBILITY.md for the provider compatibility matrix and what to do when an upstream format drifts, and docs/ADAPTERS.md for how to author a provider adapter and its fixture/privacy rules. Two commands support that workflow without ever requiring a real transcript in the repo:
agent-vault fixture <provider> <source-file>sanitizes one real session file into a structure-only fixture undertests/fixtures/(ids and paths become deterministic fake values, everything else becomesREDACTED-<n>; the source is only ever read).tests/test_conformance.pythen runs every adapter against every fixture and fails with a clear message if a provider has none.agent-vault report <path> [--output PATH]prints a content-free structural diagnostic (provider detection, record-type histogram, key shapes, parse errors by line number) for attaching to bug reports instead of a transcript.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file obsidian_agent_vault-0.2.0.tar.gz.
File metadata
- Download URL: obsidian_agent_vault-0.2.0.tar.gz
- Upload date:
- Size: 90.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e60ca31b752020d6fac5f2a79e767fdeaac85c4e9e88b91268c5995db22db082
|
|
| MD5 |
0d7a3c4f035007187ec402a28b96287f
|
|
| BLAKE2b-256 |
4020189967fc8cd3cfa775e36da62a5539cfb483d1c70a7d96905ae73e6cdf26
|
Provenance
The following attestation bundles were made for obsidian_agent_vault-0.2.0.tar.gz:
Publisher:
publish.yml on spawnofsociety2/agent-vault
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
obsidian_agent_vault-0.2.0.tar.gz -
Subject digest:
e60ca31b752020d6fac5f2a79e767fdeaac85c4e9e88b91268c5995db22db082 - Sigstore transparency entry: 2155286536
- Sigstore integration time:
-
Permalink:
spawnofsociety2/agent-vault@17681a3f01358de33fc2e27bafb26ea2b258a121 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/spawnofsociety2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@17681a3f01358de33fc2e27bafb26ea2b258a121 -
Trigger Event:
release
-
Statement type:
File details
Details for the file obsidian_agent_vault-0.2.0-py3-none-any.whl.
File metadata
- Download URL: obsidian_agent_vault-0.2.0-py3-none-any.whl
- Upload date:
- Size: 58.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54841af12bb45863aeb61345b56e6b4c958244c0185631fc781a411fba7fce49
|
|
| MD5 |
41243957c157ce4a61602c6447050a8f
|
|
| BLAKE2b-256 |
ff3cf2d0ccc39dda8683bc7a4b6209182f4ccfed186214205cd316a1046a9e6f
|
Provenance
The following attestation bundles were made for obsidian_agent_vault-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on spawnofsociety2/agent-vault
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
obsidian_agent_vault-0.2.0-py3-none-any.whl -
Subject digest:
54841af12bb45863aeb61345b56e6b4c958244c0185631fc781a411fba7fce49 - Sigstore transparency entry: 2155286638
- Sigstore integration time:
-
Permalink:
spawnofsociety2/agent-vault@17681a3f01358de33fc2e27bafb26ea2b258a121 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/spawnofsociety2
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@17681a3f01358de33fc2e27bafb26ea2b258a121 -
Trigger Event:
release
-
Statement type: