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
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,18 @@ to `.pi/`. The extension maps Mnemon's lifecycle reminders onto Pi events
(`resources_discover`, `before_agent_start`, `agent_end`,
`session_before_compact`). Start a new Pi session or run `/reload` to activate.

### [Hermes Agent](https://github.com/NousResearch/hermes-agent)

```bash
mnemon setup --target hermes --yes
```

One command deploys the mnemon skill, prompt files, and Hermes shell hooks to
`~/.hermes/`. The integration uses Hermes' native lifecycle hooks:
`on_session_start`, `pre_llm_call`, `post_llm_call`, and optional
`on_session_finalize`. Hermes may prompt once to approve the installed shell
hooks.

### [NanoClaw](https://github.com/qwibitai/nanoclaw)

NanoClaw runs agents inside Linux containers. Use the `/add-mnemon` skill to integrate:
Expand Down
133 changes: 128 additions & 5 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,32 @@ var setupCmd = &cobra.Command{

By default, installs to project-local config (.claude/, .codex/, .openclaw/, .nanobot/, .pi/).
Use --global to install to user-wide config (~/.claude/, ~/.codex/, ~/.openclaw/, ~/.nanobot/workspace/, ~/.pi/agent/).
Hermes Agent uses its native user config at ~/.hermes/.

Supported environments: Claude Code, Codex, OpenClaw, Nanobot, Pi.
Supported environments: Claude Code, Codex, OpenClaw, Nanobot, Pi, Hermes Agent.

Examples:
mnemon setup # Interactive: project-local install
mnemon setup --global # Interactive: user-wide install
mnemon setup --target claude-code # Non-interactive: Claude Code only
mnemon setup --target hermes # Non-interactive: Hermes Agent only
mnemon setup --eject # Interactive: remove integrations
mnemon setup --eject --target claude-code # Non-interactive: remove Claude Code only
mnemon setup --yes # Auto-confirm all prompts`,
RunE: runSetup,
}

func init() {
setupCmd.Flags().StringVar(&setupTarget, "target", "", "target environment (claude-code, codex, openclaw, nanobot, pi)")
setupCmd.Flags().StringVar(&setupTarget, "target", "", "target environment (claude-code, codex, openclaw, nanobot, pi, hermes)")
setupCmd.Flags().BoolVar(&setupEject, "eject", false, "remove mnemon integrations")
setupCmd.Flags().BoolVar(&setupYes, "yes", false, "auto-confirm all prompts (CI-friendly)")
setupCmd.Flags().BoolVar(&setupGlobal, "global", false, "install to user-wide config instead of project-local config")
rootCmd.AddCommand(setupCmd)
}

