Skip to content

Sharing Memories

Denys Kashkovskyi edited this page Jun 5, 2026 · 1 revision

Sharing Memories

threadnote share lets a small team keep a curated set of durable memories in a git repository so every member's local agent can recall them. Personal handoffs, preferences, and unpublished durable notes stay local; only memories you explicitly publish leave your machine.

Model in One Screen

A team is a configured shared git repo. Each team has a name (default: default), a git remote, a local working tree, and a separate gitdir.

Piece Location Notes
Working tree ~/.openviking/data/viking/<account>/user/<you>/memories/shared/<team>/ Lives inside the OpenViking data tree, so files are addressable as viking://user/<you>/memories/shared/<team>/... and appear in normal recall.
Gitdir ~/.openviking/share/teams/<team>.gitdir/ Lives outside the OV data tree so OpenViking never sees git internals.
Team config ~/.openviking/share/teams.json (mode 0600) Records the team name, remote, worktree, and gitdir.

The working tree being inside the memory tree is the whole trick: published files are real Markdown on disk under your user namespace, so they are indexed and returned by recall like any other durable memory, while git operates against the gitdir kept outside the OV tree.

Workflow

One-time setup

Create the repo first on GitHub/GitLab/etc. and copy its SSH URL, then clone it into your memory tree:

threadnote share init git@github.com:org/team-memories.git
# Optional: add a second team
threadnote share init --team friends git@github.com:you/friends-memories.git

share init clones the remote into your local memory tree and ingests any existing Markdown memories into OpenViking. It refuses to clone over a non-empty worktree. Switch the default with init --set-default <name>, or just run publish/sync with an explicit --team.

See what is configured

threadnote share list
threadnote share status                # default team
threadnote share status --team friends

Share a memory

# 1. Identify the personal URI you want to publish (use recall/list as usual).
# 2. Preview the would-be-published bytes before committing:
threadnote share publish viking://user/you/memories/durable/projects/foo/bar.md --preview
# 3. Publish:
threadnote share publish viking://user/you/memories/durable/projects/foo/bar.md

share publish writes the memory into the team's shared subtree, commits with the message share: publish <relative-path>, pushes, then removes the personal copy after the push succeeds. The memory's recall path becomes viking://user/you/memories/shared/<team>/durable/projects/foo/bar.md.

Before writing, share publish strips supersedes: and archived_from: lines from the memory's header block. Those pointers only resolve on the publisher's machine — teammates pull via git and cannot dereference them — so keeping them would leak local-only provenance into team git history.

Flags:

Flag Effect
--preview Read, strip, and scrub the memory and print the exact bytes that would land in git. No writes, no commits, no pushes. Use before any publish to catch leaks by inspection.
--redact Replace soft-leak matches (local home paths) with placeholders and continue. Credentials still block.
--team <name> Target a specific team instead of the default.
--message "..." Override the commit message.
--no-push Commit locally without pushing.
--dry-run Show what would happen without changing anything.

Update an existing shared memory

Do not publish a second copy. Replace the shared URI directly, and Threadnote updates it in place and pushes:

threadnote remember \
  --replace viking://user/you/memories/shared/default/durable/projects/foo/bar.md \
  --project foo \
  --topic bar \
  --text "Updated shared knowledge."

For MCP, pass the same shared URI as replaceUri to remember_context. Threadnote rewrites the shared memory in place, strips local-only provenance headers, commits, and pushes. You do not store a personal replacement and run share publish again.

Take a memory back

threadnote share unpublish viking://user/you/memories/shared/default/durable/projects/foo/bar.md

The memory is rewritten back into your personal namespace and removed from the shared repo.

Stop sharing for a team

threadnote share remove --team friends             # deletes worktree + gitdir
threadnote share remove --team friends --keep-files
threadnote share remove --team friends --preserve-local

--preserve-local copies shared durable memories back into the personal durable tree before removing the team config. It does not delete remote git history. Without --keep-files, share remove deletes the local checkout. Push any unpushed commits first (threadnote share sync or git -C <worktree> push), otherwise unpublished work is lost.

Rename or move a team remote

threadnote share rename --team friends --to platform
threadnote share set-url --team platform git@github.com:org/platform-threadnote.git

share rename moves the local worktree/gitdir, updates teams.json, and reindexes the shared memory namespace under the new team name. share set-url updates the git origin URL and verifies it with git fetch.

Auto-sync vs Manual Sync

