Vault Mirror Skill
Project-instruction file resolution:CLAUDE.mdandAGENTS.md(Codex CLI) are transparent aliases — see [skills/_shared/instruction-file-resolution.md](../_shared/instruction-file-resolution.md). When this skill mentions Session Config inCLAUDE.md, the alias rule applies.
Purpose
vault-mirror populates the Meta-Vault with machine-generated notes derived from structured JSONL records. It converts entries from .orchestrator/metrics/sessions.jsonl and .orchestrator/metrics/learnings.jsonl into vault-conformant Markdown files under numeric-prefix subdirectories. This is distinct from vault-sync, which validates the vault — vault-sync validates the vault; vault-mirror populates it. The two skills are complementary: vault-mirror writes notes, vault-sync checks that the vault as a whole remains conformant.
When Invoked
vault-mirror is called in two places:
- Phase 3.7 of session-end (
skills/session-end/session-metrics-write.md) — mirrors the sessions.jsonl entry for the closing session. - Phase 3.5 of evolve (
skills/evolve/SKILL.md) — mirrors all learnings.jsonl entries added during the evolve cycle.
Both call sites are conditional: vault-mirror runs only when vault-integration.enabled == true AND vault-integration.mode != "off" in the project's Session Config. When either condition is not met, the call site skips silently and vault-mirror is never invoked.
Inputs
scripts/vault-mirror.mjs is the implementation. All arguments are required except --dry-run.
| Flag | Type | Required | Description | |---|---|---|---| | --vault-dir | path | yes | Absolute path to the Meta-Vault root directory. Must exist. | | --source | path | yes | Path to the JSONL file to read (one JSON object per line). Must exist. | | --kind | session or learning | yes | Determines which generator and target path are used. | | --dry-run | flag | no | Parse and resolve paths but do not write any files. Emits action lines as normal. |
Empty lines in the JSONL source are silently skipped.
Outputs
One JSON line is written to stdout for each non-empty JSONL entry processed. Exit code reflects the run outcome.
Action values
| action | Meaning | |---|---| | created | Entry did not exist in the vault; file created. | | updated | Entry existed (generator marker present, same id) with an older updated date; file overwritten. | | skipped-noop | Entry existed, same id, updated date not advanced; file unchanged. | | skipped-handwritten | A file at the target path has no _generator marker (or an unknown generator); left untouched. | | skipped-collision-resolved | A file at the target path has the generator marker but a different id; a disambiguated slug was used instead. | | skipped-invalid | Entry is missing one or more required fields; entry skipped, processing continues. | | skipped-quality-low | Entry failed the quality gate (PRD F1.2): learning confidence below vault-mirror.quality.min-confidence (CLI: --quality-min-confidence, default 0.5), or session rendered-narrative length below vault-mirror.quality.min-narrative-chars (CLI: --quality-min-narrative-chars, default 400). The emitted JSON line includes a reason field describing the violated threshold and path: null (no file was created). The quality gate runs before --force; --force does not bypass it. |
Output line shape
{"action":"created","path":"50-sessions/session-2026-04-13.md","kind":"session","id":"session-2026-04-13"}
path is relative to --vault-dir.
Exit codes
| Code | Meaning | |---|---| | 0 | Success (including idempotent no-ops and per-entry skips). | | 1 | Malformed JSON on a JSONL line — fatal, processing stops. Also returned when required CLI args are missing. | | 2 | Filesystem error: --vault-dir not found, --source not found, or an unexpected write error. |
Target Paths
| Kind | Target | |---|---| | session | <vault-dir>/50-sessions/<session-id>.md | | learning | <vault-dir>/40-learnings/<slug>.md |
Subdirectories are created automatically with mkdirSync({ recursive: true }) when writing (not in --dry-run mode).
The numeric prefix (50-sessions/, 40-learnings/) follows the vault folder ordering convention so that sessions and learnings appear in the correct position in the vault tree relative to other note types.
Path contract note: Issue #187 proposed <vault>/05-orchestrator/<repo>/ as the target layout. The shipped implementation uses numeric-prefix paths for vault ordering. If you need the issue-text path, file a new issue — do NOT silently change the script.
Idempotency
vault-mirror is safe to run multiple times against the same JSONL source:
- No file exists → create.
- File exists,
_generatormarker present, same id,updateddate not advanced →skipped-noop, file unchanged. - File exists,
_generatormarker present, same id,updateddate advanced →updated, file overwritten. - File exists, no
_generatormarker →skipped-handwritten, file untouched. The absence of the marker means the file was written by a human and must not be overwritten automatically. - File exists,
_generatormarker present, different id → slug collision. A disambiguated slug is derived by appending-<first-8-chars-of-entry-uuid>(hyphens stripped from the UUID before taking the prefix). The original file is left unchanged; the new note is written at the disambiguated path with actionskipped-collision-resolved.
The generator marker value is session-orchestrator-vault-mirror@1 and appears in the YAML frontmatter as _generator: session-orchestrator-vault-mirror@1.
Failure Modes
| Condition | Behaviour | |---|---| | Entry missing a required field | skipped-invalid emitted on stdout, error written to stderr, exit 0 (processing continues for remaining entries). | | Malformed JSON on a JSONL line | Error written to stderr, exit 1 (processing stops). | | --vault-dir not found | Error written to stderr, exit 2. | | --source file not found | Error written to stderr, exit 2. | | Hand-written file at target path | skipped-handwritten emitted on stdout, note written to stderr, file left unchanged. | | Unknown _generator value in existing file | Treated as hand-written: skipped-handwritten, file left unchanged. | | Unexpected filesystem write error | Error written to stderr, exit 2. |
Examples
Mirror the sessions.jsonl for the current project into a vault:
node scripts/vault-mirror.mjs \
--vault-dir ~/Projects/vault \
--source .orchestrator/metrics/sessions.jsonl \
--kind session
Dry-run a learnings mirror to preview actions without writing:
node scripts/vault-mirror.mjs \
--vault-dir ~/Projects/vault \
--source .orchestrator/metrics/learnings.jsonl \
--kind learning \
--dry-run
Live State
Phase 1 shipped and actively running. As of 2026-05-09, the skill has produced:
- 847 learning notes under
vault://40-learnings/— each carries_generator: session-orchestrator-vault-mirror@1in the YAML frontmatter. - 466 session notes under
vault://50-sessions/— same generator stamp.
Both target directories were verified by direct ls | wc -l measurement against ~/Projects/vault/. The numeric-prefix layout (40-learnings/, 50-sessions/) confirmed correct per the Target Paths spec above.
Configuration
vault-mirror respects the vault-integration block in the project's Session Config (CLAUDE.md, or AGENTS.md on Codex CLI). The script itself does not read Session Config — the calling skill (session-end, evolve) is responsible for reading the config and deciding whether to invoke vault-mirror at all.
| Field | Type | Default | Meaning | |---|---|---|---| | vault-integration.enabled | boolean | false | When false, the calling skill skips vault-mirror entirely. | | vault-integration.vault-dir | string | —