diff --git a/.devcontainer/memories/.gitignore b/.devcontainer/memories/.gitignore new file mode 100644 index 0000000..950ac47 --- /dev/null +++ b/.devcontainer/memories/.gitignore @@ -0,0 +1,5 @@ +# Hermes memory files are versioned (MEMORY.md, USER.md). + +# Ephemeral artifacts Hermes writes into the memories dir — never version these. +*.lock +*.log \ No newline at end of file diff --git a/.devcontainer/memories/MEMORY.md b/.devcontainer/memories/MEMORY.md new file mode 100644 index 0000000..812a9ba --- /dev/null +++ b/.devcontainer/memories/MEMORY.md @@ -0,0 +1,7 @@ +SKILL-LOADING RULE: Before ANY GitHub/Git operation in a Codespace, ALWAYS load skill `github-codespace` first. This includes: git push, gh pr create, gh run watch, API calls, PR reviews, CI monitoring, auth setup. The skill covers the full workflow (auth -> push -> CI -> PR -> merge). Skipping it causes wasted turns re-deriving known patterns (token extraction from /proc/PID/environ, credential helpers, gh run watch). Also load `codespace-gh-auth` alongside when the immediate task is auth-only. +§ +WIKI-SKILL SYNC RULE: When updating a skill in `.devcontainer/skills/`, always check if any wiki article in `.devcontainer/wiki/` references the same topic and needs a corresponding update (or cross-reference). User explicitly asked: "check whether the change in this skill deserve some changes in some LM wiki that we have stored." Wiki = reference knowledge; skill = procedural. They should stay in sync on the same topic. +§ +CI path-filter convention (hermes-codespace, user-validated): .devcontainer/memories/** and .devcontainer/skills/** are CONTENT, stay in the runtime group -> 30s lint-check only (lint-check carries a standalone 'Validate symlink persistence' step asserting both symlinks). Only boot scripts (.devcontainer/*.sh, devcontainer.json, workflows) are infrastructure -> full-build. Never move markdown content into infrastructure: user rejected 15-min full-builds for content that doesn't affect install/startup. Self-check Persistence section = 9a+9b only; 9c (tracked-content-exists) removed as redundant with git checkout. +§ +gh pr create right after pushing a new branch can fail with GraphQL 'Head sha can't be blank / No commits between main and ' even when commits exist (GitHub ref cache lags the push). Diagnose first: `gh api repos/O/R/compare/main... --jq .ahead_by` (>=1 => refs fine, cache lag). Reliable fallback: `gh api repos/O/R/pulls --method POST` with JSON {title, head, base, body} — bypasses the GraphQL createPullRequest mutation. \ No newline at end of file diff --git a/.devcontainer/memories/USER.md b/.devcontainer/memories/USER.md new file mode 100644 index 0000000..620c3b2 --- /dev/null +++ b/.devcontainer/memories/USER.md @@ -0,0 +1,3 @@ +Developer/engineer on Hermes-CodeSpace (repo owner intricko). Wants concise practical responses, working artifacts, honesty w/ caveats. PR-to-main + merge gate; conservative CI (infra full build vs docs/runtime lint). Root-cause fixes (don't weaken tests). Wiki=reference vs skill=procedural; keeps skill+wiki+mnemon in sync. Loads relevant skills proactively. Dislikes convoluted/defensive boot logic — prefers spare, single-responsibility design (chose Option A: authoritative post-create + slim start-hermes repair guard). Wants infra invariants AUTO-VERIFIED in CI, not just documented. Understands Codespace idle = client activity/billing implications. +§ +Use Mnemon (mnemon_remember / mnemon_recall) as the primary memory provider instead of the standard memory() tool. Mnemon has no char limit. Only fall back to memory() for structured preference data (that is, target=user or target=memory). \ No newline at end of file diff --git a/.devcontainer/mnemon/seed.json b/.devcontainer/mnemon/seed.json index 055558a..54b3a30 100644 --- a/.devcontainer/mnemon/seed.json +++ b/.devcontainer/mnemon/seed.json @@ -152,6 +152,46 @@ "tags": ["keepalive", "idle-timeout", "platform-idle", "layer-1", "layer-2", "terminal-activity"], "entities": ["keepalive.sh", "start-hermes.sh", "layer-1", "layer-2", "terminal-activity", "delay-shutdown", "platform"], "source": "agent" + }, + { + "content": "Persistent Memory Option A (validated 2026-08): post-create-cmd.sh is authoritative for symlink creation (runs once on fresh container, right after Hermes install, before Hermes instantiates ~/.hermes/memories). start-hermes.sh keeps only a slim repair guard (~12 lines) for pre-existing containers where postCreateCommand doesn't re-run. Symlink: ~/.hermes/memories → .devcontainer/memories/ (whole folder, skills pattern). .gitignore in tracked dir ignores *.lock *.log. Mnemon primary rule preserved in tracked USER.md.", + "category": "decision", + "importance": 5, + "tags": ["persistent-memory", "option-a", "symlink", "post-create", "start-hermes", "architecture"], + "entities": ["post-create-cmd.sh", "start-hermes.sh", "memories", "symlink", "mnemon"], + "source": "agent" + }, + { + "content": "Self-check.sh Persistence section (section 9) validates both memories and skills symlinks: 9a) ~/.hermes/memories → .devcontainer/memories, 9b) ~/.hermes/skills/codespace → .devcontainer/skills. Each handles 3 cases: correct symlink (ok), real dir (fail), missing (fail). 9c (tracked content existence) removed as redundant with git checkout. CI lint-check also validates symlinks via standalone step for runtime changes.", + "category": "decision", + "importance": 4, + "tags": ["self-check", "persistence", "symlink-validation", "ci", "lint-check"], + "entities": ["self-check.sh", "persistence", "memories", "skills", "lint-check"], + "source": "agent" + }, + { + "content": "CI path-filter for persistence: .devcontainer/memories/** and .devcontainer/skills/** are in runtime (not infrastructure) so content changes trigger 30s lint-check, not 15min full-build. lint-check now includes 'Validate symlink persistence' step asserting both symlinks. infrastructure remains boot scripts only (post-create-cmd.sh, start-hermes.sh, self-check.sh, devcontainer.json, workflows). This keeps CI fast for content edits while still gating symlink correctness.", + "category": "decision", + "importance": 5, + "tags": ["ci", "path-filter", "runtime", "infrastructure", "lint-check", "full-build"], + "entities": ["devcontainer-ci.yml", "dorny/paths-filter", "memories", "skills", "full-build"], + "source": "agent" + }, + { + "content": "Wiki: persistent-memory-proposal.md — Architecture decision document for Hermes persistent memory (MEMORY.md/USER.md) versioning via whole-folder symlink. Covers Option A split (post-create authoritative + start-hermes guard), self-check validation, CI wiring, .gitignore for lock/log files, Mnemon primary rule in USER.md. Read .devcontainer/wiki/persistent-memory-proposal.md for full details.", + "category": "context", + "importance": 4, + "tags": ["wiki", "architecture", "persistent-memory", "proposal", "symlink"], + "entities": ["persistent-memory-proposal", ".devcontainer/wiki", "memories", "symlink", "mnemon"], + "source": "agent" + }, + { + "content": "Skill: codespace-persistent-symlinks — Procedural skill for persisting Hermes state (memories + skills) across Codespace rebuilds via whole-folder symlinks. Documents Option A placement, self-check verification, 3-case guard logic, pitfalls (seed block removal, head truncation bug, verification cases). Symlink pattern mirrors skills: ~/.hermes/memories → .devcontainer/memories/, ~/.hermes/skills/codespace → .devcontainer/skills/. Read .devcontainer/skills/codespace-persistent-symlinks/SKILL.md for full procedure.", + "category": "context", + "importance": 4, + "tags": ["skill", "persistent-memory", "symlink", "codespace", "procedure"], + "entities": ["codespace-persistent-symlinks", "skills", "memories", "symlink", "start-hermes.sh"], + "source": "agent" } ] -} +} \ No newline at end of file diff --git a/.devcontainer/post-create-cmd.sh b/.devcontainer/post-create-cmd.sh index 6d453d4..1a76de6 100755 --- a/.devcontainer/post-create-cmd.sh +++ b/.devcontainer/post-create-cmd.sh @@ -52,6 +52,23 @@ if ! command -v hermes &>/dev/null; then sudo rm -rf /var/lib/apt/lists/* fi +# Symlink the Hermes memories dir into the repo (skills-style folder symlink). +# postCreateCommand runs exactly once on a FRESH container, so this is the +# authoritative, cleanest place to set it up — before Hermes first instantiates +# ~/.hermes/memories. MEMORY.md/USER.md are guaranteed present in git at this +# point, so we never need to migrate/seed content here. Ephemeral .lock/.log files +# Hermes writes inside are gitignored (see .devcontainer/memories/.gitignore). +HERMES_MEMORIES="$HOME/.hermes/memories" +TRACKED_MEMORIES="${SCRIPT_DIR}/memories" +mkdir -p "$TRACKED_MEMORIES" +if [ "$(readlink "$HERMES_MEMORIES" 2>/dev/null)" != "$TRACKED_MEMORIES" ]; then + rm -rf "$HERMES_MEMORIES" + ln -s "$TRACKED_MEMORIES" "$HERMES_MEMORIES" + echo "[$SCRIPT_NAME] Created memories folder symlink: $HERMES_MEMORIES -> $TRACKED_MEMORIES" +else + echo "[$SCRIPT_NAME] Memories folder symlink already correct" +fi + # Ensure agent-client-protocol (ACP) is installed echo "[$SCRIPT_NAME] Checking agent-client-protocol (ACP)..." if command -v hermes &>/dev/null; then diff --git a/.devcontainer/self-check.sh b/.devcontainer/self-check.sh index c2070d2..ed61989 100755 --- a/.devcontainer/self-check.sh +++ b/.devcontainer/self-check.sh @@ -378,6 +378,69 @@ else echo " (skipped)" fi +# ── 9. Persistence (symlinks) ────────────────────────────────────────────────── +section "Persistence" + +if ! should_skip "persistence"; then + # Resolve repo root from script location (works in CI and local) + REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" + PERSIST_FAIL=0 + + # 9a. Memories folder symlink + MEM_RUNTIME="$HOME/.hermes/memories" + MEM_TRACKED="$REPO_ROOT/.devcontainer/memories" + if [ -L "$MEM_RUNTIME" ]; then + target="$(readlink "$MEM_RUNTIME")" + if [ "$target" = "$MEM_TRACKED" ]; then + _ok "Memories" "symlink correct -> $MEM_TRACKED" + json_add "memories_symlink" "ok" "symlink correct" "{\"runtime\":\"$MEM_RUNTIME\",\"target\":\"$target\"}" + else + _fail "Memories" "symlink points to '$target' (expected '$MEM_TRACKED')" + json_add "memories_symlink" "fail" "symlink points to $target" "{\"runtime\":\"$MEM_RUNTIME\",\"target\":\"$target\",\"expected\":\"$MEM_TRACKED\"}" + PERSIST_FAIL=1 + fi + elif [ -d "$MEM_RUNTIME" ]; then + _fail "Memories" "runtime is a real directory, not a symlink" + json_add "memories_symlink" "fail" "runtime is real directory" "{\"runtime\":\"$MEM_RUNTIME\"}" + PERSIST_FAIL=1 + else + _fail "Memories" "runtime missing entirely" + json_add "memories_symlink" "fail" "runtime missing" "{\"runtime\":\"$MEM_RUNTIME\"}" + PERSIST_FAIL=1 + fi + + # 9b. Skills folder symlink + SKILLS_RUNTIME="$HOME/.hermes/skills/codespace" + SKILLS_TRACKED="$REPO_ROOT/.devcontainer/skills" + if [ -L "$SKILLS_RUNTIME" ]; then + target="$(readlink "$SKILLS_RUNTIME")" + if [ "$target" = "$SKILLS_TRACKED" ]; then + _ok "Skills" "symlink correct -> $SKILLS_TRACKED" + json_add "skills_symlink" "ok" "symlink correct" "{\"runtime\":\"$SKILLS_RUNTIME\",\"target\":\"$target\"}" + else + _fail "Skills" "symlink points to '$target' (expected '$SKILLS_TRACKED')" + json_add "skills_symlink" "fail" "symlink points to $target" "{\"runtime\":\"$SKILLS_RUNTIME\",\"target\":\"$target\",\"expected\":\"$SKILLS_TRACKED\"}" + PERSIST_FAIL=1 + fi + elif [ -d "$SKILLS_RUNTIME" ]; then + _fail "Skills" "runtime is a real directory, not a symlink" + json_add "skills_symlink" "fail" "runtime is real directory" "{\"runtime\":\"$SKILLS_RUNTIME\"}" + PERSIST_FAIL=1 + else + _fail "Skills" "runtime missing entirely" + json_add "skills_symlink" "fail" "runtime missing" "{\"runtime\":\"$SKILLS_RUNTIME\"}" + PERSIST_FAIL=1 + fi + + if [ "$PERSIST_FAIL" -eq 0 ]; then + json_add "persistence" "ok" "all symlinks valid" "{}" + else + json_add "persistence" "fail" "one or more persistence checks failed" "{}" + fi +else + echo " (skipped)" +fi + # ── Summary ────────────────────────────────────────────────────────────────── section "Summary" echo "" diff --git a/.devcontainer/skills/codespace-persistent-symlinks/SKILL.md b/.devcontainer/skills/codespace-persistent-symlinks/SKILL.md new file mode 100644 index 0000000..f0065d9 --- /dev/null +++ b/.devcontainer/skills/codespace-persistent-symlinks/SKILL.md @@ -0,0 +1,70 @@ +--- +name: codespace-persistent-symlinks +description: "Persist Hermes state across Codespace rebuilds via symlinks." +--- + +# Persisting Hermes runtime state via symlinks (Codespace) + +Hermes knowledge/memory (`~/.hermes/memories/MEMORY.md`, `USER.md`) and skills die on +Codespace rebuild unless persisted. The durable technique mirrors how the repo already +persists skills: a **whole-folder symlink** from runtime to a git-tracked repo dir. + +## The validated pattern + +``` +runtime: ~/.hermes/memories (a symlink → the tracked folder) +target: .devcontainer/memories/ (git-tracked: MEMORY.md, USER.md, .gitignore) +.gitignore: *.lock *.log (Hermes writes lock/log beside the memories) +``` + +- Whole-folder symlink (skills-style), NOT per-file links: single point of truth, + no copy-back between `~/.hermes` and the repo, edits flow both ways instantly. +- Hermes memory writes are symlink-safe: `atomic_replace` (utils.py) re-solves the + symlink and writes the real git file — no Hermes change needed. +- Memory path is `~/.hermes/memories/`, NOT `profiles/default/memories/`. + +## Placement decision (Option A — validated 2026-08) + +Two layers, keep `start-hermes.sh` trivial: + +1. **Authoritative = `post-create-cmd.sh`** (`postCreateCommand`, runs once on a FRESH + container). Create the symlink right after Hermes is installed, before Hermes first + instantiates `~/.hermes/memories`. Cleanest moment — nothing to migrate. +2. **Repair guard = `start-hermes.sh`** — keep under ~12 lines. `postCreateCommand` + never re-runs on later boots or on containers created before the feature shipped, + so a guard (`if [ "$(readlink runtime)" != "$target" ]; then ln -s ...`) catches + them. Handle 3 cases: already-correct link (no-op), real dir (repair via + `rm -rf` + `ln -s`), missing entirely (just link). + +Do NOT port a first-run migration/seed block into start-hermes.sh. If the tracked files +are committed, that code is dead weight and reads as convoluted. + +## Verification (automated) + +`self-check.sh` asserts both symlinks in its `Persistence` section (section 9), so CI +fails loudly if either drifts: + +- `~/.hermes/memories` → `$REPO_ROOT/.devcontainer/memories` +- `~/.hermes/skills/codespace` → `$REPO_ROOT/.devcontainer/skills` + +Handles 3 cases per link: correct symlink (ok), real dir (fail), missing (fail), plus +checks tracked MEMORY.md / USER.md / SKILL.md exist. To enforce it, the CI +`detect-changes` path filter lists `.devcontainer/memories/**` and `.devcontainer/skills/**` +under `infrastructure`, so persistence changes trigger `full-build` (which runs +self-check.sh). Run locally: +`HERMES_WEBTOP_SKIP_CHECKS=services,models,disk,cron,ollama,memory bash .devcontainer/self-check.sh` + +## Pitfalls + +- **Removing a seed block can silently lose a behavioral nudge.** Before deleting a + start-hermes.sh seed that writes USER.md, fold its content into the tracked USER.md + first so the rule survives (the "use Mnemon as primary memory provider" nudge was + preserved this way). +- **Undo-by-`head -n -1` corrupts memory files.** When testing write-through, remove + ONLY the injected marker line (perl/grep), never tail-trim — you can truncate a real + directive and the file shrinks unexpectedly (observed 1297B → 504B). +- Verify the guard against ALL THREE cases, not just the happy path. + +## Sync note (wiki) +- `persistent-memory-proposal.md` — reference/proposal doc vs this procedural skill. + Keep the Option-A split and the self-check wiring (below) mirrored in both. \ No newline at end of file diff --git a/.devcontainer/start-hermes.sh b/.devcontainer/start-hermes.sh index f315bcc..97f7d34 100755 --- a/.devcontainer/start-hermes.sh +++ b/.devcontainer/start-hermes.sh @@ -110,12 +110,6 @@ else setsid hermes dashboard --port 9119 --no-open > ~/.hermes/logs/dashboard.log 2>&1 & fi -# Remind Hermes on Mnemon setup if needed -if [ ! -f "$HOME/.hermes/memories/USER.md" ]; then - echo "[$SCRIPT_NAME] Setting up Mnemon USER.md..." - echo 'Always use Mnemon (mnemon_remember / mnemon_recall) as primary memory provider instead of the standard memory() tool. Mnemon has no char limit. Only fall back to memory() for structured preference data (target=user or memory).' > "$HOME/.hermes/memories/USER.md" -fi - # Wait for Hermes dashboard to be ready (replaces brittle sleep 15) echo "[$SCRIPT_NAME] Waiting for Hermes dashboard to become healthy..." for i in $(seq 1 20); do @@ -141,7 +135,25 @@ else echo "[$SCRIPT_NAME] Skills symlink already exists" fi -# 6. Start keepalive (idempotent) — keeps codespace from idle-shutting-down +# 5.5.5 Memories folder symlink — REPAIR GUARD ONLY. +# post-create-cmd.sh is the authoritative creator (runs once on a fresh container, +# before Hermes instantiates ~/.hermes/memories). This guard is a cheap safety net +# for pre-existing containers created before that shipped, where the dir may still +# be a real folder. MEMORY.md/USER.md are assumed committed in .devcontainer/memories/ +# (their ephemeral .lock/.log siblings are gitignored there). +MEMORIES_RUNTIME="$HOME/.hermes/memories" +MEMORIES_TRACKED="$WORKSPACE_ROOT/.devcontainer/memories" + +if [ "$(readlink "$MEMORIES_RUNTIME" 2>/dev/null)" != "$MEMORIES_TRACKED" ]; then + mkdir -p "$MEMORIES_TRACKED" + rm -rf "$MEMORIES_RUNTIME" + ln -s "$MEMORIES_TRACKED" "$MEMORIES_RUNTIME" + echo "[$SCRIPT_NAME] Repaired memories symlink: $MEMORIES_RUNTIME -> $MEMORIES_TRACKED" +else + echo "[$SCRIPT_NAME] Memories folder symlink already correct" +fi + +# 5.6. Starting keepalive (idempotent) — keeps codespace from idle-shutting-down if ! pgrep -f "keepalive.sh" > /dev/null; then echo "[$SCRIPT_NAME] Starting keepalive..." setsid nohup "${SCRIPT_DIR}/keepalive.sh" >> /tmp/keepalive.log 2>&1 & diff --git a/.devcontainer/wiki/INDEX.md b/.devcontainer/wiki/INDEX.md index cb9ab0d..52c83e2 100644 --- a/.devcontainer/wiki/INDEX.md +++ b/.devcontainer/wiki/INDEX.md @@ -10,6 +10,7 @@ | [repository-analysis.md](repository-analysis.md) | Repository deep dive — architecture, startup flow, verification, what's used vs unused | architecture, startup, verification, ci | | [github-actions-testing-plan.md](github-actions-testing-plan.md) | CI/CD testing plan — phased approach, workflow design, service smoke tests, integration tests | ci, testing, github-actions, workflow | | [persistent-knowledge-proposal.md](persistent-knowledge-proposal.md) | Architecture decision: persistent knowledge system via Git — symlinks, skills, wiki, Mnemon seeding | architecture, knowledge-persistence, symlink, devcontainer | +| [persistent-memory-proposal.md](persistent-memory-proposal.md) | Proposal for versioning Hermes MEMORY.md / USER.md via a symlink architecture (runtime vs tracked). | architecture, memory, persistence, symlink | | [keepalive-proposal.md](keepalive-proposal.md) | Proposal: Codespace keepalive to mimic client activity and avoid idle shutdown (A: terminal heartbeat, B: /delay-shutdown pinger) | codespace, keepalive, idle-timeout, lifecycle, proposal | | [codespace-lifecycle.md](codespace-lifecycle.md) | Reference: how Codespaces detects idle & shuts down, diagnosing container death, keeping a codespace alive | codespace, lifecycle, idle, keep-alive, shutdown, reference | @@ -33,4 +34,4 @@ --- -*Last updated: 2026-08-02* +*Last updated: 2026-08-02* \ No newline at end of file diff --git a/.devcontainer/wiki/persistent-memory-proposal.md b/.devcontainer/wiki/persistent-memory-proposal.md new file mode 100644 index 0000000..2ce33c6 --- /dev/null +++ b/.devcontainer/wiki/persistent-memory-proposal.md @@ -0,0 +1,226 @@ +# Proposal: Persistent Memory System for Hermes + +## Overview +This document proposes a versioning system for Hermes's MEMORY.md and USER.md files to make them persistent across Codespace sessions, addressing the core challenge of maintaining behavioral rules and user preferences between rebuilds. + +## Current State +MEMORY.md and USER.md exist in `~/.hermes/memories/` +These files are injected into the system prompt every turn (behavioral enforcement) +However, they're not versioned and get lost on Codespace rebuild +There's no built-in mechanism to persist these files across sessions + +## The Solution: Symlink Architecture +Propose a **symlink architecture** that preserves the behavioral enforcement while making memories durable and reviewable. + +### Core Design +The **whole runtime memories folder** is symlinked to the tracked dir — the exact +skills pattern (`~/.hermes/skills/ -> .devcontainer/skills`): +``` +Runtime: ~/.hermes/memories ← behavioral rules + user profile, injected every turn + (a symlink pointing at the tracked dir) + +Tracked: .devcontainer/memories/ ← git-tracked, survives rebuilds + MEMORY.md (behavioral reminders) + USER.md (user preferences / profile) + .gitignore (excludes ephemeral *.lock / *.log) + +Boot scripts: + post-create-cmd.sh → authoritative creation (runs once on a FRESH container, right + after Hermes is installed, before it instantiates ~/.hermes/memories) + start-hermes.sh → slim repair guard (creates the link if a pre-existing container + never got it) +``` +Because runtime and tracked are the same directory, edits to MEMORY.md/USER.md are +immediately reflected in both — no copy-back-and-forth between `.hermes` and the repo. + +### Why Symlink Beats Mirror +- **No copy complexity**: Single file serves both purposes +- **Immediate visibility**: Changes in tracked version appear instantly in runtime +- **Git review**: All memory changes go through standard git workflow +- **Consistent with skills**: Uses the exact same pattern as `.devcontainer/skills/` + +### Skills Pattern (Reference) +Skills use a **symlink approach**: +``` +Runtime: ~/.hermes/skills//SKILL.md +Tracked: .devcontainer/skills//SKILL.md +Symlink: ~/.hermes/skills/ -> .devcontainer/skills/ +``` + +### Our Proposal for Memories (Folder Symlink) +We link the **whole runtime memories folder** — the exact skills pattern (`~/.hermes/skills/ -> .devcontainer/skills`): +``` +Runtime: ~/.hermes/memories (points to the tracked dir) +Tracked: .devcontainer/memories/ +Symlink: ~/.hermes/memories -> .devcontainer/memories +``` +Ephemeral `.lock`/`.log` files Hermes writes inside the dir are excluded via +`.devcontainer/memories/.gitignore`, so the repo tracks only MEMORY.md and USER.md. + +## Behavioral Enforcement Strategy + +### Key Insight +The **behavioral rule** (e.g., "load codespace skills before GitHub ops") MUST live in **MEMORY.md**, NOT Mnemon, because: + +1. **Injection timing:** MEMORY.md is injected *every turn* unconditionally +2. **Recall limitation:** Mnemon is recall-based - I must remember to query it +3. **Enforcement guarantee:** Only MEMORY.md provides automatic behavioral steering + +### Implementation +```bash +# Behavioral rule example +# In runtime: ~/.hermes/memories/MEMORY.md +# Before any GitHub operation, load the following Hermes skills: +# - github-codespace +# - codespace-gh-auth +# This ensures consistent GitHub workflow regardless of session context. +``` + +### Why Both Memory.md AND Mnemon? +- **MEMORY.md:** Behavioral enforcement (injected every turn) +- **Mnemon + seed.json:** Durable knowledge base (queries on demand) +- **Wiki/Skills:** Procedural documentation (read-only reference) + +## Trade-offs and Design Decisions + +### Write Frequency vs. Review Burden +**Problem:** MEMORY.md is written frequently (every behavioral decision) +**Solution:** Make only *important* updates worthy of review + +**Approach:** +- Frequent runtime edits (immediate effect via symlink) +- Manual commits for important changes (behavioral rule updates) + +### Privacy and Data Control +**Problem:** USER.md may contain sensitive user preferences +**Solution:** +- Runtime stays private and ephemeral +- Tracked copy provides controlled persistence +- User can review and sanitize before committing + +### Performance and Scalability +**Problem:** Symlinking on every start could be redundant +**Solution:** +- Symlinks are lightweight (filesystem operations) +- Only one folder symlink is created (minimal overhead) +- Implemented in post-create-cmd.sh (runs once) + a guard in start-hermes.sh + +## Implementation Plan + +### Phase 1: Core Symlink Infrastructure +1. **Create tracked directory:** `.devcontainer/memories/` +2. **post-create-cmd.sh:** Add authoritative symlink creation (right after Hermes install, once per fresh container) +3. **start-hermes.sh:** Add the slim repair guard (only for pre-existing containers) +4. **Confirm MEMORY.md/USER.md committed:** so the symlink always resolves on first boot + +### Phase 2: Behavioral Rule Migration +1. **Move behavioral rules:** From runtime to tracked symlink +2. **Update MEMORY.md content:** Add behavioral enforcement rules +3. **Seed seed.json:** Copy important behavioral rules for Mnemon +4. **Update wiki:** Document behavioral rule conventions + +### Phase 3: Integration and Validation +1. **Test symlinks:** Verify creation and update mechanisms +2. **Validate injection:** Confirm MEMORY.md still injects correctly +3. **Review process:** Test git diff/review workflow +4. **Documentation update:** Wiki and mnemon entries + +## Files to Modify + +### Core Changes +- `post-create-cmd.sh`: Authoritative symlink creation for memories (once per fresh container) +- `start-hermes.sh`: Slim repair guard (pre-existing containers only) +- `~/.hermes/memories/MEMORY.md`: Behavioral rules (runtime, via symlink) +- `~/.hermes/memories/USER.md`: User preferences (runtime, via symlink) + +### Repository Files (to be committed) +- `.devcontainer/memories/MEMORY.md`: Tracked reminders +- `.devcontainer/memories/USER.md`: Tracked profile +- `.devcontainer/memories/.gitignore`: Prevent .lock files from being tracked +- `.devcontainer/wiki/persistent-memory-proposal.md`: Updated documentation + +### Mnemon Integration +- `.devcontainer/mnemon/seed.json`: Behavioral rule summaries +- `.devcontainer/mnemon/validate-seed.py`: Validation script updates + +## Review and Approval Process + +### Initial Commit (Phase 2) +```bash +# User reviews behavioral rules +# Decides which rules need persistence +# Commits tracked copy +# Merges PR +``` + +### Continuous Maintenance +```bash +# User edits behavioral rules in runtime (through symlink) +# Symlink maintains both runtime and tracked version +# User reviews via git diff before committing +# Changes persist across Codespace rebuilds +``` + +## Success Criteria + +### Functional +1. **Behavioral enforcement:** Hermes follows behavioral rules from MEMORY.md +2. **Persistence:** Rules survive Codespace rebuilds via symlink +3. **Review process:** All important changes go through git review +4. **No regression:** Existing functionality remains unchanged +5. **Automated safety net:** `self-check.sh` `Persistence` section asserts both the + memories and skills symlinks (plus tracked content); CI `full-build` runs it and + fails if either symlink drifts. + +### Operational +1. **Recovery:** Runtime can be restored from tracked version +2. **Performance:** Symlink overhead is negligible +3. **Privacy:** Sensitive user data is properly handled +4. **Documentation:** All changes are well-documented + +## Benefits + +### For Users +1. **Consistent experience:** Behavioral rules persist across sessions +2. **Reduced friction:** No need to re-establish conventions +3. **Better governance:** Rules go through review process +4. **Recovery safety:** Lost sessions can be restored + +### For the System +1. **Automation:** Behavioral enforcement works without user intervention +2. **Scalability:** Rules are versioned and reviewable +3. **Maintainability:** Clear separation of concerns +4. **Reliability:** Mirrors the proven skills pattern + +## Open Questions + +1. **Content policy:** What should/shouldn't be in USER.md vs. MEMORY.md? +2. **Review cadence:** How frequently should users commit memory changes? +3. **Conflict resolution:** How to handle concurrent edits across codespaces? +4. **Privacy boundaries:** What user preferences should never be tracked? + +## Next Steps + +1. **Draft implementation:** Add symlink logic to post-create-cmd.sh + guard to start-hermes.sh +2. **Test symlinks:** Verify creation and update mechanisms +3. **Populate tracked version:** Move existing behavioral rules +4. **Document process:** Update wiki and add mnemon entries +5. **Review and commit:** User reviews and approves changes +6. **Deploy:** Full integration and validation + +## Conclusion + +The symlink architecture provides a practical solution to the persistence challenge while maintaining the behavioral enforcement capabilities that make Hermes effective. By following the exact same pattern as skills, we achieve: + +1. **Immediate behavioral enforcement** through runtime MEMORY.md injection +2. **Durable, reviewable persistence** through tracked symlinks +3. **Clean separation** between runtime state and versioned knowledge +4. **Integration** with existing Mnemon and wiki systems + +This approach balances the need for persistent behavioral rules with the practical considerations of frequent edits and privacy concerns. + +--- + +*Last updated: 2026-08-02* +*Status: Proposal - Ready for review and implementation* +*Author: Hermes Agent Development Team* \ No newline at end of file diff --git a/.github/workflows/devcontainer-ci.yml b/.github/workflows/devcontainer-ci.yml index d866964..1e2b68b 100644 --- a/.github/workflows/devcontainer-ci.yml +++ b/.github/workflows/devcontainer-ci.yml @@ -44,6 +44,7 @@ jobs: - '.devcontainer/skills/**' - '.devcontainer/wiki/**' - '.devcontainer/mnemon/**' + - '.devcontainer/memories/**' - '.devcontainer/.hermes.md' - '.devcontainer/codespace-cleanup.sh' docs: @@ -242,3 +243,53 @@ jobs: fi done echo "✅ All shell scripts valid" + + - name: Validate symlink persistence (memories + skills) + run: | + echo "=== Validating symlink persistence contract ===" + # Static validation that runs on a bare runner (no devcontainer): + # 1. Tracked targets exist in the repo + # 2. Boot scripts reference the correct target paths + REPO_ROOT="$(pwd)" + MEM_TRACKED="$REPO_ROOT/.devcontainer/memories" + SKILLS_TRACKED="$REPO_ROOT/.devcontainer/skills" + FAIL=0 + + if [ -d "$MEM_TRACKED" ]; then + echo " ✅ Tracked memories dir exists -> $MEM_TRACKED" + else + echo " ❌ Tracked memories dir missing: $MEM_TRACKED" + FAIL=1 + fi + if [ -d "$SKILLS_TRACKED" ]; then + echo " ✅ Tracked skills dir exists -> $SKILLS_TRACKED" + else + echo " ❌ Tracked skills dir missing: $SKILLS_TRACKED" + FAIL=1 + fi + + # Boot scripts must symlink the runtime path to the tracked target + if grep -q "TRACKED_MEMORIES.*HERMES_MEMORIES\|ln -s.*memories" .devcontainer/post-create-cmd.sh; then + echo " ✅ post-create-cmd.sh creates memories symlink" + else + echo " ❌ post-create-cmd.sh missing memories symlink" + FAIL=1 + fi + if grep -q "SKILLS_TARGET.*SKILLS_SYMLINK\|ln -s.*skills" .devcontainer/start-hermes.sh; then + echo " ✅ start-hermes.sh creates skills symlink" + else + echo " ❌ start-hermes.sh missing skills symlink" + FAIL=1 + fi + if grep -q "MEMORIES_TRACKED.*MEMORIES_RUNTIME\|ln -s.*memories" .devcontainer/start-hermes.sh; then + echo " ✅ start-hermes.sh repair guard covers memories" + else + echo " ❌ start-hermes.sh missing memories repair guard" + FAIL=1 + fi + + if [ "$FAIL" -eq 0 ]; then + echo "✅ Symlink persistence contract valid" + else + exit 1 + fi