Threadnote does a periodic background git fetch for configured share teams. When an agent calls MCP recall_context / read_context, or the CLI threadnote recall / threadnote read, Threadnote checks whether a shared repo is behind. If it is, Threadnote rebases the clean worktree, reindexes the pulled Markdown into OpenViking, then returns the requested result. Sync errors degrade to warnings so memory access still works with the best local data available.

Crucially, automatic recall/read sync never commits a dirty shared worktree — it warns and leaves that case for explicit threadnote share sync.

Manual sync remains useful to publish local edits, clear a dirty worktree, resolve git conflicts, or force a sync immediately:

threadnote share sync                  # default team
threadnote share sync --team friends   # other team
threadnote share sync --no-push        # pull only
threadnote share sync --no-auto-commit # refuse to sync when the worktree is dirty

share sync auto-commits any uncommitted edits in the worktree, fetches and rebases from the remote, reindexes pulled Markdown into OpenViking (so recall finds them immediately), and pushes.

Privacy and Safety

Only the durable/ kind is shareable. handoffs/, preferences/, incidents/, and other lifecycle kinds stay local by construction — both the initial ingest (share init) and the sync-pull reindex (share sync) skip any file outside durable/.

The publish-time scrubber hard-blocks (never redactable) when it matches any of:

Pattern What it catches
-----BEGIN ... PRIVATE KEY----- PEM private key headers
sk-... (16+ chars) OpenAI / Anthropic-style keys (also matches any slug starting sk-; break the pattern by hand on a false positive)
gh[pousr]_... GitHub classic tokens
github_pat_... GitHub fine-grained PATs
glpat-... GitLab PATs
Bearer ... (20+ chars) HTTP bearer tokens
eyJ... (three base64url segments) Bare JWTs, even when the Authorization: Bearer prefix was stripped
AKIA... AWS access keys
xox[abcdeprs]-... Slack bot, user, configuration, legacy cookie, refresh, app, and similar tokens

The scrubber also blocks on soft-leak patterns that show up routinely in curated memories. These are redactable: pass --redact to replace each match with a generic placeholder and continue. Credentials always block regardless of --redact.

  • macOS home paths (/Users/<you>/...) -> <local-path>
  • linux home paths (/home/<you>/...) -> <local-path>

Other guarantees:

  • Transactional. share publish writes and pushes the shared copy first, and only removes the personal copy after the push succeeds. A push failure leaves the personal copy intact; you never end half-published.
  • No silent overwrite. share publish refuses to overwrite an existing shared memory at the same URI. Use threadnote remember --replace <shared-uri> (or remember_context({replaceUri:"<shared-uri>"})) for updates, or pick a different topic name.
  • Human review still matters. The scrubber is best-effort. Strip the value, preview with --preview, then publish.
  • share publish deletes the personal copy after publishing. To keep both, copy the memory to a new URI first before publishing.

Conflict Resolution

share sync uses git pull --rebase against the remote. When git cannot merge cleanly:

  1. The pull reports the conflict and leaves the worktree in a rebase-in-progress state.
  2. Resolve the conflicts manually in the worktree — it is a normal git checkout.
  3. Run git rebase --continue (or --abort) yourself.
  4. Re-run threadnote share sync to finish the reindex and push.

Two publishes touching the same <topic>.md from different machines will collide. Coordinate ownership per topic, or use distinct topics.

Cross-machine Identity

Each user clones into their own user-namespaced path, so identical content shows up under each user's namespace. A memory authored on machine A as viking://user/alice/memories/shared/team/durable/projects/foo/bar.md shows up on machine B as viking://user/bob/memories/shared/team/durable/projects/foo/bar.md. The file content is identical.

supersedes: / archived_from: lines are stripped at the publish boundary so cross-machine URI references do not pollute team git history. Explicit viking:// references inside a body still point at the author's namespace. Prefer narrative references ("see the foo memory under shared/team") over viking:// links in shared content.

Publishing via MCP

The MCP tool share_publish runs the same scrubber as the CLI. It writes and pushes the shared copy first, then removes the personal copy after the push succeeds. Pass an optional team, and "push":false to commit without pushing.

share_publish({"uri":"viking://user/you/memories/durable/projects/foo/bar.md"})
share_publish({"uri":"viking://user/you/memories/durable/projects/foo/bar.md","team":"friends","push":false})

Before publishing, confirm with the user unless they have already authorized you to share durable memories autonomously.

See also

Safety and Security, Memory Lifecycle, Agent Instructions, CLI Reference

Clone this wiki locally