func runSetup(cmd *cobra.Command, args []string) error {
if setupTarget != "" && setupTarget != "claude-code" && setupTarget != "codex" && setupTarget != "openclaw" && setupTarget != "nanobot" && setupTarget != "pi" {
return fmt.Errorf("invalid target %q (must be claude-code, codex, openclaw, nanobot, or pi)", setupTarget)
if setupTarget != "" && setupTarget != "claude-code" && setupTarget != "codex" && setupTarget != "openclaw" && setupTarget != "nanobot" && setupTarget != "pi" && setupTarget != "hermes" {
return fmt.Errorf("invalid target %q (must be claude-code, codex, openclaw, nanobot, pi, or hermes)", setupTarget)
}

envs := setup.DetectEnvironments(setupGlobal)
Expand Down Expand Up @@ -83,7 +85,7 @@ func runInstallFlow(envs []setup.Environment) error {

if len(detected) == 0 {
fmt.Println("\nNo supported LLM CLI environments detected.")
fmt.Println("Install Claude Code, Codex, OpenClaw, Nanobot, or Pi, then run 'mnemon setup' again.")
fmt.Println("Install Claude Code, Codex, OpenClaw, Nanobot, Pi, or Hermes Agent, then run 'mnemon setup' again.")
return nil
}

Expand Down Expand Up @@ -133,6 +135,8 @@ func installEnv(env *setup.Environment) error {
err = installNanobot(env)
case "pi":
err = installPi(env)
case "hermes":
err = installHermes(env)
}
if err != nil {
return err
Expand Down Expand Up @@ -606,6 +610,118 @@ func installPi(env *setup.Environment) error {
return nil
}

// ─── Hermes Agent ───────────────────────────────────────────────────

func installHermes(env *setup.Environment) error {
configDir := env.ConfigDir

fmt.Printf("\nSetting up Hermes Agent (%s)...\n", configDir)

fmt.Println("\n[1/4] Skill")
if path, err := setup.HermesWriteSkill(configDir); err != nil {
setup.StatusError(0, 0, "Skill", err)
return err
} else {
setup.StatusOK(0, 0, "Skill", path)
}

fmt.Println("\n[2/4] Prompts")
var promptPath string
if path, err := setup.WritePromptFiles(); err != nil {
setup.StatusError(0, 0, "Prompts", err)
return err
} else {
setup.StatusOK(0, 0, "Prompts", path)
promptPath = path
}

fmt.Println("\n[3/4] Hooks")
sel := selectHermesOptionalHooks()
if path, err := setup.HermesWriteHook(configDir, "prime.sh", assets.HermesPrimeHook); err != nil {
setup.StatusError(0, 0, "Hook: prime", err)
return err
} else {
setup.StatusOK(0, 0, "Hook: prime", path)
}
if sel.Remind {
if path, err := setup.HermesWriteHook(configDir, "remind.sh", assets.HermesRemindHook); err != nil {
setup.StatusError(0, 0, "Hook: remind", err)
return err
} else {
setup.StatusOK(0, 0, "Hook: remind", path)
}
}
if sel.Nudge {
if path, err := setup.HermesWriteHook(configDir, "nudge.sh", assets.HermesNudgeHook); err != nil {
setup.StatusError(0, 0, "Hook: nudge", err)
return err
} else {
setup.StatusOK(0, 0, "Hook: nudge", path)
}
}
if sel.Compact {
if path, err := setup.HermesWriteHook(configDir, "compact.sh", assets.HermesCompactHook); err != nil {
setup.StatusError(0, 0, "Hook: compact", err)
return err
} else {
setup.StatusOK(0, 0, "Hook: compact", path)
}
}

fmt.Println("\n[4/4] Config")
if path, err := setup.HermesRegisterHooks(configDir, sel); err != nil {
setup.StatusError(0, 0, "Config", err)
return err
} else {
setup.StatusUpdated(0, 0, "Config", path)
}

var hookNames []string
hookNames = append(hookNames, "prime")
if sel.Remind {
hookNames = append(hookNames, "remind")
}
if sel.Nudge {
hookNames = append(hookNames, "nudge")
}
if sel.Compact {
hookNames = append(hookNames, "compact")
}

fmt.Println()
fmt.Println("Setup complete!")
fmt.Printf(" Skill %s/skills/mnemon/SKILL.md\n", configDir)
fmt.Printf(" Hooks %s/config.yaml (%s)\n", configDir, strings.Join(hookNames, ", "))
fmt.Printf(" Prompts %s/ (guide.md, skill.md)\n", promptPath)
fmt.Println()
fmt.Println("Start a new Hermes session to activate.")
fmt.Println("Hermes may prompt once to approve the installed shell hooks.")
fmt.Println("Run 'mnemon setup --eject --target hermes' to remove.")

return nil
}

func selectHermesOptionalHooks() setup.HookSelection {
sel := setup.HookSelection{Remind: true, Nudge: true, Compact: false}

if setupYes || !setup.IsInteractive() {
return sel
}

opts := []string{
"Remind — recall relevant memories before each LLM call (recommended)",
"Nudge — queue remember guidance after each LLM response",
"Compact — queue preservation guidance on session finalization",
}
defs := []bool{true, true, false}
choices := setup.SelectMulti("Select hooks to enable", opts, defs)

sel.Remind = choices[0]
sel.Nudge = choices[1]
sel.Compact = choices[2]
return sel
}

// ─── Eject ──────────────────────────────────────────────────────────

func runEjectFlow(envs []setup.Environment) error {
Expand Down Expand Up @@ -705,6 +821,13 @@ func ejectEnv(env *setup.Environment) error {
if len(errs) > 0 {
return errs[0]
}

case "hermes":
errs := setup.HermesEject(env.ConfigDir)
ejectMarkdown("AGENTS.md", "Remove memory guidance from ./AGENTS.md?")
if len(errs) > 0 {
return errs[0]
}
}
return nil
}
Expand Down
5 changes: 3 additions & 2 deletions docs/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mnemon setup --target claude-code
mnemon setup --target openclaw
mnemon setup --target pi
mnemon setup --target nanobot --global
mnemon setup --target hermes

# Auto-confirm all prompts (CI-friendly)
mnemon setup --yes
Expand All @@ -43,8 +44,8 @@ mnemon setup --eject --target claude-code

| Flag | Default | Description |
|---|---|---|
| `--global` | `false` | Install to user-wide config instead of project-local (recommended for Nanobot: installs to `~/.nanobot/workspace/`; Pi installs to `~/.pi/agent/`) |
| `--target <name>` | (auto-detect) | Target environment: `claude-code`, `codex`, `openclaw`, `nanobot`, or `pi` |
| `--global` | `false` | Install to user-wide config instead of project-local (recommended for Nanobot: installs to `~/.nanobot/workspace/`; Pi installs to `~/.pi/agent/`; Hermes installs to `~/.hermes/`) |
| `--target <name>` | (auto-detect) | Target environment: `claude-code`, `codex`, `openclaw`, `nanobot`, `pi`, or `hermes` |
| `--eject` | `false` | Remove mnemon integrations |
| `--yes` | `false` | Auto-confirm all prompts |

Expand Down
11 changes: 11 additions & 0 deletions docs/zh/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ mnemon setup --target pi --yes
(`resources_discover`、`before_agent_start`、`agent_end`、
`session_before_compact`)。启动新的 Pi session 或运行 `/reload` 即可激活。

### [Hermes Agent](https://github.com/NousResearch/hermes-agent)

```bash
mnemon setup --target hermes --yes
```

一条命令将 mnemon skill、prompt 文件和 Hermes shell hooks 部署到
`~/.hermes/`。该集成使用 Hermes 原生生命周期 hooks:
`on_session_start`、`pre_llm_call`、`post_llm_call`,以及可选的
`on_session_finalize`。Hermes 可能会在首次运行时提示批准这些 shell hooks。

### [NanoClaw](https://github.com/qwibitai/nanoclaw)

NanoClaw 在 Linux 容器内运行智能体。使用 `/add-mnemon` 技能集成:
Expand Down
5 changes: 3 additions & 2 deletions docs/zh/USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ mnemon setup --target codex
mnemon setup --target openclaw
mnemon setup --target pi
mnemon setup --target nanobot --global
mnemon setup --target hermes

# 自动确认所有提示(CI 友好)
mnemon setup --yes
Expand All @@ -44,8 +45,8 @@ mnemon setup --eject --target claude-code

| 标志 | 默认值 | 说明 |
|---|---|---|
| `--global` | `false` | 安装到用户级配置而非项目本地(Nanobot 推荐安装到 `~/.nanobot/workspace/`;Pi 安装到 `~/.pi/agent/`) |
| `--target <name>` | (自动检测) | 目标环境:`claude-code`、`codex`、`openclaw`、`nanobot` 或 `pi` |
| `--global` | `false` | 安装到用户级配置而非项目本地(Nanobot 推荐安装到 `~/.nanobot/workspace/`;Pi 安装到 `~/.pi/agent/`;Hermes 安装到 `~/.hermes/`) |
| `--target <name>` | (自动检测) | 目标环境:`claude-code`、`codex`、`openclaw`、`nanobot`、`pi` 或 `hermes` |
| `--eject` | `false` | 移除 mnemon 集成 |
| `--yes` | `false` | 自动确认所有提示 |

Expand Down
17 changes: 16 additions & 1 deletion internal/setup/assets/assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,22 @@ var PiSkill []byte
//go:embed pi/mnemon.ts
var PiExtension []byte

//go:embed hermes/SKILL.md
var HermesSkill []byte

//go:embed hermes/prime.sh
var HermesPrimeHook []byte

//go:embed hermes/remind.sh
var HermesRemindHook []byte

//go:embed hermes/nudge.sh
var HermesNudgeHook []byte

//go:embed hermes/compact.sh
var HermesCompactHook []byte

// All returns the embedded filesystem for inspection.
//
//go:embed claude codex openclaw nanoclaw nanobot pi
//go:embed claude codex openclaw nanoclaw nanobot pi hermes
var All embed.FS
48 changes: 48 additions & 0 deletions internal/setup/assets/hermes/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
name: mnemon
description: Persistent memory CLI for Hermes Agent. Store facts, recall past knowledge, link related memories, manage lifecycle.
---

# mnemon

Use `mnemon` when durable memory can materially improve continuity across
Hermes sessions. Hooks may inject recalled context before an LLM call, but the
agent decides what is worth storing.

## Workflow

1. Recall when prior decisions, preferences, or facts may affect the current task:
`mnemon recall "<query>" --limit 10`
2. Remember only stable, reusable knowledge:
`mnemon remember "<fact>" --cat <cat> --imp <1-5> --entities "e1,e2" --source agent`
3. Link related memories after reviewing candidates from `remember`:
`mnemon link <id> <candidate> --type <causal|semantic> --weight <0-1>`

## Commands

```bash
mnemon remember "<fact>" --cat <cat> --imp <1-5> --entities "e1,e2" --source agent
mnemon link <id1> <id2> --type <type> --weight <0-1> [--meta '<json>']
mnemon recall "<query>" --limit 10
mnemon search "<query>" --limit 10
mnemon import --dry-run <file>
mnemon import <file>
mnemon forget <id>
mnemon related <id> --edge causal
mnemon gc --threshold 0.4
mnemon gc --keep <id>
mnemon status
mnemon log
mnemon store list
mnemon store create <name>
mnemon store set <name>
mnemon store remove <name>
```

## Guardrails

- Do not store secrets, passwords, tokens, private keys, or short-lived operational noise.
- Prefer concise insights over transcript dumps.
- Categories: `preference` · `decision` · `insight` · `fact` · `context`
- Edge types: `temporal` · `semantic` · `causal` · `entity`
- Max 8,000 chars per insight.
13 changes: 13 additions & 0 deletions internal/setup/assets/hermes/compact.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
# mnemon Hermes on_session_finalize hook.
# Queue compaction guidance for the next pre_llm_call. This preserves Hermes'
# non-blocking lifecycle while keeping memory decisions LLM-supervised.

STATE_DIR="${HERMES_HOME:-$HOME/.hermes}/mnemon"
mkdir -p "$STATE_DIR" 2>/dev/null || true

cat > "${STATE_DIR}/pending-nudge.txt" <<'EOF' 2>/dev/null || true
[mnemon] Session finalization occurred. Before relying on compressed or resumed context, preserve only critical continuity with mnemon remember when justified. Do not store transcript dumps.
EOF

printf '{}\n'
42 changes: 42 additions & 0 deletions internal/setup/assets/hermes/nudge.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/bin/bash
# mnemon Hermes post_llm_call hook.
# post_llm_call is observational, so queue a reminder for the next pre_llm_call
# instead of trying to force another model turn.

INPUT_FILE=$(mktemp)
trap 'rm -f "$INPUT_FILE"' EXIT
cat > "$INPUT_FILE"

python3 - "$INPUT_FILE" <<'PY'
import json
import os
from pathlib import Path
import sys

try:
payload = json.loads(Path(sys.argv[1]).read_text() or "{}")
except Exception:
payload = {}

extra = payload.get("extra") if isinstance(payload.get("extra"), dict) else {}
response = ""
for key in ("response_text", "response", "assistant_message"):
value = extra.get(key)
if isinstance(value, str):
response = value
break

if "mnemon" not in response.lower() and "durable memory" not in response.lower():
hermes_home = Path(os.environ.get("HERMES_HOME") or Path.home() / ".hermes")
state_dir = hermes_home / "mnemon"
try:
state_dir.mkdir(parents=True, exist_ok=True)
(state_dir / "pending-nudge.txt").write_text(
"[mnemon] Consider whether the previous exchange warrants durable memory. "
"Use mnemon remember only for stable decisions, preferences, facts, or insights.\n"
)
except Exception:
pass

print("{}")
PY
Loading
Loading