-
Notifications
You must be signed in to change notification settings - Fork 0
memory: port sync dedup fix, tolerate missing transcripts, document rem workflow #154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,113 +2,53 @@ | |
|
|
||
| Agent-agnostic session memory and private data sync for dotagents. | ||
|
|
||
| This directory makes AI session context and private skill data durable across | ||
| tools and machines without tying the layout to any one agent or memory provider. | ||
| Hook entrypoints capture compact session digests, sync selected memory facts into | ||
| the knowledge vault, and keep the derived search index current. | ||
| This directory is the **canonical upstream** of the memory layer: hook | ||
| entrypoints capture compact session digests, `rem` captures explicit facts, | ||
| consolidation is report-first, and `knowledge-sync` keeps the knowledge vault | ||
| git-synced. User repos deployed via `dotagents setup` carry their own copy of | ||
| `memory/lib`, `memory/hooks`, and `memory/tools`; `dotagents sync` builds the | ||
| tools and this repo carries the tested reference implementations. | ||
|
|
||
| `memory/` is the stable abstraction. The dependency-free basic tier writes | ||
| bounded Markdown digests directly; `memsearch` remains an optional indexed | ||
| provider behind the full hook pipeline. | ||
|
|
||
| ## Setup | ||
|
|
||
| Choose a tier through the CLI: | ||
| ## The rem workflow | ||
|
|
||
| ```bash | ||
| dotagents setup --memory basic # default; Python 3 only | ||
| dotagents setup --memory off # no managed memory hooks | ||
| dotagents setup --memory memsearch # requires memsearch on PATH | ||
| rem add -src claude "prefers pnpm for Node work" # capture a candidate fact anywhere | ||
| rem dream # consolidation report (report-only) | ||
| rem dream --apply # collapse exact-duplicate records | ||
| rem search "quota preferences" # semantic search via memsearch | ||
| rem sync # flush the vault via knowledge-sync | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a user configures the knowledge vault with a public Git remote and follows this new Useful? React with 👍 / 👎. |
||
| ``` | ||
|
|
||
| Basic memory appends compact digests under | ||
| `$KNOWLEDGE_DIR/sessions/YYYY-MM-DD.md` and injects bounded recent context at | ||
| session start. It does not invoke memsearch or persist raw transcripts. | ||
|
|
||
| The memsearch tier registers the indexed SessionStart, Stop, and SessionEnd | ||
| pipeline. Configure its vault through `~/.agents/memsearch.conf`. | ||
| Candidates are inert until promoted into durable instructions — consolidation | ||
| is report-first by design. See [tools/rem/README.md](tools/rem/README.md). | ||
|
|
||
| ## Dream review | ||
| ## Setup | ||
|
|
||
| Run the dream pass manually to find exact repeated preferences or corrections, | ||
| duplicate session records, and conflicting copies: | ||
| Choose a tier through the CLI: | ||
|
|
||
| ```bash | ||
| python3 ~/.agents/memory/lib/basic_memory.py dream \ | ||
| --output "$KNOWLEDGE_DIR/reviews/memory-dream-2026-08-19.json" | ||
| ``` | ||
|
|
||
| The output is a deterministic, review-only JSON artifact. The command reads | ||
| complete marker-delimited records under `sessions/` plus exact duplicate facts | ||
| in the legacy `sessions/knowledge.md` export. It does not scan | ||
| `profile/USER.md`, edit canonical memory, archive records, or delete anything. | ||
| The output path must be new and live under `$KNOWLEDGE_DIR/reviews/`. | ||
|
|
||
| Candidates are deliberately conservative: | ||
|
|
||
| - Repeated preferences and corrections require the same explicit statement in | ||
| at least two distinct session IDs. | ||
| - A stale duplicate requires an identical session block (ignoring trailing | ||
| whitespace) outside its unique UTC-dated canonical file. | ||
| - Legacy cleanup reports only exact facts repeated across distinct `## Sync` | ||
| records; it does not infer staleness from age. | ||
| - Reused session IDs with differing content are reported as conflicts, without | ||
| choosing a survivor. | ||
| - Assistant output, truncated statements, incomplete records, fuzzy semantic | ||
| matches, and age alone never produce candidates. | ||
|
|
||
| Review the source coordinates and candidate IDs before making a separate, | ||
| explicit edit to canonical memory. There is intentionally no apply or delete | ||
| mode. | ||
|
|
||
| ## Canonical paths | ||
|
|
||
| The knowledge vault path is set in `~/.agents/memsearch.conf` as `KNOWLEDGE_DIR`. | ||
| All skills and hooks resolve data paths relative to this variable: | ||
|
|
||
| ``` | ||
| $KNOWLEDGE_DIR/ # vault root (default: ~/Workspace/knowledge) | ||
| sessions/ # dated session digests (YYYY-MM-DD.md) | ||
| notes/ # handwritten notes | ||
| profile/ # USER.md, WORK.md | ||
| skills/<name>/ # private data for shared skills | ||
| dotagents setup --memory basic # default; Python 3 only | ||
| dotagents setup --memory off # no managed memory hooks | ||
| dotagents setup --memory memsearch # indexed search; requires memsearch on PATH | ||
| ``` | ||
|
|
||
| Do not hardcode `~/Workspace/knowledge` in code. Use `$KNOWLEDGE_DIR`. | ||
|
|
||
| Skills with private data store it under `$KNOWLEDGE_DIR/skills/<skill-name>/`. | ||
| Example: `$KNOWLEDGE_DIR/skills/jobs/opportunities.yaml`. | ||
|
|
||
| ## Design principles | ||
|
|
||
| - No symlinks. One canonical location per data file. | ||
| - Reuse existing sync pipelines when generalizing. Do not add ad-hoc rsync, | ||
| cron, or new sync tooling for individual skills. | ||
| - The knowledge vault is the canonical store for all private data that needs | ||
| to be available across agents and machines. | ||
| - The search index (memsearch) is derived state. Markdown in the vault is | ||
| canonical. | ||
| - Keep lifecycle concepts generic: `session-start`, `stop`, `session-end`, sync. | ||
| - Avoid per-agent directories unless an integration boundary forces them. | ||
| - Prefer payload dispatch and small parser modules over duplicated hooks. | ||
| - Index durable summaries and curated vault content, not raw full transcripts. | ||
|
|
||
| ## Security | ||
|
|
||
| The knowledge vault contains private data. It must not be pushed to any public | ||
| remote. Distribution is limited to local git and private sync between trusted | ||
| machines (Mac and VPS via the knowledge-sync tool). | ||
| | Tier | Behavior | Dependency | | ||
| |---|---|---| | ||
| | `off` | no managed memory hooks | none | | ||
| | `basic` | bounded session digests into the knowledge vault | Python 3 | | ||
| | `memsearch` | adds a derived search index over the vault | `memsearch` | | ||
|
|
||
| ## Layout | ||
|
|
||
| - `hooks/`: executable lifecycle hook entrypoints. | ||
| - `lib/`: transcript digest, vault sync, and indexing helpers. | ||
| - `tools/`: small repo-owned operational helpers for the memory subsystem. | ||
| - `AGENTS.md`: local rules for keeping this area agent-agnostic. | ||
| - `hooks/` — lifecycle entrypoints (session start/end/stop) registered per harness | ||
| - `lib/` — Python implementation: `basic_memory.py` (digests, dream-pass parsing), | ||
| `sync.py` (Hermes memory ↔ vault), `safety.py` | ||
| - `tools/` — Go binaries built by `dotagents sync`: `rem`, `knowledge-sync` | ||
| - `tests/` — reference test suite | ||
|
|
||
| ## Hook registration | ||
| ## Relationship to user repos | ||
|
|
||
| Memory-tier selection and native hook registration live in the dotagents CLI, | ||
| not in copy-pasted setup snippets. Re-run `dotagents setup --memory <tier>` to | ||
| change the managed hooks. Keep manual troubleshooting details in | ||
| `skills/dotagents/references/memory-sync.md`. | ||
| `dotagents` (this repo) is upstream: changes land here first, with tests. Your | ||
| `~/.agents` repository (created by `setup`) carries the deployed copy; `sync` | ||
| rebuilds the binaries whenever the sources change. Keep the two in sync by | ||
| porting changes here, then pulling in the user repo. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |||||||||||||
| python sync.py both # bidirectional | ||||||||||||||
| """ | ||||||||||||||
|
|
||||||||||||||
| import hashlib | ||||||||||||||
| import os | ||||||||||||||
| import re | ||||||||||||||
| import sys | ||||||||||||||
|
|
@@ -62,6 +63,25 @@ def normalize(entry: str) -> str: | |||||||||||||
| return re.sub(r"\s+", " ", text).strip() | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| def exported_entry_fingerprints(text: str, memory_entries: list[str]) -> set[bytes]: | ||||||||||||||
| """Find exact serialized entries, preferring longer overlapping matches.""" | ||||||||||||||
| occupied: list[tuple[int, int]] = [] | ||||||||||||||
| fingerprints: set[bytes] = set() | ||||||||||||||
| for entry in sorted(memory_entries, key=len, reverse=True): | ||||||||||||||
| pattern = re.compile( | ||||||||||||||
| rf"(?m)^- {re.escape(entry)}\n(?=- |\n## Sync |\Z)" | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When Hermes changes only an entry's case or punctuation after an earlier export (for example, AGENTS.md reference: AGENTS.md:L42-L46 Useful? React with 👍 / 👎. |
||||||||||||||
| ) | ||||||||||||||
|
Comment on lines
+71
to
+73
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): When Triggers: When an existing knowledge export has blank lines between bullet entries. Suggested fix: Allow whitespace between serialized entries, or parse the sync sections as Markdown bullets instead of requiring adjacent
Suggested change
|
||||||||||||||
| for match in pattern.finditer(text): | ||||||||||||||
| start, end = match.span() | ||||||||||||||
| if any(start < used_end and used_start < end for used_start, used_end in occupied): | ||||||||||||||
| continue | ||||||||||||||
| occupied.append((start, end)) | ||||||||||||||
| fingerprint = hashlib.sha256(normalize(entry).encode("utf-8")).digest() | ||||||||||||||
| fingerprints.add(fingerprint) | ||||||||||||||
| break | ||||||||||||||
| return fingerprints | ||||||||||||||
|
|
||||||||||||||
|
|
||||||||||||||
| # -- Direction 1: Memory -> Vault ------------------------------------------ | ||||||||||||||
| def memory_to_vault(paths: dict): | ||||||||||||||
| """Export Hermes memory facts to the knowledge vault.""" | ||||||||||||||
|
|
@@ -71,17 +91,17 @@ def memory_to_vault(paths: dict): | |||||||||||||
| memory_entries = parse_hermes_memory(paths["hermes_memory"]) | ||||||||||||||
| knowledge_path = paths["vault_knowledge"] | ||||||||||||||
|
|
||||||||||||||
| existing_knowledge = set() | ||||||||||||||
| existing_knowledge: set[bytes] = set() | ||||||||||||||
| if knowledge_path.exists(): | ||||||||||||||
| for line in knowledge_path.read_text(encoding="utf-8").splitlines(): | ||||||||||||||
| line = line.strip().lstrip("- ").strip() | ||||||||||||||
| if line: | ||||||||||||||
| existing_knowledge.add(normalize(line)) | ||||||||||||||
| knowledge_text = knowledge_path.read_text(encoding="utf-8") | ||||||||||||||
| existing_knowledge = exported_entry_fingerprints(knowledge_text, memory_entries) | ||||||||||||||
|
|
||||||||||||||
| new_entries = [] | ||||||||||||||
| for entry in memory_entries: | ||||||||||||||
| if normalize(entry) not in existing_knowledge: | ||||||||||||||
| fingerprint = hashlib.sha256(normalize(entry).encode("utf-8")).digest() | ||||||||||||||
| if fingerprint not in existing_knowledge: | ||||||||||||||
| new_entries.append(entry) | ||||||||||||||
| existing_knowledge.add(fingerprint) | ||||||||||||||
|
|
||||||||||||||
| if new_entries: | ||||||||||||||
| knowledge_path.parent.mkdir(parents=True, exist_ok=True) | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For users installing the release CLI,
dotagents setupwalksStarterAssets, whose embed list contains onlymemory/hooksandmemory/lib, notmemory/tools; consequently the created~/.agentsrepository has no tool sources anddotagents synchas nothing to build. The newly documentedremworkflow therefore fails with command-not-found after the documented setup, so either include the tools in the starter assets or document the separate installation step.AGENTS.md reference: AGENTS.md:L48-L50
Useful? React with 👍 / 👎.