Skip to content

feat(cli): memory export — one-way Obsidian vault export of agent memory - #296

Merged
plombeer31 merged 1 commit into
mainfrom
fix/discord-obsidian-memory-export
Aug 31, 2026
Merged

feat(cli): memory export — one-way Obsidian vault export of agent memory#296
plombeer31 merged 1 commit into
mainfrom
fix/discord-obsidian-memory-export

Conversation

@plombeer31

@plombeer31 plombeer31 commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

What

A minimal v1 of the Discord-requested Obsidian integration: a new atomic-agent memory CLI command with an export subcommand that mirrors the cross-session memory corpus (<stateDir>/memory.sqlite) into an existing Obsidian vault.

atomic-agent memory export --vault ~/Documents/MyVault [--folder atomic-agent]
# or: OBSIDIAN_VAULT_PATH=~/Documents/MyVault atomic-agent memory export

Each record becomes one markdown file with YAML frontmatter (type, created, updated, tags, plus status/source where the row has them) under the export subfolder:

  • notes/note-<id>.mdmemories rows, body = content, ## Links section from memory_links edges and the consolidated_into back-pointer
  • lessons/lesson-<id>.md — activation + principle, ## Sources wikilinks to parent notes
  • procedures/procedure-<id>.md — activation + numbered steps (with tool hints), ## Sources wikilinks to parent lessons/notes

Design assumptions (the reporter never described a flow, so v1 commits to these)

  • One-way, read-only. The DB is opened readonly and never migrated — an export cannot mutate agent state. Older schema files degrade gracefully (missing lessons/procedures/memory_links tables ⇒ empty sets). No watch mode, no sync-back, no conflict handling: a re-export overwrites the exported files.
  • Stable id-based filenames so a record keeps its Obsidian identity (and backlinks) across re-exports; Obsidian resolves [[note-17]] by basename.
  • Idempotent + overwrite-safe. File content is a pure function of the rows (no export timestamps — an unchanged corpus re-exports byte-identically, and unchanged files are not rewritten, keeping mtimes stable for sync tools). Stale-file pruning only touches machine-owned note-<n>.md / lesson-<n>.md / procedure-<n>.md names inside the export's three subfolders; anything else the user keeps there is never touched.
  • Soft pointers to evicted rows are skipped rather than rendered as dangling wikilinks; profile facts and vote/embedding internals are out of scope for v1.
  • The vault directory must already exist (the export never creates a vault at a typo'd path), and --folder must stay a subfolder of the vault (usage error otherwise). The new command implements the CLI's 0/1/2 exit-code split.

Tests

  • src/memory/obsidian-export.test.ts (8 tests): frontmatter + body rendering, every wikilink edge kind, dangling-soft-pointer skip, byte-identical idempotency, prune-vs-user-files safety, record-update overwrite, --folder traversal guards, missing DB/vault errors, and a hand-rolled pre-lessons legacy schema exported without migration (asserts no new tables were created).
  • src/cli/memory-command.test.ts (7 tests): help, unknown subcommand ⇒ 2, missing vault ⇒ 2, --vault happy path against a seeded state dir, $OBSIDIAN_VAULT_PATH fallback + --folder, escaping folder ⇒ 2, missing DB ⇒ 1.
  • Verified the prune test fails when pruning is disabled (mutation check).
  • npm run lint clean; full src/memory + src/cli suites: 58 files / 696 tests passed.
  • Manual end-to-end through npx tsx src/cli/index.ts memory export --vault … against a seeded corpus: files, wikilinks, and second-run idempotency verified.

Reported on Discord: https://discord.com/channels/1515649306781155428/1515649308161085612/1540448454373941361

Windows note: paths go through node:path (join/resolve/sep) throughout and tests use tmp dirs, but this was runtime-verified on macOS only.

…y corpus

Adds an `atomic-agent memory` command with an `export` subcommand that
mirrors the cross-session memory corpus (notes / lessons / procedures
in <stateDir>/memory.sqlite) into an existing Obsidian vault as
markdown files with YAML frontmatter (type, created, updated, tags)
and [[wikilinks]] along the schema's own edges: memory_links rows,
consolidated_into back-pointers, and lesson/procedure parent ids.

v1 design decisions:
- one-way and read-only: the database is opened readonly and never
  migrated; older schemas degrade gracefully (missing tables => empty)
- stable id-based filenames (note-<id>.md, ...) under a vault
  subfolder (--folder, default 'atomic-agent') so records keep their
  Obsidian identity and backlinks across re-exports
- idempotent and overwrite-safe: content is a pure function of the
  rows, unchanged files are not rewritten, and pruning of stale files
  is restricted to the machine-owned <kind>-<n>.md name patterns
  inside the export subfolders — user files are never touched
- soft parent pointers to evicted rows are skipped, not rendered as
  dangling links
- --vault falls back to $OBSIDIAN_VAULT_PATH (including via
  <stateDir>/.env); no watch mode, no sync-back in v1

The new command implements the 0/1/2 exit-code split (usage errors
return 2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@plombeer31
plombeer31 merged commit 1a1f03d into main Aug 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant