Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion internal/setup/assets/claude/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ Check the output `errors` field because imports can partially succeed.

## Guardrails

- Never run `remember` or `link` in the main conversation — always delegate to a sub-agent.
- Main conversation: never run `remember` or `link` directly; delegate to a Task sub-agent.
- Memory sub-agent: you are the leaf writer. Run `mnemon remember` and any justified `mnemon link` commands directly; do not spawn another sub-agent.
- Do not store secrets, passwords, or tokens.
- Categories: `preference` · `decision` · `insight` · `fact` · `context`
- Edge types: `temporal` · `semantic` · `causal` · `entity`
Expand Down
7 changes: 4 additions & 3 deletions internal/setup/assets/claude/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ Aim for a rough distribution: ~20% at 4-5, ~50% at 2-3, ~30% at 1.
Avoid defaulting everything to 4-5 — that defeats the scoring system.

**What to store**: both conclusions AND context. Prefer storing a little too much over missing something useful.
**How to store**: delegate to a Task sub-agent (`subagent_type="general-purpose"`, `model="sonnet"`).
**How to store from the main conversation**: delegate to a Task sub-agent (`subagent_type="general-purpose"`, `model="sonnet"`).
Only provide what to store — content, category, importance, entities, and create/update intent.
The sub-agent will read the mnemon skill and execute the correct commands itself.
The sub-agent is the leaf memory writer: it will read the mnemon skill and execute `mnemon remember` / `mnemon link` itself.

Do NOT: write CLI commands or workflow steps in the sub-agent prompt (the sub-agent has access to the skill docs and will use the correct flags).
Do NOT run memory writes in the main conversation, or remember operational/public/git-tracked/transient info.
Do NOT run memory writes in the main conversation, or remember operational/public/git-tracked/transient info.
Do NOT ask the memory sub-agent to delegate again; nested memory sub-agents waste tokens and can fail in Claude Code.
5 changes: 5 additions & 0 deletions internal/setup/assets/claude/stop.sh
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ if echo "$MSG" | grep -qiE "mnemon remember|sub-agent.*remember|Stored.*imp="; t
exit 0
fi

if echo "$INPUT" | grep -qE '"hook_event_name"[[:space:]]*:[[:space:]]*"SubagentStop"|"agent_id"[[:space:]]*:'; then
echo "[mnemon] If this sub-agent was delegated memory work, complete it directly with mnemon remember/link; do not spawn another sub-agent. If no durable memory is needed, say so and finish."
exit 0
fi

echo "[mnemon] Consider: does this exchange warrant a remember sub-agent?"
48 changes: 48 additions & 0 deletions internal/setup/claude_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package setup
import (
"os"
"path/filepath"
"strings"
"testing"

"github.com/mnemon-dev/mnemon/internal/setup/assets"
)

func TestPromptDirHonorsMnemonDataDir(t *testing.T) {
Expand Down Expand Up @@ -61,6 +64,51 @@ func TestWritePromptFilesWritesUnderMnemonDataDir(t *testing.T) {
}
}

func TestClaudeMemoryInstructionsKeepSubagentsLeafWriters(t *testing.T) {
tests := []struct {
name string
body string
want []string
}{
{
name: "guide",
body: string(assets.ClaudeGuide),
want: []string{
"The sub-agent is the leaf memory writer",
"Do NOT ask the memory sub-agent to delegate again",
},
},
{
name: "skill",
body: string(assets.ClaudeSkill),
want: []string{
"Main conversation: never run `remember` or `link` directly",
"Memory sub-agent: you are the leaf writer",
"do not spawn another sub-agent",
},
},
{
name: "stop hook",
body: string(assets.ClaudeStopHook),
want: []string{
`"hook_event_name"[[:space:]]*:[[:space:]]*"SubagentStop"`,
"complete it directly with mnemon remember/link",
"do not spawn another sub-agent",
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for _, want := range tt.want {
if !strings.Contains(tt.body, want) {
t.Fatalf("%s is missing %q", tt.name, want)
}
}
})
}
}

func TestCollidesWithUserConfigHomeInstall(t *testing.T) {
home := t.TempDir()
t.Setenv("HOME", home)
Expand Down
Loading