From 3f2928814d1cd56e73a9f23fdd10db8e64043079 Mon Sep 17 00:00:00 2001 From: Grivn Date: Thu, 14 May 2026 17:05:41 +0000 Subject: [PATCH 1/4] Define harness loop module standard --- docs/harness/LOOP_MODULE_STANDARD.md | 202 ++++++++++++++++++++++++ docs/harness/README.md | 3 + docs/zh/harness/LOOP_MODULE_STANDARD.md | 197 +++++++++++++++++++++++ docs/zh/harness/README.md | 3 + harness/memory-loop/README.md | 2 + harness/memory-loop/module.json | 46 ++++++ harness/skill-loop/README.md | 2 + harness/skill-loop/module.json | 48 ++++++ 8 files changed, 503 insertions(+) create mode 100644 docs/harness/LOOP_MODULE_STANDARD.md create mode 100644 docs/zh/harness/LOOP_MODULE_STANDARD.md create mode 100644 harness/memory-loop/module.json create mode 100644 harness/skill-loop/module.json diff --git a/docs/harness/LOOP_MODULE_STANDARD.md b/docs/harness/LOOP_MODULE_STANDARD.md new file mode 100644 index 0000000..12dd405 --- /dev/null +++ b/docs/harness/LOOP_MODULE_STANDARD.md @@ -0,0 +1,202 @@ +# Loop Module Standard + +Chinese version: [LOOP_MODULE_STANDARD.md](../zh/harness/LOOP_MODULE_STANDARD.md) + +This document defines the standard structure for Mnemon harness loop modules. +The standard is host-agnostic. Concrete hosts such as Claude Code, Codex, +OpenClaw, or future runtimes consume the same loop module through host-specific +projection adapters. + +## Core Model + +Mnemon separates canonical harness state from host runtime projection: + +```text +.mnemon canonical state + | + | projected by a host adapter + v +.claude / .codex / other host config + | + v +host runtime +``` + +The loop module owns policy, lifecycle reminders, protocol skills, maintenance +agents, environment contracts, and setup adapters. The host runtime owns the +conversation loop, prompt assembly, tool routing, native skill discovery, +permission model, and UI. + +## Standard Directory + +Every installable loop module should follow this shape: + +```text +harness// +├── README.md +├── module.json +├── env.sh +├── GUIDE.md +├── hooks/ +│ ├── prime.md +│ ├── remind.md +│ ├── nudge.md +│ └── compact.md +├── skills/ +│ └── .md +├── subagents/ +│ └── .md +└── setup/ + ├── claude-code/ + │ ├── install.sh + │ └── uninstall.sh + └── / + ├── install.sh + └── uninstall.sh +``` + +Loop-specific runtime files may be added when they are part of the loop +contract, such as `MEMORY.md` for the Memory Loop. + +## Concepts + +| Concept | Required | Role | +| --- | --- | --- | +| `module.json` | Yes | Machine-readable loop identity, declared assets, state directories, lifecycle events, and supported host adapters. | +| `GUIDE.md` | Yes | Policy for when the loop should act, what the host agent should consider, and what remains out of scope. | +| `env.sh` | Yes | Runtime path contract for scripts, hooks, protocol skills, and maintenance agents. | +| `hooks/*.md` | Yes | Host-agnostic lifecycle reminders. They describe what the agent should consider at a lifecycle boundary. | +| `skills/*.md` | Usually | Protocol skills for reusable online operations. These define procedures, not host-specific installation. | +| `subagents/*.md` | Optional | Maintenance roles for heavier review, consolidation, or proposal generation. Hosts without native subagents may run them as manual or scheduled jobs. | +| `setup//` | At least one | Host-specific projection adapter that installs or removes the module from a host runtime. | + +## Lifecycle Events + +Mnemon standardizes lifecycle vocabulary so different hosts can map their native +extension points to the same loop semantics. + +| Event | Meaning | Typical Use | +| --- | --- | --- | +| `prime` | Session or runtime start. | Make loop policy, important state, and active surfaces visible. | +| `remind` | User request or task boundary. | Decide whether recall, observation, or other loop action could change the task. | +| `nudge` | Turn end or work completion. | Decide whether durable writeback, evidence capture, or report generation is justified. | +| `compact` | Context compaction or checkpoint boundary. | Preserve critical continuity and trigger maintenance when state is oversized or stale. | +| `maintenance` | Offline or explicit maintenance job. | Run heavier consolidation, curator review, evaluation, audit, or proposal work. | + +Adapters may degrade gracefully. If a host lacks an exact hook, it can map the +event to the closest lifecycle boundary or expose it through an app-server eval +API. + +## Host Projection + +A host projection adapter renders the canonical loop module into a host-native +surface. Projection must not create a second source of truth. + +```text +canonical loop module + | + | install / project + v +host-native files +``` + +Typical responsibilities: + +- Resolve canonical `.mnemon` and project-local paths. +- Copy or reference loop assets. +- Render host-readable skills, hooks, and configuration. +- Register native lifecycle hooks when the host supports them. +- Write a host manifest under `.mnemon/hosts//`. +- Preserve canonical state during uninstall unless explicitly requested. + +## Canonical State + +The canonical state belongs under `.mnemon`, not under a host-specific directory. +Host directories such as `.claude` or `.codex` contain projections only. + +Recommended layout: + +```text +.mnemon/ +├── data/ +│ └── /mnemon.db +├── harness/ +│ ├── memory-loop/ +│ └── skill-loop/ +├── reports/ +├── proposals/ +├── audit/ +├── hosts/ +│ ├── claude-code/ +│ │ └── manifest.json +│ └── codex/ +│ └── manifest.json +└── manifest.json +``` + +Current MVP setup scripts may still place runtime files inside host config +directories. New adapters should move toward the canonical `.mnemon` layout and +use host directories only as projection surfaces. + +## Manifest Schema + +Each loop module should include a `module.json` file with this stable shape: + +```json +{ + "schema_version": 1, + "name": "memory-loop", + "version": "0.1.0", + "description": "Connects prompt-facing working memory with Mnemon long-term memory.", + "lifecycle_events": ["prime", "remind", "nudge", "compact"], + "assets": { + "guide": "GUIDE.md", + "env": "env.sh", + "hooks": { + "prime": "hooks/prime.md", + "remind": "hooks/remind.md", + "nudge": "hooks/nudge.md", + "compact": "hooks/compact.md" + }, + "skills": ["skills/memory_get.md", "skills/memory_set.md"], + "subagents": ["subagents/dreaming.md"] + }, + "state": { + "canonical": [".mnemon/data", ".mnemon/reports", ".mnemon/proposals", ".mnemon/audit"], + "loop_runtime": [] + }, + "host_adapters": { + "claude-code": "setup/claude-code" + } +} +``` + +The manifest is descriptive in the MVP. Later setup tooling can validate loop +modules, generate projections, and drive app-server evals from this metadata. + +## Adapter Mapping + +The same standard concepts map differently across hosts: + +| Loop Standard | Claude Code Projection | Codex Projection | +| --- | --- | --- | +| `GUIDE.md` | Prompt guide or skill guidance visible to Claude Code. | Codex instruction or skill guidance visible to Codex. | +| `hooks/prime.md` | Session-start hook. | Session init hook or app-server lifecycle endpoint. | +| `hooks/remind.md` | User-prompt hook. | Request or message boundary hook. | +| `hooks/nudge.md` | Stop or turn-end hook. | Turn-end hook or app-server lifecycle endpoint. | +| `hooks/compact.md` | Pre-compact hook. | Compact, checkpoint, or explicit eval lifecycle endpoint. | +| `skills/*.md` | `.claude/skills` projection. | `.codex/skills` or Codex skill surface projection. | +| `subagents/*.md` | Native subagent projection when available. | Codex subagent, task adapter, or maintenance job. | +| `env.sh` | Sourced by hook scripts and injected into context. | Sourced by Codex adapter and app-server eval runtime. | + +## Quality Rules + +- Keep loop modules host-agnostic by default. +- Keep host-specific code under `setup//`. +- Do not duplicate canonical state into host directories. +- Treat host directories as projections that can be regenerated. +- Keep setup, status, and uninstall behavior explicit and auditable. +- Preserve user state on uninstall unless a destructive flag is explicit. +- Document English and Chinese behavior together when adding or changing public + harness concepts. + diff --git a/docs/harness/README.md b/docs/harness/README.md index dc28e72..344e532 100644 --- a/docs/harness/README.md +++ b/docs/harness/README.md @@ -25,6 +25,7 @@ projection into host surfaces, and optional daemon scheduling. | Topic | Design | | --- | --- | | Modular Agent Harness | [EN](modular-agent/DESIGN.md) / [中文](../zh/harness/modular-agent/DESIGN.md) | +| Loop Module Standard | [EN](LOOP_MODULE_STANDARD.md) / [中文](../zh/harness/LOOP_MODULE_STANDARD.md) | | Harness Roadmap | [EN](ROADMAP.md) / [中文](../zh/harness/ROADMAP.md) | | Memory Loop | [EN](memory-loop/DESIGN.md) / [中文](../zh/harness/memory-loop/DESIGN.md) / [site](../site/memory-loop/site.html) | | Skill Loop | [EN](skill-loop/DESIGN.md) / [中文](../zh/harness/skill-loop/DESIGN.md) / [site](../site/skill-loop/site.html) | @@ -40,11 +41,13 @@ projection into host surfaces, and optional daemon scheduling. | Concept | Meaning | | --- | --- | +| loop module | Standard package shape for one attachable harness loop. | | GUIDE | Markdown policy for deciding when a loop should act. | | setup | Installation and mounting into a host agent. | | hook | Host lifecycle timing such as Prime, Remind, Nudge, and Compact. | | protocol | Markdown skills that define reusable operations. | | subagent | Background maintenance agent for heavier review or consolidation. | +| projection | Host-specific rendering of canonical loop assets into `.claude`, `.codex`, or another runtime surface. | | daemon | Optional harness maintenance runner for scheduled module work. | | substrate | Mnemon-owned runtime base for module state, setup, projection, scheduling, and cross-module protocols. | diff --git a/docs/zh/harness/LOOP_MODULE_STANDARD.md b/docs/zh/harness/LOOP_MODULE_STANDARD.md new file mode 100644 index 0000000..75f6603 --- /dev/null +++ b/docs/zh/harness/LOOP_MODULE_STANDARD.md @@ -0,0 +1,197 @@ +# Loop Module Standard + +英文版本:[LOOP_MODULE_STANDARD.md](../../harness/LOOP_MODULE_STANDARD.md) + +本文定义 Mnemon harness loop module 的标准结构。这个标准与宿主无关。 +Claude Code、Codex、OpenClaw 或未来 runtime 都应该通过各自的 host projection +adapter 使用同一套 loop module。 + +## 核心模型 + +Mnemon 把 canonical harness state 和 host runtime projection 分开: + +```text +.mnemon canonical state + | + | 由 host adapter 投影 + v +.claude / .codex / 其他宿主配置 + | + v +host runtime +``` + +Loop module 拥有 policy、lifecycle reminders、protocol skills、maintenance +agents、environment contract 和 setup adapters。宿主 runtime 拥有 conversation +loop、prompt assembly、tool routing、native skill discovery、权限模型和 UI。 + +## 标准目录 + +每个可安装 loop module 应该遵循这个结构: + +```text +harness// +├── README.md +├── module.json +├── env.sh +├── GUIDE.md +├── hooks/ +│ ├── prime.md +│ ├── remind.md +│ ├── nudge.md +│ └── compact.md +├── skills/ +│ └── .md +├── subagents/ +│ └── .md +└── setup/ + ├── claude-code/ + │ ├── install.sh + │ └── uninstall.sh + └── / + ├── install.sh + └── uninstall.sh +``` + +如果某个 loop 的契约需要额外 runtime 文件,可以加入该目录,例如 Memory Loop +的 `MEMORY.md`。 + +## 概念 + +| 概念 | 是否必需 | 作用 | +| --- | --- | --- | +| `module.json` | 是 | 机器可读的 loop identity、资产声明、state 目录、lifecycle events 和已支持 host adapters。 | +| `GUIDE.md` | 是 | 定义 loop 何时应该行动、宿主 agent 应该如何判断,以及哪些内容不属于该 loop。 | +| `env.sh` | 是 | scripts、hooks、protocol skills 和 maintenance agents 使用的运行时路径契约。 | +| `hooks/*.md` | 是 | 与宿主无关的 lifecycle reminders。描述 agent 在生命周期边界应考虑什么。 | +| `skills/*.md` | 通常是 | 用于在线可复用操作的 protocol skills。它们定义流程,不定义宿主安装方式。 | +| `subagents/*.md` | 可选 | 用于较重 review、consolidation 或 proposal generation 的维护角色。没有 native subagent 的宿主可以降级为人工或定时 job。 | +| `setup//` | 至少一个 | Host-specific projection adapter,把 module 安装或移除到某个宿主 runtime。 | + +## 生命周期事件 + +Mnemon 标准化 lifecycle 词汇,让不同宿主可以把自己的 native extension points +映射到同一套 loop semantics。 + +| 事件 | 含义 | 常见用途 | +| --- | --- | --- | +| `prime` | Session 或 runtime 启动。 | 让 loop policy、重要 state 和 active surfaces 可见。 | +| `remind` | 用户请求或任务边界。 | 判断 recall、observation 或其他 loop action 是否会改变当前任务。 | +| `nudge` | 回合结束或工作完成。 | 判断 durable writeback、evidence capture 或 report generation 是否有必要。 | +| `compact` | Context compaction 或 checkpoint 边界。 | 保存关键连续性,并在 state 过大或过旧时触发维护。 | +| `maintenance` | 离线或显式维护任务。 | 运行较重的 consolidation、curator review、evaluation、audit 或 proposal 工作。 | + +Adapter 可以优雅降级。如果宿主没有完全对应的 hook,可以映射到最接近的 +lifecycle boundary,或通过 app-server eval API 显式触发。 + +## Host Projection + +Host projection adapter 把 canonical loop module 渲染到宿主原生 surface。投影不能制造第二份真实状态。 + +```text +canonical loop module + | + | install / project + v +host-native files +``` + +典型职责: + +- 解析 canonical `.mnemon` 和 project-local paths。 +- 复制或引用 loop assets。 +- 渲染宿主可读的 skills、hooks 和配置。 +- 当宿主支持时注册 native lifecycle hooks。 +- 在 `.mnemon/hosts//` 下写入 host manifest。 +- 卸载时保留 canonical state,除非用户显式要求破坏性删除。 + +## Canonical State + +Canonical state 属于 `.mnemon`,不属于某个宿主目录。`.claude` 或 `.codex` +这类宿主目录只保存 projections。 + +推荐布局: + +```text +.mnemon/ +├── data/ +│ └── /mnemon.db +├── harness/ +│ ├── memory-loop/ +│ └── skill-loop/ +├── reports/ +├── proposals/ +├── audit/ +├── hosts/ +│ ├── claude-code/ +│ │ └── manifest.json +│ └── codex/ +│ └── manifest.json +└── manifest.json +``` + +当前 MVP setup scripts 仍可能把 runtime files 放在 host config 目录下。新的 +adapters 应逐步转向 canonical `.mnemon` 布局,并把 host directories 只作为 +projection surfaces。 + +## Manifest Schema + +每个 loop module 应该包含一个 `module.json` 文件,使用这个稳定结构: + +```json +{ + "schema_version": 1, + "name": "memory-loop", + "version": "0.1.0", + "description": "Connects prompt-facing working memory with Mnemon long-term memory.", + "lifecycle_events": ["prime", "remind", "nudge", "compact"], + "assets": { + "guide": "GUIDE.md", + "env": "env.sh", + "hooks": { + "prime": "hooks/prime.md", + "remind": "hooks/remind.md", + "nudge": "hooks/nudge.md", + "compact": "hooks/compact.md" + }, + "skills": ["skills/memory_get.md", "skills/memory_set.md"], + "subagents": ["subagents/dreaming.md"] + }, + "state": { + "canonical": [".mnemon/data", ".mnemon/reports", ".mnemon/proposals", ".mnemon/audit"], + "loop_runtime": [] + }, + "host_adapters": { + "claude-code": "setup/claude-code" + } +} +``` + +在 MVP 阶段,manifest 是描述性的。后续 setup tooling 可以基于这些 metadata +验证 loop modules、生成 projections,并驱动 app-server eval。 + +## Adapter Mapping + +同一个标准概念在不同宿主中有不同投影方式: + +| Loop Standard | Claude Code Projection | Codex Projection | +| --- | --- | --- | +| `GUIDE.md` | Claude Code 可见的 prompt guide 或 skill guidance。 | Codex 可见的 instruction 或 skill guidance。 | +| `hooks/prime.md` | Session-start hook。 | Session init hook 或 app-server lifecycle endpoint。 | +| `hooks/remind.md` | User-prompt hook。 | Request 或 message boundary hook。 | +| `hooks/nudge.md` | Stop 或 turn-end hook。 | Turn-end hook 或 app-server lifecycle endpoint。 | +| `hooks/compact.md` | Pre-compact hook。 | Compact、checkpoint 或显式 eval lifecycle endpoint。 | +| `skills/*.md` | `.claude/skills` projection。 | `.codex/skills` 或 Codex skill surface projection。 | +| `subagents/*.md` | 可用时投影为 native subagent。 | Codex subagent、task adapter 或 maintenance job。 | +| `env.sh` | 被 hook scripts source,并注入上下文。 | 被 Codex adapter 和 app-server eval runtime source。 | + +## 质量规则 + +- Loop modules 默认保持 host-agnostic。 +- Host-specific code 只放在 `setup//`。 +- 不要把 canonical state 复制成宿主目录下的第二份真实状态。 +- 把 host directories 视为可重新生成的 projection。 +- setup、status 和 uninstall 行为必须明确、可审计。 +- 卸载时保留用户状态,除非用户显式传入破坏性选项。 +- 新增或修改公开 harness 概念时,同步维护英文和中文文档。 + diff --git a/docs/zh/harness/README.md b/docs/zh/harness/README.md index 7b044d1..807e114 100644 --- a/docs/zh/harness/README.md +++ b/docs/zh/harness/README.md @@ -20,6 +20,7 @@ host surface projection,以及可选的 daemon scheduling。 | 主题 | 设计 | | --- | --- | | Modular Agent Harness | [中文](modular-agent/DESIGN.md) / [EN](../../harness/modular-agent/DESIGN.md) | +| Loop Module Standard | [中文](LOOP_MODULE_STANDARD.md) / [EN](../../harness/LOOP_MODULE_STANDARD.md) | | Harness Roadmap | [中文](ROADMAP.md) / [EN](../../harness/ROADMAP.md) | | Memory Loop | [中文](memory-loop/DESIGN.md) / [EN](../../harness/memory-loop/DESIGN.md) / [site](../../site/memory-loop/site.html) | | Skill Loop | [中文](skill-loop/DESIGN.md) / [EN](../../harness/skill-loop/DESIGN.md) / [site](../../site/skill-loop/site.html) | @@ -35,11 +36,13 @@ host surface projection,以及可选的 daemon scheduling。 | 概念 | 含义 | | --- | --- | +| loop module | 一个可挂载 harness loop 的标准包结构。 | | GUIDE | Markdown policy,用来判断某个 loop 何时应该行动。 | | setup | 安装并挂载到宿主 agent。 | | hook | Prime、Remind、Nudge、Compact 等宿主生命周期时机。 | | protocol | 定义可复用操作的 Markdown skill。 | | subagent | 用于较重 review 或 consolidation 的后台维护 agent。 | +| projection | 把 canonical loop assets 渲染到 `.claude`、`.codex` 或其他 runtime surface 的宿主特定过程。 | | daemon | 可选的 harness maintenance runner,用于调度模块后台工作。 | | substrate | Mnemon 拥有的运行时基座,用于 module state、setup、projection、scheduling 和跨模块协议。 | diff --git a/harness/memory-loop/README.md b/harness/memory-loop/README.md index d0bb57b..cc864fc 100644 --- a/harness/memory-loop/README.md +++ b/harness/memory-loop/README.md @@ -9,6 +9,7 @@ the loop into its own runtime without a custom adapter. ```text harness/memory-loop/ ├── README.md +├── module.json ├── env.sh ├── GUIDE.md ├── MEMORY.md @@ -47,6 +48,7 @@ harness/memory-loop/ | Asset | Purpose | | --- | --- | +| `module.json` | Machine-readable loop manifest for standard lifecycle events, assets, state, and host adapters. | | `env.sh` | Runtime config: memory directory, env path, and dreaming threshold. | | `GUIDE.md` | Policy: when to read memory, when to write memory, and what is worth keeping. | | `hooks/*.md` | Four lifecycle reminders: Prime, Remind, Nudge, and Compact. | diff --git a/harness/memory-loop/module.json b/harness/memory-loop/module.json new file mode 100644 index 0000000..a5bfcfc --- /dev/null +++ b/harness/memory-loop/module.json @@ -0,0 +1,46 @@ +{ + "schema_version": 1, + "name": "memory-loop", + "version": "0.1.0", + "description": "Connects prompt-facing working memory, Mnemon long-term memory, and dreaming consolidation.", + "lifecycle_events": [ + "prime", + "remind", + "nudge", + "compact" + ], + "assets": { + "guide": "GUIDE.md", + "env": "env.sh", + "runtime_files": [ + "MEMORY.md" + ], + "hooks": { + "prime": "hooks/prime.md", + "remind": "hooks/remind.md", + "nudge": "hooks/nudge.md", + "compact": "hooks/compact.md" + }, + "skills": [ + "skills/memory_get.md", + "skills/memory_set.md" + ], + "subagents": [ + "subagents/dreaming.md" + ] + }, + "state": { + "canonical": [ + ".mnemon/data", + ".mnemon/reports", + ".mnemon/proposals", + ".mnemon/audit" + ], + "loop_runtime": [ + "MEMORY.md" + ] + }, + "host_adapters": { + "claude-code": "setup/claude-code" + } +} diff --git a/harness/skill-loop/README.md b/harness/skill-loop/README.md index 3f3412f..c37392e 100644 --- a/harness/skill-loop/README.md +++ b/harness/skill-loop/README.md @@ -9,6 +9,7 @@ the canonical skill lifecycle state and the evidence used to evolve it. ```text harness/skill-loop/ ├── README.md +├── module.json ├── env.sh ├── GUIDE.md ├── hooks/ @@ -47,6 +48,7 @@ harness/skill-loop/ | Asset | Purpose | | --- | --- | +| `module.json` | Machine-readable loop manifest for standard lifecycle events, assets, state, and host adapters. | | `env.sh` | Runtime config: canonical skill library, host skill surface, usage log, and proposal paths. | | `GUIDE.md` | Policy for evidence, review triggers, lifecycle movement, and proposal-first changes. | | `hooks/*.md` | Four lifecycle reminders. Prime syncs active skills; Nudge records evidence; Compact may trigger review; Remind is no-op by default. | diff --git a/harness/skill-loop/module.json b/harness/skill-loop/module.json new file mode 100644 index 0000000..8d97cdc --- /dev/null +++ b/harness/skill-loop/module.json @@ -0,0 +1,48 @@ +{ + "schema_version": 1, + "name": "skill-loop", + "version": "0.1.0", + "description": "Manages active, stale, and archived skills through evidence, curator review, and approved lifecycle changes.", + "lifecycle_events": [ + "prime", + "remind", + "nudge", + "compact" + ], + "assets": { + "guide": "GUIDE.md", + "env": "env.sh", + "hooks": { + "prime": "hooks/prime.md", + "remind": "hooks/remind.md", + "nudge": "hooks/nudge.md", + "compact": "hooks/compact.md" + }, + "skills": [ + "skills/skill_observe.md", + "skills/skill_curate.md", + "skills/skill_manage.md" + ], + "subagents": [ + "subagents/curator.md" + ] + }, + "state": { + "canonical": [ + ".mnemon/data", + ".mnemon/reports", + ".mnemon/proposals", + ".mnemon/audit" + ], + "loop_runtime": [ + "skills/active", + "skills/stale", + "skills/archived", + "skills/.usage.jsonl", + "proposals" + ] + }, + "host_adapters": { + "claude-code": "setup/claude-code" + } +} From 83ed09448ac62dec990d64415d42a07575ced7fc Mon Sep 17 00:00:00 2001 From: Grivn Date: Thu, 14 May 2026 17:09:20 +0000 Subject: [PATCH 2/4] Document harness host projection contract --- docs/harness/HOST_PROJECTION.md | 268 +++++++++++++++++++++++++++++ docs/harness/README.md | 2 + docs/zh/harness/HOST_PROJECTION.md | 255 +++++++++++++++++++++++++++ docs/zh/harness/README.md | 2 + 4 files changed, 527 insertions(+) create mode 100644 docs/harness/HOST_PROJECTION.md create mode 100644 docs/zh/harness/HOST_PROJECTION.md diff --git a/docs/harness/HOST_PROJECTION.md b/docs/harness/HOST_PROJECTION.md new file mode 100644 index 0000000..4805c57 --- /dev/null +++ b/docs/harness/HOST_PROJECTION.md @@ -0,0 +1,268 @@ +# Host Projection + +Chinese version: [HOST_PROJECTION.md](../zh/harness/HOST_PROJECTION.md) + +This document defines how a Mnemon loop module is projected into a concrete +host runtime such as Claude Code, Codex, OpenClaw, or a future app-server eval +host. + +The loop module standard defines the canonical package shape. Host projection +defines how that package becomes visible and executable inside a host runtime. + +## Principle + +Mnemon keeps canonical harness state in `.mnemon`. Host directories contain +projections that can be regenerated. + +```text +.mnemon/ + canonical state, loop modules, reports, proposals, audit + | + | projected by setup/ + v +.claude/ or .codex/ + host-readable skills, hooks, config, and pointers back to .mnemon + | + v +host runtime +``` + +The projection adapter should not create an independent copy of truth. It should +render enough host-native files for the host to discover and use the loop while +keeping durable state under `.mnemon`. + +## Responsibilities + +A host projection adapter owns these responsibilities: + +| Responsibility | Description | +| --- | --- | +| Path resolution | Resolve project root, host config directory, canonical `.mnemon`, active store, and loop module path. | +| Asset projection | Render or copy host-readable GUIDE, hooks, protocol skills, and subagents. | +| Hook registration | Register host lifecycle hooks when the host supports them. | +| Environment injection | Make `MNEMON_DATA_DIR`, `MNEMON_STORE`, `MNEMON_HARNESS_DIR`, and loop-specific env visible to hooks and skills. | +| Manifest writing | Record what was projected and where under `.mnemon/hosts//manifest.json`. | +| Validation | Detect missing assets, stale projections, incompatible host capabilities, and path conflicts. | +| Uninstall | Remove host projection files while preserving canonical `.mnemon` state by default. | + +## Non-Responsibilities + +A host projection adapter should not: + +- Reimplement Mnemon memory storage or retrieval. +- Move canonical state into `.claude`, `.codex`, or another host directory. +- Hide host-specific behavior inside loop module root files. +- Mutate user-owned host config outside declared projection sections. +- Delete memory, reports, proposals, or audit records unless the user explicitly + requests destructive cleanup. + +## Canonical Layout + +The target canonical layout is: + +```text +.mnemon/ +├── data/ +│ └── /mnemon.db +├── harness/ +│ ├── memory-loop/ +│ └── skill-loop/ +├── reports/ +├── proposals/ +├── audit/ +├── hosts/ +│ ├── claude-code/ +│ │ └── manifest.json +│ └── codex/ +│ └── manifest.json +└── manifest.json +``` + +Current MVP scripts may still place loop runtime files in host config +directories. New projection adapters should move toward this canonical layout +and keep host directories as generated views. + +## Projection Layouts + +### Claude Code + +Claude Code projection uses the host's native skill, hook, subagent, and +settings surfaces. + +```text +.claude/ +├── skills/ +│ └── +├── hooks/ +│ └── +├── agents/ +│ └── +└── settings.json +``` + +Claude Code projection should: + +- Register lifecycle hooks in `settings.json`. +- Keep generated hook entrypoints small. +- Source Mnemon env files from the canonical `.mnemon` location when possible. +- Keep policy in `GUIDE.md` and hook prompts, not in shell glue. + +### Codex + +Codex projection should follow the same canonical model while rendering into +Codex-native surfaces. + +```text +.codex/ +├── skills/ +│ └── +├── hooks/ +│ └── +├── agents/ +│ └── +└── config/ + └── +``` + +Codex projection should: + +- Project protocol skills into the Codex skill surface. +- Map lifecycle events to Codex hooks when available. +- Use app-server lifecycle endpoints as a fallback when direct hooks are not + available. +- Pass canonical `.mnemon` paths into the app server and skills through env or + runtime config. +- Write eval artifacts under `.mnemon/reports`, `.mnemon/proposals`, and + `.mnemon/audit`. + +Exact Codex paths may evolve with Codex host capabilities. The adapter should +record its chosen paths in `.mnemon/hosts/codex/manifest.json`. + +## Lifecycle Mapping + +Host adapters map Mnemon lifecycle events to native host events: + +| Mnemon Event | Claude Code Projection | Codex Projection | Fallback | +| --- | --- | --- | --- | +| `prime` | Session start hook. | Session init hook or app-server session start. | Explicit `/lifecycle/prime` eval call. | +| `remind` | User prompt hook. | Request or message boundary hook. | Explicit `/lifecycle/remind` eval call. | +| `nudge` | Stop or turn-end hook. | Turn-end hook or response finalization. | Explicit `/lifecycle/nudge` eval call. | +| `compact` | Pre-compact hook. | Compact, checkpoint, or context-save event. | Explicit `/lifecycle/compact` eval call. | +| `maintenance` | Subagent or manual task. | Subagent, background task, or app-server job. | Explicit maintenance command. | + +The mapping is semantic, not necessarily one-to-one. If a host cannot supply an +exact lifecycle event, the adapter should choose the closest safe boundary and +document it in the host manifest. + +## Host Manifest + +Every projection should write a host manifest: + +```text +.mnemon/hosts//manifest.json +``` + +Recommended shape: + +```json +{ + "schema_version": 1, + "host": "codex", + "installed_at": "2026-05-14T00:00:00Z", + "project_root": "/path/to/project", + "mnemon_dir": "/path/to/project/.mnemon", + "store": "default", + "loops": { + "memory-loop": { + "module_path": ".mnemon/harness/memory-loop", + "module_version": "0.1.0", + "projection_path": ".codex", + "projected_assets": { + "skills": [".codex/skills/memory_get.md"], + "hooks": [".codex/hooks/prime.sh"], + "subagents": [] + }, + "lifecycle_mapping": { + "prime": "session-init", + "remind": "message-boundary", + "nudge": "turn-end", + "compact": "explicit-eval" + } + } + } +} +``` + +The manifest is the bridge between setup, status, uninstall, and eval tooling. + +## Setup Contract + +All host adapters should support the same high-level operations: + +```text +install + validate loop module manifests + resolve canonical .mnemon + install canonical loop assets if needed + render host projection + register hooks/config + write host manifest + +status + read host manifest + validate projected files exist + validate registered hooks/config + report stale or missing projections + +uninstall + remove projected host files + unregister hooks/config + preserve canonical .mnemon state by default + update or remove host manifest +``` + +The `status` operation is important for app-server evals because it lets the +orchestrator verify that a run is testing the intended projection. + +## App-Server Eval Host + +An app-server eval host is a disposable host runtime used for testing loop +behavior. It should use the same projection contract as real hosts: + +```text +eval orchestrator + | + | create isolated workspace and .mnemon + | run setup//install + | start host app server + v +host app server + | + | API-driven scenarios + v +harness loop projection + | + v +Mnemon engine and canonical state +``` + +Eval should test host behavior under harness influence, not only Mnemon CLI +CRUD. Useful assertions include: + +- The app server uses the isolated `.mnemon`. +- The expected loop module versions are installed. +- Lifecycle events are invoked through the declared mapping. +- Recall decisions affect later task behavior. +- Writeback decisions create durable memory only when justified. +- Reports, proposals, and audit records are written to canonical locations. + +## Quality Rules + +- Projection files should be small and generated from canonical assets. +- Host-specific behavior belongs in `setup//` or generated adapter files. +- Setup should be repeatable and idempotent where practical. +- Uninstall should be conservative and preserve canonical state. +- Manifest paths should be relative when possible and absolute when required for + runtime execution. +- Public projection behavior must be documented in both English and Chinese. + diff --git a/docs/harness/README.md b/docs/harness/README.md index 344e532..668448f 100644 --- a/docs/harness/README.md +++ b/docs/harness/README.md @@ -26,6 +26,7 @@ projection into host surfaces, and optional daemon scheduling. | --- | --- | | Modular Agent Harness | [EN](modular-agent/DESIGN.md) / [中文](../zh/harness/modular-agent/DESIGN.md) | | Loop Module Standard | [EN](LOOP_MODULE_STANDARD.md) / [中文](../zh/harness/LOOP_MODULE_STANDARD.md) | +| Host Projection | [EN](HOST_PROJECTION.md) / [中文](../zh/harness/HOST_PROJECTION.md) | | Harness Roadmap | [EN](ROADMAP.md) / [中文](../zh/harness/ROADMAP.md) | | Memory Loop | [EN](memory-loop/DESIGN.md) / [中文](../zh/harness/memory-loop/DESIGN.md) / [site](../site/memory-loop/site.html) | | Skill Loop | [EN](skill-loop/DESIGN.md) / [中文](../zh/harness/skill-loop/DESIGN.md) / [site](../site/skill-loop/site.html) | @@ -48,6 +49,7 @@ projection into host surfaces, and optional daemon scheduling. | protocol | Markdown skills that define reusable operations. | | subagent | Background maintenance agent for heavier review or consolidation. | | projection | Host-specific rendering of canonical loop assets into `.claude`, `.codex`, or another runtime surface. | +| host manifest | Machine-readable record of projected loops, paths, lifecycle mappings, and host capabilities. | | daemon | Optional harness maintenance runner for scheduled module work. | | substrate | Mnemon-owned runtime base for module state, setup, projection, scheduling, and cross-module protocols. | diff --git a/docs/zh/harness/HOST_PROJECTION.md b/docs/zh/harness/HOST_PROJECTION.md new file mode 100644 index 0000000..0926d63 --- /dev/null +++ b/docs/zh/harness/HOST_PROJECTION.md @@ -0,0 +1,255 @@ +# Host Projection + +英文版本:[HOST_PROJECTION.md](../../harness/HOST_PROJECTION.md) + +本文定义 Mnemon loop module 如何投影到具体宿主 runtime,例如 Claude Code、 +Codex、OpenClaw,或未来的 app-server eval host。 + +Loop Module Standard 定义 canonical package shape。Host Projection 定义这套 +package 如何在某个宿主 runtime 中变得可见、可执行。 + +## 原则 + +Mnemon 把 canonical harness state 保存在 `.mnemon`。宿主目录只保存可重新生成的 projections。 + +```text +.mnemon/ + canonical state, loop modules, reports, proposals, audit + | + | 由 setup/ 投影 + v +.claude/ or .codex/ + 宿主可读的 skills、hooks、config,以及指回 .mnemon 的路径 + | + v +host runtime +``` + +Projection adapter 不应该制造另一份真实状态。它只渲染足够的宿主原生文件, +让宿主能够发现和使用 loop,同时把持久状态保留在 `.mnemon` 下。 + +## 职责 + +Host projection adapter 负责: + +| 职责 | 说明 | +| --- | --- | +| Path resolution | 解析 project root、host config directory、canonical `.mnemon`、active store 和 loop module path。 | +| Asset projection | 渲染或复制宿主可读的 GUIDE、hooks、protocol skills 和 subagents。 | +| Hook registration | 当宿主支持时,注册宿主生命周期 hooks。 | +| Environment injection | 让 `MNEMON_DATA_DIR`、`MNEMON_STORE`、`MNEMON_HARNESS_DIR` 和 loop-specific env 对 hooks 和 skills 可见。 | +| Manifest writing | 在 `.mnemon/hosts//manifest.json` 记录投影了什么、投影到哪里。 | +| Validation | 检测缺失资产、过期 projection、不兼容宿主能力和路径冲突。 | +| Uninstall | 删除宿主 projection 文件,默认保留 canonical `.mnemon` 状态。 | + +## 非职责 + +Host projection adapter 不应该: + +- 重新实现 Mnemon memory storage 或 retrieval。 +- 把 canonical state 移动到 `.claude`、`.codex` 或其他宿主目录。 +- 把宿主特定行为隐藏在 loop module 根目录文件里。 +- 修改声明区域之外的用户宿主配置。 +- 删除 memory、reports、proposals 或 audit records,除非用户显式要求破坏性清理。 + +## Canonical Layout + +目标 canonical layout: + +```text +.mnemon/ +├── data/ +│ └── /mnemon.db +├── harness/ +│ ├── memory-loop/ +│ └── skill-loop/ +├── reports/ +├── proposals/ +├── audit/ +├── hosts/ +│ ├── claude-code/ +│ │ └── manifest.json +│ └── codex/ +│ └── manifest.json +└── manifest.json +``` + +当前 MVP scripts 仍可能把 loop runtime files 放在 host config 目录下。新的 +projection adapters 应逐步转向 canonical `.mnemon` 布局,并把 host directories +作为 generated views。 + +## Projection Layouts + +### Claude Code + +Claude Code projection 使用宿主原生的 skill、hook、subagent 和 settings surface。 + +```text +.claude/ +├── skills/ +│ └── +├── hooks/ +│ └── +├── agents/ +│ └── +└── settings.json +``` + +Claude Code projection 应该: + +- 在 `settings.json` 中注册 lifecycle hooks。 +- 让生成的 hook entrypoints 保持很小。 +- 尽可能从 canonical `.mnemon` 位置 source Mnemon env files。 +- 把 policy 保留在 `GUIDE.md` 和 hook prompts 中,而不是 shell glue 中。 + +### Codex + +Codex projection 应遵循同一个 canonical model,同时渲染到 Codex-native surfaces。 + +```text +.codex/ +├── skills/ +│ └── +├── hooks/ +│ └── +├── agents/ +│ └── +└── config/ + └── +``` + +Codex projection 应该: + +- 把 protocol skills 投影到 Codex skill surface。 +- 当 Codex 支持对应 hook 时,把 lifecycle events 映射过去。 +- 当 direct hooks 不可用时,使用 app-server lifecycle endpoints 作为降级路径。 +- 通过 env 或 runtime config 把 canonical `.mnemon` paths 传给 app server 和 skills。 +- 把 eval artifacts 写入 `.mnemon/reports`、`.mnemon/proposals` 和 `.mnemon/audit`。 + +Codex 的精确路径可能会随 Codex host capabilities 演化。Adapter 应该把实际选择的路径记录在 `.mnemon/hosts/codex/manifest.json`。 + +## Lifecycle Mapping + +Host adapters 把 Mnemon lifecycle events 映射到宿主 native events: + +| Mnemon Event | Claude Code Projection | Codex Projection | Fallback | +| --- | --- | --- | --- | +| `prime` | Session start hook。 | Session init hook 或 app-server session start。 | 显式 `/lifecycle/prime` eval call。 | +| `remind` | User prompt hook。 | Request 或 message boundary hook。 | 显式 `/lifecycle/remind` eval call。 | +| `nudge` | Stop 或 turn-end hook。 | Turn-end hook 或 response finalization。 | 显式 `/lifecycle/nudge` eval call。 | +| `compact` | Pre-compact hook。 | Compact、checkpoint 或 context-save event。 | 显式 `/lifecycle/compact` eval call。 | +| `maintenance` | Subagent 或 manual task。 | Subagent、background task 或 app-server job。 | 显式 maintenance command。 | + +这个 mapping 是语义映射,不要求一对一。如果宿主不能提供完全对应的 lifecycle +event,adapter 应选择最接近且安全的边界,并在 host manifest 中记录。 + +## Host Manifest + +每次 projection 都应该写入 host manifest: + +```text +.mnemon/hosts//manifest.json +``` + +推荐结构: + +```json +{ + "schema_version": 1, + "host": "codex", + "installed_at": "2026-05-14T00:00:00Z", + "project_root": "/path/to/project", + "mnemon_dir": "/path/to/project/.mnemon", + "store": "default", + "loops": { + "memory-loop": { + "module_path": ".mnemon/harness/memory-loop", + "module_version": "0.1.0", + "projection_path": ".codex", + "projected_assets": { + "skills": [".codex/skills/memory_get.md"], + "hooks": [".codex/hooks/prime.sh"], + "subagents": [] + }, + "lifecycle_mapping": { + "prime": "session-init", + "remind": "message-boundary", + "nudge": "turn-end", + "compact": "explicit-eval" + } + } + } +} +``` + +Manifest 是 setup、status、uninstall 和 eval tooling 之间的桥。 + +## Setup Contract + +所有 host adapters 应支持同一组高层操作: + +```text +install + validate loop module manifests + resolve canonical .mnemon + install canonical loop assets if needed + render host projection + register hooks/config + write host manifest + +status + read host manifest + validate projected files exist + validate registered hooks/config + report stale or missing projections + +uninstall + remove projected host files + unregister hooks/config + preserve canonical .mnemon state by default + update or remove host manifest +``` + +`status` 对 app-server eval 很重要,因为 orchestrator 可以用它确认当前 run +正在测试预期的 projection。 + +## App-Server Eval Host + +App-server eval host 是用于测试 loop 行为的一次性宿主 runtime。它应该使用与真实宿主相同的 projection contract: + +```text +eval orchestrator + | + | create isolated workspace and .mnemon + | run setup//install + | start host app server + v +host app server + | + | API-driven scenarios + v +harness loop projection + | + v +Mnemon engine and canonical state +``` + +Eval 应测试 harness influence 下的 host behavior,而不是只测试 Mnemon CLI CRUD。 +有价值的断言包括: + +- App server 使用隔离的 `.mnemon`。 +- 安装了预期版本的 loop modules。 +- Lifecycle events 通过 manifest 声明的 mapping 被调用。 +- Recall decisions 影响后续任务行为。 +- Writeback decisions 只在合理时写入 durable memory。 +- Reports、proposals 和 audit records 写入 canonical locations。 + +## 质量规则 + +- Projection files 应保持小而明确,并从 canonical assets 生成。 +- Host-specific behavior 放在 `setup//` 或生成的 adapter files。 +- Setup 应尽可能可重复、幂等。 +- Uninstall 应保守,默认保留 canonical state。 +- Manifest paths 尽可能使用相对路径;只有 runtime execution 需要时才使用绝对路径。 +- 公开 projection 行为必须同时维护英文和中文文档。 + diff --git a/docs/zh/harness/README.md b/docs/zh/harness/README.md index 807e114..17b3a99 100644 --- a/docs/zh/harness/README.md +++ b/docs/zh/harness/README.md @@ -21,6 +21,7 @@ host surface projection,以及可选的 daemon scheduling。 | --- | --- | | Modular Agent Harness | [中文](modular-agent/DESIGN.md) / [EN](../../harness/modular-agent/DESIGN.md) | | Loop Module Standard | [中文](LOOP_MODULE_STANDARD.md) / [EN](../../harness/LOOP_MODULE_STANDARD.md) | +| Host Projection | [中文](HOST_PROJECTION.md) / [EN](../../harness/HOST_PROJECTION.md) | | Harness Roadmap | [中文](ROADMAP.md) / [EN](../../harness/ROADMAP.md) | | Memory Loop | [中文](memory-loop/DESIGN.md) / [EN](../../harness/memory-loop/DESIGN.md) / [site](../../site/memory-loop/site.html) | | Skill Loop | [中文](skill-loop/DESIGN.md) / [EN](../../harness/skill-loop/DESIGN.md) / [site](../../site/skill-loop/site.html) | @@ -43,6 +44,7 @@ host surface projection,以及可选的 daemon scheduling。 | protocol | 定义可复用操作的 Markdown skill。 | | subagent | 用于较重 review 或 consolidation 的后台维护 agent。 | | projection | 把 canonical loop assets 渲染到 `.claude`、`.codex` 或其他 runtime surface 的宿主特定过程。 | +| host manifest | 机器可读记录,描述已投影 loops、paths、lifecycle mappings 和 host capabilities。 | | daemon | 可选的 harness maintenance runner,用于调度模块后台工作。 | | substrate | Mnemon 拥有的运行时基座,用于 module state、setup、projection、scheduling 和跨模块协议。 | From 4eb7fea783681c341e67910e9bd8d225f7f08711 Mon Sep 17 00:00:00 2001 From: Grivn Date: Thu, 14 May 2026 17:22:11 +0000 Subject: [PATCH 3/4] Refactor harness deployment layers --- Makefile | 5 +- README.md | 6 +- docs/harness/LOOP_MODULE_STANDARD.md | 32 +- docs/harness/README.md | 13 +- docs/harness/memory-loop/DESIGN.md | 4 +- docs/harness/modular-agent/DESIGN.md | 4 +- docs/harness/skill-loop/DESIGN.md | 2 +- docs/zh/README.md | 6 +- docs/zh/harness/LOOP_MODULE_STANDARD.md | 32 +- docs/zh/harness/README.md | 13 +- docs/zh/harness/memory-loop/DESIGN.md | 4 +- docs/zh/harness/modular-agent/DESIGN.md | 4 +- docs/zh/harness/skill-loop/DESIGN.md | 2 +- harness/hosts/README.md | 12 + harness/hosts/claude-code/host.json | 19 + .../claude-code/memory-loop}/hooks/compact.sh | 0 .../claude-code/memory-loop}/hooks/nudge.sh | 0 .../claude-code/memory-loop}/hooks/prime.sh | 0 .../claude-code/memory-loop}/hooks/remind.sh | 0 .../memory-loop}/scripts/update_settings.py | 0 harness/hosts/claude-code/projector.sh | 426 ++++++++++++++++++ .../claude-code/skill-loop}/hooks/compact.sh | 0 .../claude-code/skill-loop}/hooks/nudge.sh | 0 .../claude-code/skill-loop}/hooks/prime.sh | 0 .../claude-code/skill-loop}/hooks/remind.sh | 0 .../skill-loop}/scripts/update_settings.py | 0 harness/memory-loop/README.md | 116 +---- .../memory-loop/setup/claude-code/install.sh | 147 +----- .../setup/claude-code/uninstall.sh | 62 +-- harness/modules/README.md | 12 + harness/{ => modules}/memory-loop/GUIDE.md | 0 harness/{ => modules}/memory-loop/MEMORY.md | 0 harness/modules/memory-loop/README.md | 110 +++++ harness/{ => modules}/memory-loop/env.sh | 0 .../memory-loop/hooks/compact.md | 0 .../{ => modules}/memory-loop/hooks/nudge.md | 0 .../{ => modules}/memory-loop/hooks/prime.md | 0 .../{ => modules}/memory-loop/hooks/remind.md | 0 harness/{ => modules}/memory-loop/module.json | 2 +- .../memory-loop/skills/memory_get.md | 0 .../memory-loop/skills/memory_set.md | 0 .../memory-loop/subagents/dreaming.md | 0 harness/{ => modules}/skill-loop/GUIDE.md | 0 harness/modules/skill-loop/README.md | 116 +++++ harness/{ => modules}/skill-loop/env.sh | 0 .../{ => modules}/skill-loop/hooks/compact.md | 0 .../{ => modules}/skill-loop/hooks/nudge.md | 0 .../{ => modules}/skill-loop/hooks/prime.md | 0 .../{ => modules}/skill-loop/hooks/remind.md | 0 harness/{ => modules}/skill-loop/module.json | 2 +- .../skill-loop/skills/skill_curate.md | 0 .../skill-loop/skills/skill_manage.md | 0 .../skill-loop/skills/skill_observe.md | 0 .../skill-loop/subagents/curator.md | 0 harness/setup/README.md | 24 + harness/setup/install.sh | 63 +++ harness/setup/lib/paths.sh | 32 ++ harness/setup/status.sh | 61 +++ harness/setup/uninstall.sh | 63 +++ harness/skill-loop/README.md | 122 +---- .../skill-loop/setup/claude-code/install.sh | 153 +------ .../skill-loop/setup/claude-code/uninstall.sh | 79 +--- scripts/validate_harness_modules.sh | 60 +++ 63 files changed, 1104 insertions(+), 704 deletions(-) create mode 100644 harness/hosts/README.md create mode 100644 harness/hosts/claude-code/host.json rename harness/{memory-loop/setup/claude-code => hosts/claude-code/memory-loop}/hooks/compact.sh (100%) rename harness/{memory-loop/setup/claude-code => hosts/claude-code/memory-loop}/hooks/nudge.sh (100%) rename harness/{memory-loop/setup/claude-code => hosts/claude-code/memory-loop}/hooks/prime.sh (100%) rename harness/{memory-loop/setup/claude-code => hosts/claude-code/memory-loop}/hooks/remind.sh (100%) rename harness/{memory-loop/setup/claude-code => hosts/claude-code/memory-loop}/scripts/update_settings.py (100%) create mode 100755 harness/hosts/claude-code/projector.sh rename harness/{skill-loop/setup/claude-code => hosts/claude-code/skill-loop}/hooks/compact.sh (100%) rename harness/{skill-loop/setup/claude-code => hosts/claude-code/skill-loop}/hooks/nudge.sh (100%) rename harness/{skill-loop/setup/claude-code => hosts/claude-code/skill-loop}/hooks/prime.sh (100%) rename harness/{skill-loop/setup/claude-code => hosts/claude-code/skill-loop}/hooks/remind.sh (100%) rename harness/{skill-loop/setup/claude-code => hosts/claude-code/skill-loop}/scripts/update_settings.py (100%) mode change 100644 => 100755 harness/memory-loop/setup/claude-code/install.sh mode change 100644 => 100755 harness/memory-loop/setup/claude-code/uninstall.sh create mode 100644 harness/modules/README.md rename harness/{ => modules}/memory-loop/GUIDE.md (100%) rename harness/{ => modules}/memory-loop/MEMORY.md (100%) create mode 100644 harness/modules/memory-loop/README.md rename harness/{ => modules}/memory-loop/env.sh (100%) rename harness/{ => modules}/memory-loop/hooks/compact.md (100%) rename harness/{ => modules}/memory-loop/hooks/nudge.md (100%) rename harness/{ => modules}/memory-loop/hooks/prime.md (100%) rename harness/{ => modules}/memory-loop/hooks/remind.md (100%) rename harness/{ => modules}/memory-loop/module.json (95%) rename harness/{ => modules}/memory-loop/skills/memory_get.md (100%) rename harness/{ => modules}/memory-loop/skills/memory_set.md (100%) rename harness/{ => modules}/memory-loop/subagents/dreaming.md (100%) rename harness/{ => modules}/skill-loop/GUIDE.md (100%) create mode 100644 harness/modules/skill-loop/README.md rename harness/{ => modules}/skill-loop/env.sh (100%) rename harness/{ => modules}/skill-loop/hooks/compact.md (100%) rename harness/{ => modules}/skill-loop/hooks/nudge.md (100%) rename harness/{ => modules}/skill-loop/hooks/prime.md (100%) rename harness/{ => modules}/skill-loop/hooks/remind.md (100%) rename harness/{ => modules}/skill-loop/module.json (95%) rename harness/{ => modules}/skill-loop/skills/skill_curate.md (100%) rename harness/{ => modules}/skill-loop/skills/skill_manage.md (100%) rename harness/{ => modules}/skill-loop/skills/skill_observe.md (100%) rename harness/{ => modules}/skill-loop/subagents/curator.md (100%) create mode 100644 harness/setup/README.md create mode 100755 harness/setup/install.sh create mode 100755 harness/setup/lib/paths.sh create mode 100755 harness/setup/status.sh create mode 100755 harness/setup/uninstall.sh mode change 100644 => 100755 harness/skill-loop/setup/claude-code/install.sh mode change 100644 => 100755 harness/skill-loop/setup/claude-code/uninstall.sh create mode 100755 scripts/validate_harness_modules.sh diff --git a/Makefile b/Makefile index 4295890..2e00777 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ ifeq ($(GOBIN),) GOBIN := $(shell go env GOPATH)/bin endif -.PHONY: deps build install uninstall test unit vet docker-build docker-run compose-up compose-down compose-dev release-snapshot clean help +.PHONY: deps build install uninstall test unit vet harness-validate docker-build docker-run compose-up compose-down compose-dev release-snapshot clean help .DEFAULT_GOAL := help @@ -45,6 +45,9 @@ unit: ## Run Go unit tests vet: ## Run go vet static analysis go vet ./... +harness-validate: ## Validate harness module manifests and declared asset paths + bash scripts/validate_harness_modules.sh + # ── Containers / Deployment ────────────────────────────────────────── docker-build: ## Build runtime Docker image diff --git a/README.md b/README.md index 903a966..fe154de 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ Different agents/processes can use different stores via the `MNEMON_STORE` envir **How do I customize the behavior?** Edit the generated guideline (`~/.mnemon/prompt/guide.md` in current setup -flows) or use the installable [memory loop GUIDE](harness/memory-loop/GUIDE.md) +flows) or use the installable [memory loop GUIDE](harness/modules/memory-loop/GUIDE.md) as the source. The skill file should stay focused on command syntax. **What is sub-agent delegation?** @@ -250,8 +250,8 @@ See [Development and Deployment](docs/DEPLOYMENT.md) for Docker, Compose, Ollama ## Documentation - [Modular Self-Evolution Harness](docs/harness/README.md) — formal harness docs for modular agent, memory loop, and skill loop design -- [Memory Loop Harness](harness/memory-loop/README.md) — installable memory loop assets -- [Skill Loop Harness](harness/skill-loop/README.md) — installable skill loop assets +- [Memory Loop Harness](harness/modules/memory-loop/README.md) — installable memory loop assets +- [Skill Loop Harness](harness/modules/skill-loop/README.md) — installable skill loop assets - [Design & Architecture](docs/DESIGN.md) — current engine architecture, algorithms, integration design - [Usage & Reference](docs/USAGE.md) — CLI commands, embedding support, architecture overview - [Architecture Diagrams](docs/diagrams/) — system architecture, pipelines, lifecycle management diff --git a/docs/harness/LOOP_MODULE_STANDARD.md b/docs/harness/LOOP_MODULE_STANDARD.md index 12dd405..e358a9e 100644 --- a/docs/harness/LOOP_MODULE_STANDARD.md +++ b/docs/harness/LOOP_MODULE_STANDARD.md @@ -32,7 +32,7 @@ permission model, and UI. Every installable loop module should follow this shape: ```text -harness// +harness/modules// ├── README.md ├── module.json ├── env.sh @@ -46,13 +46,24 @@ harness// │ └── .md ├── subagents/ │ └── .md -└── setup/ - ├── claude-code/ - │ ├── install.sh - │ └── uninstall.sh - └── / - ├── install.sh - └── uninstall.sh +``` + +Host-specific projection logic lives outside modules: + +```text +harness/hosts// +├── projector.sh +├── templates/ +└── scripts/ +``` + +Shared setup entrypoints compose modules and hosts: + +```text +harness/setup/ +├── install.sh +├── status.sh +└── uninstall.sh ``` Loop-specific runtime files may be added when they are part of the loop @@ -68,7 +79,7 @@ contract, such as `MEMORY.md` for the Memory Loop. | `hooks/*.md` | Yes | Host-agnostic lifecycle reminders. They describe what the agent should consider at a lifecycle boundary. | | `skills/*.md` | Usually | Protocol skills for reusable online operations. These define procedures, not host-specific installation. | | `subagents/*.md` | Optional | Maintenance roles for heavier review, consolidation, or proposal generation. Hosts without native subagents may run them as manual or scheduled jobs. | -| `setup//` | At least one | Host-specific projection adapter that installs or removes the module from a host runtime. | +| `harness/hosts//` | At least one host overall | Host-specific projection adapter that installs or removes modules from a host runtime. | ## Lifecycle Events @@ -166,7 +177,7 @@ Each loop module should include a `module.json` file with this stable shape: "loop_runtime": [] }, "host_adapters": { - "claude-code": "setup/claude-code" + "claude-code": "../../hosts/claude-code" } } ``` @@ -199,4 +210,3 @@ The same standard concepts map differently across hosts: - Preserve user state on uninstall unless a destructive flag is explicit. - Document English and Chinese behavior together when adding or changing public harness concepts. - diff --git a/docs/harness/README.md b/docs/harness/README.md index 668448f..cb546e4 100644 --- a/docs/harness/README.md +++ b/docs/harness/README.md @@ -35,8 +35,17 @@ projection into host surfaces, and optional daemon scheduling. | Harness Module | Implementation | | --- | --- | -| Memory Loop | [harness/memory-loop](../../harness/memory-loop/README.md) | -| Skill Loop | [harness/skill-loop](../../harness/skill-loop/README.md) | +| Memory Loop | [harness/modules/memory-loop](../../harness/modules/memory-loop/README.md) | +| Skill Loop | [harness/modules/skill-loop](../../harness/modules/skill-loop/README.md) | + +## Repository Layout + +| Directory | Role | +| --- | --- | +| `harness/modules/` | Canonical host-agnostic loop modules. | +| `harness/hosts/` | Host projection adapters such as Claude Code and future Codex support. | +| `harness/setup/` | Shared install, status, and uninstall entrypoints that compose modules with hosts. | +| `harness//` | Compatibility wrappers for older install paths. | ## Vocabulary diff --git a/docs/harness/memory-loop/DESIGN.md b/docs/harness/memory-loop/DESIGN.md index ad851b3..ebc64b3 100644 --- a/docs/harness/memory-loop/DESIGN.md +++ b/docs/harness/memory-loop/DESIGN.md @@ -4,7 +4,7 @@ Related visualization: [site.html](../../site/memory-loop/site.html) Chinese version: [DESIGN.md](../../zh/harness/memory-loop/DESIGN.md) -Installable MVP assets: [harness/memory-loop](../../../harness/memory-loop/README.md) +Installable MVP assets: [harness/modules/memory-loop](../../../harness/modules/memory-loop/README.md) The memory loop is the first practical slice of the self-evolution harness. It gives a host agent a prompt-facing working memory while using Mnemon as durable long-term memory. The harness stays small: it installs Markdown policy, hook prompts, protocol skills, and one maintenance subagent around an existing host agent. @@ -111,7 +111,7 @@ Everything else is a harness asset around these three parts. | Concept | Memory Loop Asset | Responsibility | Boundary | | --- | --- | --- | --- | | GUIDE | `GUIDE.md` | Defines when to read, write, compact, and consolidate memory. | Policy only; it does not bind storage targets. | -| setup | `setup/claude-code` + `env.sh` | Installs hooks, protocol skills, dreaming subagent, memory files, and environment variables. | Installation only; not a runtime decision maker. | +| setup | `harness/setup` + host projection | Installs hooks, protocol skills, dreaming subagent, memory files, and environment variables. | Installation only; not a runtime decision maker. | | hook | `prime/remind/nudge/compact` | Provides host lifecycle timing and short reminders. | No heavy reasoning or storage protocol. | | protocol | `memory_get.md` / `memory_set.md` | Defines online recall from Mnemon and online edits to `MEMORY.md`. | Called by the host only when GUIDE says memory work is useful. | | subagent | `dreaming` | Consolidates `MEMORY.md` into Mnemon and rewrites working memory. | Background or explicit maintenance, not every-turn online behavior. | diff --git a/docs/harness/modular-agent/DESIGN.md b/docs/harness/modular-agent/DESIGN.md index 6efc05b..5fa7d1f 100644 --- a/docs/harness/modular-agent/DESIGN.md +++ b/docs/harness/modular-agent/DESIGN.md @@ -195,8 +195,8 @@ locks, and status. | Module | Purpose | Current Reference Host | | --- | --- | --- | -| Memory Loop | Adds working memory, long-term memory, and dreaming consolidation. | Claude Code setup under `harness/memory-loop/setup/claude-code`. | -| Skill Loop | Adds active/stale/archived skill lifecycle, evidence capture, curator proposals, and approved lifecycle mutation. | Claude Code setup under `harness/skill-loop/setup/claude-code`. | +| Memory Loop | Adds working memory, long-term memory, and dreaming consolidation. | Claude Code setup under `harness/setup/install.sh --host claude-code --module memory-loop`. | +| Skill Loop | Adds active/stale/archived skill lifecycle, evidence capture, curator proposals, and approved lifecycle mutation. | Claude Code setup under `harness/setup/install.sh --host claude-code --module skill-loop`. | ## Relationship To Skill Packs diff --git a/docs/harness/skill-loop/DESIGN.md b/docs/harness/skill-loop/DESIGN.md index c7f48ae..923bde9 100644 --- a/docs/harness/skill-loop/DESIGN.md +++ b/docs/harness/skill-loop/DESIGN.md @@ -2,7 +2,7 @@ Related visualization: [site.html](../../site/skill-loop/site.html) -Installable MVP assets: [harness/skill-loop](../../../harness/skill-loop/README.md) +Installable MVP assets: [harness/modules/skill-loop](../../../harness/modules/skill-loop/README.md) The skill loop gives a host agent a self-evolving skill library without replacing the host's native skill runtime. It treats skills as host-native assets, while `.mnemon` owns the canonical lifecycle state and the evidence used to evolve that state. diff --git a/docs/zh/README.md b/docs/zh/README.md index 8428302..afdcdbf 100644 --- a/docs/zh/README.md +++ b/docs/zh/README.md @@ -196,7 +196,7 @@ MNEMON_STORE=work mnemon recall "query" # 或按进程使用环境变量 `mnemon setup` 默认**本地**(项目级 `.claude/`),适合大多数用户。**全局**(`mnemon setup --global`,安装到 `~/.claude/`)在所有项目中激活 mnemon — 如果想让其他框架(如 OpenClaw)通过 Claude Code CLI 共享记忆很方便,但可能增加维护开销。 **如何自定义行为?** -编辑当前 setup 流程生成的 guideline(`~/.mnemon/prompt/guide.md`),或以可安装的 [memory loop GUIDE](../../harness/memory-loop/GUIDE.md) 作为来源。Skill 文件应专注于命令语法。 +编辑当前 setup 流程生成的 guideline(`~/.mnemon/prompt/guide.md`),或以可安装的 [memory loop GUIDE](../../harness/modules/memory-loop/GUIDE.md) 作为来源。Skill 文件应专注于命令语法。 **什么是 Sub-agent 委派?** Sub-agent 委派是可选执行策略。当 runtime 支持时,主 agent 可以决定*记什么*,再让更便宜或隔离的 worker 执行 `mnemon remember`。它有用,但不是 Mnemon 架构必需品。 @@ -230,8 +230,8 @@ make help # 显示所有目标 ## 文档 - [Modular Self-Evolution Harness](harness/README.md) — modular agent、memory loop 与 skill loop 的正式 harness 文档 -- [Memory Loop Harness](../../harness/memory-loop/README.md) — 可安装 memory loop 资产 -- [Skill Loop Harness](../../harness/skill-loop/README.md) — 可安装 skill loop 资产 +- [Memory Loop Harness](../../harness/modules/memory-loop/README.md) — 可安装 memory loop 资产 +- [Skill Loop Harness](../../harness/modules/skill-loop/README.md) — 可安装 skill loop 资产 - [设计与架构](DESIGN.md) — 当前 engine architecture、核心概念、算法、集成设计 - [用法与参考](USAGE.md) — CLI 命令、嵌入向量支持、架构概览 - [架构图](../diagrams/) — 系统架构、记忆/召回流程、四图模型、生命周期管理 diff --git a/docs/zh/harness/LOOP_MODULE_STANDARD.md b/docs/zh/harness/LOOP_MODULE_STANDARD.md index 75f6603..7dbecfa 100644 --- a/docs/zh/harness/LOOP_MODULE_STANDARD.md +++ b/docs/zh/harness/LOOP_MODULE_STANDARD.md @@ -30,7 +30,7 @@ loop、prompt assembly、tool routing、native skill discovery、权限模型和 每个可安装 loop module 应该遵循这个结构: ```text -harness// +harness/modules// ├── README.md ├── module.json ├── env.sh @@ -44,13 +44,24 @@ harness// │ └── .md ├── subagents/ │ └── .md -└── setup/ - ├── claude-code/ - │ ├── install.sh - │ └── uninstall.sh - └── / - ├── install.sh - └── uninstall.sh +``` + +Host-specific projection logic 位于 modules 之外: + +```text +harness/hosts// +├── projector.sh +├── templates/ +└── scripts/ +``` + +Shared setup entrypoints 负责组合 modules 和 hosts: + +```text +harness/setup/ +├── install.sh +├── status.sh +└── uninstall.sh ``` 如果某个 loop 的契约需要额外 runtime 文件,可以加入该目录,例如 Memory Loop @@ -66,7 +77,7 @@ harness// | `hooks/*.md` | 是 | 与宿主无关的 lifecycle reminders。描述 agent 在生命周期边界应考虑什么。 | | `skills/*.md` | 通常是 | 用于在线可复用操作的 protocol skills。它们定义流程,不定义宿主安装方式。 | | `subagents/*.md` | 可选 | 用于较重 review、consolidation 或 proposal generation 的维护角色。没有 native subagent 的宿主可以降级为人工或定时 job。 | -| `setup//` | 至少一个 | Host-specific projection adapter,把 module 安装或移除到某个宿主 runtime。 | +| `harness/hosts//` | 整体至少一个 host | Host-specific projection adapter,把 modules 安装或移除到某个宿主 runtime。 | ## 生命周期事件 @@ -162,7 +173,7 @@ projection surfaces。 "loop_runtime": [] }, "host_adapters": { - "claude-code": "setup/claude-code" + "claude-code": "../../hosts/claude-code" } } ``` @@ -194,4 +205,3 @@ projection surfaces。 - setup、status 和 uninstall 行为必须明确、可审计。 - 卸载时保留用户状态,除非用户显式传入破坏性选项。 - 新增或修改公开 harness 概念时,同步维护英文和中文文档。 - diff --git a/docs/zh/harness/README.md b/docs/zh/harness/README.md index 17b3a99..a31696c 100644 --- a/docs/zh/harness/README.md +++ b/docs/zh/harness/README.md @@ -30,8 +30,17 @@ host surface projection,以及可选的 daemon scheduling。 | Harness Module | 实现 | | --- | --- | -| Memory Loop | [harness/memory-loop](../../../harness/memory-loop/README.md) | -| Skill Loop | [harness/skill-loop](../../../harness/skill-loop/README.md) | +| Memory Loop | [harness/modules/memory-loop](../../../harness/modules/memory-loop/README.md) | +| Skill Loop | [harness/modules/skill-loop](../../../harness/modules/skill-loop/README.md) | + +## 仓库布局 + +| 目录 | 作用 | +| --- | --- | +| `harness/modules/` | Canonical、host-agnostic loop modules。 | +| `harness/hosts/` | Host projection adapters,例如 Claude Code,以及后续 Codex 支持。 | +| `harness/setup/` | 统一 install、status 和 uninstall 入口,用来组合 modules 与 hosts。 | +| `harness//` | 为旧安装路径保留的兼容 wrapper。 | ## 词汇 diff --git a/docs/zh/harness/memory-loop/DESIGN.md b/docs/zh/harness/memory-loop/DESIGN.md index 916c88e..3bc9890 100644 --- a/docs/zh/harness/memory-loop/DESIGN.md +++ b/docs/zh/harness/memory-loop/DESIGN.md @@ -4,7 +4,7 @@ 英文版本:[DESIGN.md](../../../harness/memory-loop/DESIGN.md) -可安装 MVP 资产:[harness/memory-loop](../../../../harness/memory-loop/README.md) +可安装 MVP 资产:[harness/modules/memory-loop](../../../../harness/modules/memory-loop/README.md) Memory loop 是 self-evolution harness 的第一个可落地切片。它给 HostAgent 提供一份面向 prompt 的工作记忆,同时使用 Mnemon 作为持久长期记忆。Harness 本身保持很小:围绕已有 HostAgent 安装 Markdown policy、hook prompt、protocol skills 和一个维护型 subagent。 @@ -102,7 +102,7 @@ Search result 只有在被 agent 内化为耐久的 user、project 或 task stat | 概念 | Memory Loop 资产 | 职责 | 边界 | | --- | --- | --- | --- | | GUIDE | `GUIDE.md` | 定义何时读、何时写、何时压缩、何时巩固。 | 只写 policy,不绑定存储目标。 | -| setup | `setup/claude-code` + `env.sh` | 安装 hooks、protocol skills、dreaming subagent、memory 文件和环境变量。 | 只负责安装,不参与 runtime 判断。 | +| setup | `harness/setup` + host projection | 安装 hooks、protocol skills、dreaming subagent、memory 文件和环境变量。 | 只负责安装,不参与 runtime 判断。 | | hook | `prime/remind/nudge/compact` | 提供 Host 生命周期时机和短提醒。 | 不承载复杂推理或存储协议。 | | protocol | `memory_get.md` / `memory_set.md` | 定义在线 Mnemon recall 和在线 `MEMORY.md` 编辑。 | 只有 GUIDE 判断需要时才由 HostAgent 调用。 | | subagent | `dreaming` | 将 `MEMORY.md` 巩固到 Mnemon,并重写工作记忆。 | 后台或显式维护流程,不是每轮在线行为。 | diff --git a/docs/zh/harness/modular-agent/DESIGN.md b/docs/zh/harness/modular-agent/DESIGN.md index 4cee352..3e3f5eb 100644 --- a/docs/zh/harness/modular-agent/DESIGN.md +++ b/docs/zh/harness/modular-agent/DESIGN.md @@ -183,8 +183,8 @@ Harness Modules -> memory、skills、eval、risk、review、audit、policy | Module | 目的 | 当前参考宿主 | | --- | --- | --- | -| Memory Loop | 增加 working memory、long-term memory 和 dreaming consolidation。 | Claude Code setup 位于 `harness/memory-loop/setup/claude-code`。 | -| Skill Loop | 增加 active/stale/archived skill lifecycle、evidence capture、curator proposal 和批准后的 lifecycle mutation。 | Claude Code setup 位于 `harness/skill-loop/setup/claude-code`。 | +| Memory Loop | 增加 working memory、long-term memory 和 dreaming consolidation。 | Claude Code setup 位于 `harness/setup/install.sh --host claude-code --module memory-loop`。 | +| Skill Loop | 增加 active/stale/archived skill lifecycle、evidence capture、curator proposal 和批准后的 lifecycle mutation。 | Claude Code setup 位于 `harness/setup/install.sh --host claude-code --module skill-loop`。 | ## 与 Skill Packs 的关系 diff --git a/docs/zh/harness/skill-loop/DESIGN.md b/docs/zh/harness/skill-loop/DESIGN.md index 1f724fb..d03de59 100644 --- a/docs/zh/harness/skill-loop/DESIGN.md +++ b/docs/zh/harness/skill-loop/DESIGN.md @@ -4,7 +4,7 @@ 英文版本:[DESIGN.md](../../../harness/skill-loop/DESIGN.md) -可安装 MVP 资产:[harness/skill-loop](../../../../harness/skill-loop/README.md) +可安装 MVP 资产:[harness/modules/skill-loop](../../../../harness/modules/skill-loop/README.md) Skill loop 的目标是让宿主 Agent 拥有一套可自我演进的 skill library,同时不替换宿主原生的 skill runtime。Skill 仍然是宿主可发现、可调用的原生资产;Mnemon 负责保存 canonical lifecycle state,以及支撑演进判断的 evidence。 diff --git a/harness/hosts/README.md b/harness/hosts/README.md new file mode 100644 index 0000000..656f9ab --- /dev/null +++ b/harness/hosts/README.md @@ -0,0 +1,12 @@ +# Mnemon Harness Hosts + +Host adapters project canonical loop modules into a concrete runtime surface. + +```text +harness/hosts/ +├── claude-code/ +└── codex/ # future +``` + +Adapters should keep host-specific behavior here. Loop modules should stay +host-agnostic under `harness/modules//`. diff --git a/harness/hosts/claude-code/host.json b/harness/hosts/claude-code/host.json new file mode 100644 index 0000000..ec1db77 --- /dev/null +++ b/harness/hosts/claude-code/host.json @@ -0,0 +1,19 @@ +{ + "schema_version": 1, + "name": "claude-code", + "description": "Projects Mnemon harness modules into Claude Code skills, hooks, agents, and settings.json.", + "projection_surfaces": { + "skills": ".claude/skills", + "hooks": ".claude/hooks", + "subagents": ".claude/agents", + "config": ".claude/settings.json" + }, + "lifecycle_mapping": { + "prime": "SessionStart", + "remind": "UserPromptSubmit", + "nudge": "Stop", + "compact": "PreCompact", + "maintenance": "subagent-or-manual" + }, + "projector": "projector.sh" +} diff --git a/harness/memory-loop/setup/claude-code/hooks/compact.sh b/harness/hosts/claude-code/memory-loop/hooks/compact.sh similarity index 100% rename from harness/memory-loop/setup/claude-code/hooks/compact.sh rename to harness/hosts/claude-code/memory-loop/hooks/compact.sh diff --git a/harness/memory-loop/setup/claude-code/hooks/nudge.sh b/harness/hosts/claude-code/memory-loop/hooks/nudge.sh similarity index 100% rename from harness/memory-loop/setup/claude-code/hooks/nudge.sh rename to harness/hosts/claude-code/memory-loop/hooks/nudge.sh diff --git a/harness/memory-loop/setup/claude-code/hooks/prime.sh b/harness/hosts/claude-code/memory-loop/hooks/prime.sh similarity index 100% rename from harness/memory-loop/setup/claude-code/hooks/prime.sh rename to harness/hosts/claude-code/memory-loop/hooks/prime.sh diff --git a/harness/memory-loop/setup/claude-code/hooks/remind.sh b/harness/hosts/claude-code/memory-loop/hooks/remind.sh similarity index 100% rename from harness/memory-loop/setup/claude-code/hooks/remind.sh rename to harness/hosts/claude-code/memory-loop/hooks/remind.sh diff --git a/harness/memory-loop/setup/claude-code/scripts/update_settings.py b/harness/hosts/claude-code/memory-loop/scripts/update_settings.py similarity index 100% rename from harness/memory-loop/setup/claude-code/scripts/update_settings.py rename to harness/hosts/claude-code/memory-loop/scripts/update_settings.py diff --git a/harness/hosts/claude-code/projector.sh b/harness/hosts/claude-code/projector.sh new file mode 100755 index 0000000..8dcd459 --- /dev/null +++ b/harness/hosts/claude-code/projector.sh @@ -0,0 +1,426 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Project Mnemon harness modules into Claude Code. + +Usage: + projector.sh install --module MODULE [options] + projector.sh status --module MODULE [options] + projector.sh uninstall --module MODULE [options] + +Common options: + --global + --config-dir DIR + +Memory loop install options: + --store NAME + --no-remind + --no-nudge + --no-compact + +Skill loop install options: + --host-skills-dir DIR + --with-remind + --no-nudge + --no-compact + +Uninstall options: + --purge-memory + --purge-library +USAGE +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=../../setup/lib/paths.sh +source "${SCRIPT_DIR}/../../setup/lib/paths.sh" + +ACTION="${1:-}" +if [[ -z "${ACTION}" ]]; then + usage >&2 + exit 2 +fi +shift + +MODULE="" +CONFIG_DIR=".claude" +CONFIG_DIR_EXPLICIT=0 +GLOBAL=0 +STORE_NAME="" +HOST_SKILLS_DIR="" +ENABLE_REMIND="" +ENABLE_NUDGE=1 +ENABLE_COMPACT=1 +PURGE_MEMORY=0 +PURGE_LIBRARY=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --module) + MODULE="${2:?missing value for --module}" + shift 2 + ;; + --global) + GLOBAL=1 + CONFIG_DIR="${HOME}/.claude" + shift + ;; + --config-dir) + CONFIG_DIR="${2:?missing value for --config-dir}" + CONFIG_DIR_EXPLICIT=1 + shift 2 + ;; + --store) + STORE_NAME="${2:?missing value for --store}" + shift 2 + ;; + --host-skills-dir) + HOST_SKILLS_DIR="${2:?missing value for --host-skills-dir}" + shift 2 + ;; + --with-remind) + ENABLE_REMIND=1 + shift + ;; + --no-remind) + ENABLE_REMIND=0 + shift + ;; + --no-nudge) + ENABLE_NUDGE=0 + shift + ;; + --no-compact) + ENABLE_COMPACT=0 + shift + ;; + --purge-memory) + PURGE_MEMORY=1 + shift + ;; + --purge-library) + PURGE_LIBRARY=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ -z "${MODULE}" ]]; then + echo "--module is required" >&2 + usage >&2 + exit 2 +fi +if [[ "${MODULE}" != "memory-loop" && "${MODULE}" != "skill-loop" ]]; then + echo "unsupported module for Claude Code: ${MODULE}" >&2 + exit 1 +fi + +MODULE_DIR="$(mnemon_module_dir "${MODULE}")" +if [[ ! -d "${MODULE_DIR}" ]]; then + echo "module directory not found: ${MODULE_DIR}" >&2 + exit 1 +fi + +if [[ "${GLOBAL}" == "1" && "${CONFIG_DIR_EXPLICIT}" == "0" ]]; then + MNEMON_DIR="${MNEMON_HARNESS_STATE_DIR:-${HOME}/.mnemon}" +else + MNEMON_DIR="${MNEMON_HARNESS_STATE_DIR:-.mnemon}" +fi +CANONICAL_MODULE_DIR="${MNEMON_DIR}/harness/${MODULE}" +HOST_MANIFEST_DIR="${MNEMON_DIR}/hosts/claude-code" +HOST_MANIFEST="${HOST_MANIFEST_DIR}/manifest.json" + +install_file() { + local src="$1" + local dst="$2" + local mode="$3" + mkdir -p "$(dirname "${dst}")" + cp "${src}" "${dst}" + chmod "${mode}" "${dst}" +} + +ensure_python() { + if ! command -v python3 >/dev/null 2>&1; then + echo "python3 is required to update Claude Code settings.json" >&2 + exit 1 + fi +} + +ensure_mnemon_binary() { + if ! command -v mnemon >/dev/null 2>&1; then + echo "mnemon binary not found in PATH. Install it first, for example:" >&2 + echo " brew install mnemon-dev/tap/mnemon" >&2 + exit 1 + fi +} + +copy_common_canonical_assets() { + mkdir -p "${CANONICAL_MODULE_DIR}" + install_file "${MODULE_DIR}/GUIDE.md" "${CANONICAL_MODULE_DIR}/GUIDE.md" 0644 + install_file "${MODULE_DIR}/env.sh" "${CANONICAL_MODULE_DIR}/env.sh" 0755 + install_file "${MODULE_DIR}/module.json" "${CANONICAL_MODULE_DIR}/module.json" 0644 +} + +write_host_manifest() { + local projection_path="$1" + local module_version + module_version="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("version",""))' "${MODULE_DIR}/module.json")" + mkdir -p "${HOST_MANIFEST_DIR}" + MNEMON_HOST_MANIFEST="${HOST_MANIFEST}" \ + MNEMON_HOST_MODULE="${MODULE}" \ + MNEMON_HOST_MODULE_VERSION="${module_version}" \ + MNEMON_HOST_PROJECT_ROOT="$(pwd)" \ + MNEMON_HOST_MNEMON_DIR="${MNEMON_DIR}" \ + MNEMON_HOST_STORE="${STORE_NAME:-default}" \ + MNEMON_HOST_PROJECTION_PATH="${projection_path}" \ + python3 - <<'PY' +import json +import os +from datetime import datetime, timezone +from pathlib import Path + +path = Path(os.environ["MNEMON_HOST_MANIFEST"]) +if path.exists() and path.stat().st_size: + data = json.loads(path.read_text()) +else: + data = {"schema_version": 1, "host": "claude-code", "loops": {}} + +data["schema_version"] = 1 +data["host"] = "claude-code" +data["updated_at"] = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") +data["project_root"] = os.environ["MNEMON_HOST_PROJECT_ROOT"] +data["mnemon_dir"] = os.environ["MNEMON_HOST_MNEMON_DIR"] +data["store"] = os.environ["MNEMON_HOST_STORE"] +data.setdefault("loops", {})[os.environ["MNEMON_HOST_MODULE"]] = { + "module_path": f"{os.environ['MNEMON_HOST_MNEMON_DIR']}/harness/{os.environ['MNEMON_HOST_MODULE']}", + "module_version": os.environ["MNEMON_HOST_MODULE_VERSION"], + "projection_path": os.environ["MNEMON_HOST_PROJECTION_PATH"], + "lifecycle_mapping": { + "prime": "SessionStart", + "remind": "UserPromptSubmit", + "nudge": "Stop", + "compact": "PreCompact", + }, +} +path.write_text(json.dumps(data, indent=2) + "\n") +PY +} + +remove_host_manifest_module() { + [[ -f "${HOST_MANIFEST}" ]] || return 0 + MNEMON_HOST_MANIFEST="${HOST_MANIFEST}" MNEMON_HOST_MODULE="${MODULE}" python3 - <<'PY' +import json +import os +from pathlib import Path + +path = Path(os.environ["MNEMON_HOST_MANIFEST"]) +data = json.loads(path.read_text()) +loops = data.get("loops") +if isinstance(loops, dict): + loops.pop(os.environ["MNEMON_HOST_MODULE"], None) +if not data.get("loops"): + path.unlink() +else: + path.write_text(json.dumps(data, indent=2) + "\n") +PY +} + +write_memory_projection_env() { + mkdir -p "${CONFIG_DIR}/mnemon-memory-loop" + cat > "${CONFIG_DIR}/mnemon-memory-loop/env.sh" < "${CONFIG_DIR}/mnemon-skill-loop/env.sh" </dev/null | sed 's/^[* ]*//' | grep -qx "${STORE_NAME}"; then + mnemon store create "${STORE_NAME}" >/dev/null + fi + mnemon store set "${STORE_NAME}" >/dev/null + fi + + write_host_manifest "${CONFIG_DIR}" + + echo "Installed Mnemon memory loop for Claude Code." + echo "Config: ${CONFIG_DIR}" + echo "State: ${CANONICAL_MODULE_DIR}" + echo "Memory: ${CANONICAL_MODULE_DIR}/MEMORY.md" +} + +install_skill_loop() { + ensure_python + [[ -n "${ENABLE_REMIND}" ]] || ENABLE_REMIND=0 + [[ -n "${HOST_SKILLS_DIR}" ]] || HOST_SKILLS_DIR="${CONFIG_DIR}/skills" + + copy_common_canonical_assets + mkdir -p \ + "${CANONICAL_MODULE_DIR}/skills/active" \ + "${CANONICAL_MODULE_DIR}/skills/stale" \ + "${CANONICAL_MODULE_DIR}/skills/archived" \ + "${CANONICAL_MODULE_DIR}/proposals" \ + "${CANONICAL_MODULE_DIR}/reports" \ + "${HOST_SKILLS_DIR}/skill_observe" \ + "${HOST_SKILLS_DIR}/skill_curate" \ + "${HOST_SKILLS_DIR}/skill_manage" \ + "${CONFIG_DIR}/agents" \ + "${CONFIG_DIR}/hooks/mnemon-skill-loop" + write_skill_projection_env + + install_file "${MODULE_DIR}/skills/skill_observe.md" "${HOST_SKILLS_DIR}/skill_observe/SKILL.md" 0644 + install_file "${MODULE_DIR}/skills/skill_curate.md" "${HOST_SKILLS_DIR}/skill_curate/SKILL.md" 0644 + install_file "${MODULE_DIR}/skills/skill_manage.md" "${HOST_SKILLS_DIR}/skill_manage/SKILL.md" 0644 + install_file "${MODULE_DIR}/subagents/curator.md" "${CONFIG_DIR}/agents/mnemon-skill-curator.md" 0644 + + install_file "${SCRIPT_DIR}/skill-loop/hooks/prime.sh" "${CONFIG_DIR}/hooks/mnemon-skill-loop/prime.sh" 0755 + install_file "${SCRIPT_DIR}/skill-loop/hooks/remind.sh" "${CONFIG_DIR}/hooks/mnemon-skill-loop/remind.sh" 0755 + install_file "${SCRIPT_DIR}/skill-loop/hooks/nudge.sh" "${CONFIG_DIR}/hooks/mnemon-skill-loop/nudge.sh" 0755 + install_file "${SCRIPT_DIR}/skill-loop/hooks/compact.sh" "${CONFIG_DIR}/hooks/mnemon-skill-loop/compact.sh" 0755 + + python3 "$(settings_script)" install --config-dir "${CONFIG_DIR}" --remind "${ENABLE_REMIND}" --nudge "${ENABLE_NUDGE}" --compact "${ENABLE_COMPACT}" + write_host_manifest "${CONFIG_DIR}" + + echo "Installed Mnemon skill loop for Claude Code." + echo "Config: ${CONFIG_DIR}" + echo "State: ${CANONICAL_MODULE_DIR}" + echo "Host skills: ${HOST_SKILLS_DIR}" +} + +status_module() { + echo "Claude Code ${MODULE}:" + echo " config: ${CONFIG_DIR}" + echo " state: ${CANONICAL_MODULE_DIR}" + if [[ -f "${HOST_MANIFEST}" ]]; then + echo " manifest: ${HOST_MANIFEST}" + else + echo " manifest: missing" + fi + if [[ -d "${CANONICAL_MODULE_DIR}" ]]; then + echo " module: installed" + else + echo " module: missing" + fi +} + +uninstall_memory_loop() { + ensure_python + python3 "$(settings_script)" uninstall --config-dir "${CONFIG_DIR}" + rm -rf "${CONFIG_DIR}/hooks/mnemon-memory-loop" + rm -rf "${CONFIG_DIR}/skills/memory_get" + rm -rf "${CONFIG_DIR}/skills/memory_set" + rm -f "${CONFIG_DIR}/agents/mnemon-dreaming.md" + rm -rf "${CONFIG_DIR}/mnemon-memory-loop" + if [[ "${PURGE_MEMORY}" == "1" ]]; then + rm -rf "${CANONICAL_MODULE_DIR}" + else + rm -f "${CANONICAL_MODULE_DIR}/GUIDE.md" + rmdir "${CANONICAL_MODULE_DIR}" 2>/dev/null || true + fi + remove_host_manifest_module + echo "Removed Mnemon memory loop from ${CONFIG_DIR}." +} + +uninstall_skill_loop() { + ensure_python + local env_path="${CONFIG_DIR}/mnemon-skill-loop/env.sh" + if [[ -f "${env_path}" ]]; then + # shellcheck source=/dev/null + source "${env_path}" + fi + local host_skills_dir="${MNEMON_SKILL_LOOP_HOST_SKILLS_DIR:-${HOST_SKILLS_DIR:-${CONFIG_DIR}/skills}}" + + python3 "$(settings_script)" uninstall --config-dir "${CONFIG_DIR}" + if [[ -d "${host_skills_dir}" ]]; then + while IFS= read -r marker; do + rm -rf "$(dirname "${marker}")" + done < <(find "${host_skills_dir}" -mindepth 2 -maxdepth 2 -name .mnemon-skill-loop-generated -print 2>/dev/null) + fi + rm -rf "${CONFIG_DIR}/hooks/mnemon-skill-loop" + rm -rf "${host_skills_dir}/skill_observe" + rm -rf "${host_skills_dir}/skill_curate" + rm -rf "${host_skills_dir}/skill_manage" + rm -f "${CONFIG_DIR}/agents/mnemon-skill-curator.md" + rm -rf "${CONFIG_DIR}/mnemon-skill-loop" + if [[ "${PURGE_LIBRARY}" == "1" ]]; then + rm -rf "${CANONICAL_MODULE_DIR}" + else + rm -f "${CANONICAL_MODULE_DIR}/GUIDE.md" + rmdir "${CANONICAL_MODULE_DIR}/reports" 2>/dev/null || true + rmdir "${CANONICAL_MODULE_DIR}/proposals" 2>/dev/null || true + rmdir "${CANONICAL_MODULE_DIR}" 2>/dev/null || true + fi + remove_host_manifest_module + echo "Removed Mnemon skill loop from ${CONFIG_DIR}." +} + +case "${ACTION}:${MODULE}" in + install:memory-loop) install_memory_loop ;; + install:skill-loop) install_skill_loop ;; + status:memory-loop|status:skill-loop) status_module ;; + uninstall:memory-loop) uninstall_memory_loop ;; + uninstall:skill-loop) uninstall_skill_loop ;; + *) + echo "unsupported action/module: ${ACTION}/${MODULE}" >&2 + exit 1 + ;; +esac diff --git a/harness/skill-loop/setup/claude-code/hooks/compact.sh b/harness/hosts/claude-code/skill-loop/hooks/compact.sh similarity index 100% rename from harness/skill-loop/setup/claude-code/hooks/compact.sh rename to harness/hosts/claude-code/skill-loop/hooks/compact.sh diff --git a/harness/skill-loop/setup/claude-code/hooks/nudge.sh b/harness/hosts/claude-code/skill-loop/hooks/nudge.sh similarity index 100% rename from harness/skill-loop/setup/claude-code/hooks/nudge.sh rename to harness/hosts/claude-code/skill-loop/hooks/nudge.sh diff --git a/harness/skill-loop/setup/claude-code/hooks/prime.sh b/harness/hosts/claude-code/skill-loop/hooks/prime.sh similarity index 100% rename from harness/skill-loop/setup/claude-code/hooks/prime.sh rename to harness/hosts/claude-code/skill-loop/hooks/prime.sh diff --git a/harness/skill-loop/setup/claude-code/hooks/remind.sh b/harness/hosts/claude-code/skill-loop/hooks/remind.sh similarity index 100% rename from harness/skill-loop/setup/claude-code/hooks/remind.sh rename to harness/hosts/claude-code/skill-loop/hooks/remind.sh diff --git a/harness/skill-loop/setup/claude-code/scripts/update_settings.py b/harness/hosts/claude-code/skill-loop/scripts/update_settings.py similarity index 100% rename from harness/skill-loop/setup/claude-code/scripts/update_settings.py rename to harness/hosts/claude-code/skill-loop/scripts/update_settings.py diff --git a/harness/memory-loop/README.md b/harness/memory-loop/README.md index cc864fc..a434bc8 100644 --- a/harness/memory-loop/README.md +++ b/harness/memory-loop/README.md @@ -1,121 +1,21 @@ -# Mnemon Memory Loop Harness +# Mnemon Memory Loop Harness Compatibility Path -This directory is the first installable version of the memory loop harness. It is -agent-agnostic: a capable host agent can read these Markdown assets and install -the loop into its own runtime without a custom adapter. - -## File Tree +The canonical Memory Loop module now lives at: ```text -harness/memory-loop/ -├── README.md -├── module.json -├── env.sh -├── GUIDE.md -├── MEMORY.md -├── hooks/ -│ ├── prime.md -│ ├── remind.md -│ ├── nudge.md -│ └── compact.md -├── skills/ -│ ├── memory_get.md -│ └── memory_set.md -├── subagents/ -│ └── dreaming.md -└── setup/ - └── claude-code/ - ├── install.sh - ├── uninstall.sh - ├── hooks/ - │ ├── prime.sh - │ ├── remind.sh - │ ├── nudge.sh - │ └── compact.sh - └── scripts/ - └── update_settings.py +harness/modules/memory-loop/ ``` -## Core Parts - -| Part | Role | -| --- | --- | -| HostAgent | The host agent runtime. It owns task execution, model judgment, and native hook/skill/subagent mechanisms. | -| `MEMORY.md` | Prompt-facing working memory. It is loaded at Prime and kept compact. | -| Mnemon | Long-term memory binary and store. It is installed separately and accessed through skill/subagent protocols. | - -## Support Assets - -| Asset | Purpose | -| --- | --- | -| `module.json` | Machine-readable loop manifest for standard lifecycle events, assets, state, and host adapters. | -| `env.sh` | Runtime config: memory directory, env path, and dreaming threshold. | -| `GUIDE.md` | Policy: when to read memory, when to write memory, and what is worth keeping. | -| `hooks/*.md` | Four lifecycle reminders: Prime, Remind, Nudge, and Compact. | -| `skills/memory_get.md` | Online long-term recall skill backed by `mnemon recall`. | -| `skills/memory_set.md` | Online working-memory update skill backed by `MEMORY.md` edits. | -| `subagents/dreaming.md` | Offline consolidation worker backed by Mnemon writes and `MEMORY.md` compaction. | -| `setup/claude-code/` | First concrete setup implementation. It maps the harness onto Claude Code project or user config. | - -## Runtime Directory Protocol - -All reusable assets resolve their runtime files through one environment -config file and environment variables: - -```text -$MNEMON_MEMORY_LOOP_DIR/ -├── env.sh -├── GUIDE.md -└── MEMORY.md -``` - -`env.sh` defines: - -```bash -MNEMON_MEMORY_LOOP_ENV=/mnemon-memory-loop/env.sh -MNEMON_MEMORY_LOOP_DIR=/mnemon-memory-loop -MNEMON_MEMORY_LOOP_MAX_NON_EMPTY_LINES=200 -``` - -`memory_set.md`, `memory_get.md`, and `dreaming.md` should never hard-code a -Claude Code path. They should use `$MNEMON_MEMORY_LOOP_DIR` when it is available. -If the host runtime cannot pass environment variables to skills, the Prime hook -must inject the resolved path into the HostAgent context. - -`MNEMON_MEMORY_LOOP_MAX_NON_EMPTY_LINES` controls when hooks should suggest -`mnemon-dreaming` for an oversized `MEMORY.md`. - -## Boundary - -The harness does not provide a custom agent runtime. It provides Markdown -materials that a HostAgent can mount into its existing instruction, hook, skill, -and subagent systems. - -The key split is: - -```text -GUIDE.md decides when memory behavior is useful. -memory_get.md maps read-memory behavior to Mnemon recall. -memory_set.md maps write-memory behavior to MEMORY.md edits. -dreaming.md maps maintenance behavior to Mnemon write + MEMORY.md compaction. -``` - -## Claude Code Install - -Install into the current project: +This directory is kept so existing install commands continue to work: ```bash bash harness/memory-loop/setup/claude-code/install.sh +bash harness/memory-loop/setup/claude-code/uninstall.sh ``` -Install globally: - -```bash -bash harness/memory-loop/setup/claude-code/install.sh --global -``` - -Remove the installed Claude Code integration while preserving `MEMORY.md`: +New setup entrypoint: ```bash -bash harness/memory-loop/setup/claude-code/uninstall.sh +bash harness/setup/install.sh --host claude-code --module memory-loop +bash harness/setup/uninstall.sh --host claude-code --module memory-loop ``` diff --git a/harness/memory-loop/setup/claude-code/install.sh b/harness/memory-loop/setup/claude-code/install.sh old mode 100644 new mode 100755 index 1505d18..74479fa --- a/harness/memory-loop/setup/claude-code/install.sh +++ b/harness/memory-loop/setup/claude-code/install.sh @@ -1,150 +1,5 @@ #!/usr/bin/env bash set -euo pipefail -usage() { - cat <<'USAGE' -Install the Mnemon memory loop harness into Claude Code. - -Usage: - install.sh [--global] [--config-dir DIR] [--store NAME] - [--no-remind] [--no-nudge] [--no-compact] - -Defaults: - --config-dir .claude - installs all four hooks: Prime, Remind, Nudge, Compact - -Examples: - bash harness/memory-loop/setup/claude-code/install.sh - bash harness/memory-loop/setup/claude-code/install.sh --global - bash harness/memory-loop/setup/claude-code/install.sh --store mnemon -USAGE -} - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -HARNESS_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" - -CONFIG_DIR=".claude" -STORE_NAME="" -ENABLE_REMIND=1 -ENABLE_NUDGE=1 -ENABLE_COMPACT=1 - -while [[ $# -gt 0 ]]; do - case "$1" in - --global) - CONFIG_DIR="${HOME}/.claude" - shift - ;; - --config-dir) - CONFIG_DIR="${2:?missing value for --config-dir}" - shift 2 - ;; - --store) - STORE_NAME="${2:?missing value for --store}" - shift 2 - ;; - --no-remind) - ENABLE_REMIND=0 - shift - ;; - --no-nudge) - ENABLE_NUDGE=0 - shift - ;; - --no-compact) - ENABLE_COMPACT=0 - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "unknown argument: $1" >&2 - usage >&2 - exit 2 - ;; - esac -done - -if ! command -v python3 >/dev/null 2>&1; then - echo "python3 is required to update Claude Code settings.json" >&2 - exit 1 -fi - -if ! command -v mnemon >/dev/null 2>&1; then - echo "mnemon binary not found in PATH. Install it first, for example:" >&2 - echo " brew install mnemon-dev/tap/mnemon" >&2 - exit 1 -fi - -mkdir -p \ - "${CONFIG_DIR}/mnemon-memory-loop" \ - "${CONFIG_DIR}/skills/memory_get" \ - "${CONFIG_DIR}/skills/memory_set" \ - "${CONFIG_DIR}/agents" \ - "${CONFIG_DIR}/hooks/mnemon-memory-loop" - -install_file() { - local src="$1" - local dst="$2" - local mode="$3" - cp "$src" "$dst" - chmod "$mode" "$dst" -} - -install_file "${HARNESS_DIR}/GUIDE.md" "${CONFIG_DIR}/mnemon-memory-loop/GUIDE.md" 0644 -if [[ ! -f "${CONFIG_DIR}/mnemon-memory-loop/env.sh" ]]; then - install_file "${HARNESS_DIR}/env.sh" "${CONFIG_DIR}/mnemon-memory-loop/env.sh" 0755 -fi -if [[ ! -f "${CONFIG_DIR}/mnemon-memory-loop/MEMORY.md" ]]; then - install_file "${HARNESS_DIR}/MEMORY.md" "${CONFIG_DIR}/mnemon-memory-loop/MEMORY.md" 0644 -fi - -install_file "${HARNESS_DIR}/skills/memory_get.md" "${CONFIG_DIR}/skills/memory_get/SKILL.md" 0644 -install_file "${HARNESS_DIR}/skills/memory_set.md" "${CONFIG_DIR}/skills/memory_set/SKILL.md" 0644 -install_file "${HARNESS_DIR}/subagents/dreaming.md" "${CONFIG_DIR}/agents/mnemon-dreaming.md" 0644 - -install_file "${SCRIPT_DIR}/hooks/prime.sh" "${CONFIG_DIR}/hooks/mnemon-memory-loop/prime.sh" 0755 -install_file "${SCRIPT_DIR}/hooks/remind.sh" "${CONFIG_DIR}/hooks/mnemon-memory-loop/remind.sh" 0755 -install_file "${SCRIPT_DIR}/hooks/nudge.sh" "${CONFIG_DIR}/hooks/mnemon-memory-loop/nudge.sh" 0755 -install_file "${SCRIPT_DIR}/hooks/compact.sh" "${CONFIG_DIR}/hooks/mnemon-memory-loop/compact.sh" 0755 - -python3 "${SCRIPT_DIR}/scripts/update_settings.py" install \ - --config-dir "${CONFIG_DIR}" \ - --remind "${ENABLE_REMIND}" \ - --nudge "${ENABLE_NUDGE}" \ - --compact "${ENABLE_COMPACT}" - -if [[ -n "${STORE_NAME}" ]]; then - if ! mnemon store list 2>/dev/null | sed 's/^[* ]*//' | grep -qx "${STORE_NAME}"; then - mnemon store create "${STORE_NAME}" >/dev/null - fi - mnemon store set "${STORE_NAME}" >/dev/null -fi - -HOOK_SUMMARY="prime" -if [[ "${ENABLE_REMIND}" == "1" ]]; then - HOOK_SUMMARY="${HOOK_SUMMARY}, remind" -fi -if [[ "${ENABLE_NUDGE}" == "1" ]]; then - HOOK_SUMMARY="${HOOK_SUMMARY}, nudge" -fi -if [[ "${ENABLE_COMPACT}" == "1" ]]; then - HOOK_SUMMARY="${HOOK_SUMMARY}, compact" -fi - -cat <&2 - usage >&2 - exit 2 - ;; - esac -done - -if ! command -v python3 >/dev/null 2>&1; then - echo "python3 is required to update Claude Code settings.json" >&2 - exit 1 -fi - -python3 "${SCRIPT_DIR}/scripts/update_settings.py" uninstall --config-dir "${CONFIG_DIR}" - -rm -rf "${CONFIG_DIR}/hooks/mnemon-memory-loop" -rm -rf "${CONFIG_DIR}/skills/memory_get" -rm -rf "${CONFIG_DIR}/skills/memory_set" -rm -f "${CONFIG_DIR}/agents/mnemon-dreaming.md" - -if [[ "${PURGE_MEMORY}" == "1" ]]; then - rm -rf "${CONFIG_DIR}/mnemon-memory-loop" -else - rm -f "${CONFIG_DIR}/mnemon-memory-loop/GUIDE.md" - rmdir "${CONFIG_DIR}/mnemon-memory-loop" 2>/dev/null || true -fi - -echo "Removed Mnemon memory loop from ${CONFIG_DIR}." +exec "${SCRIPT_DIR}/../../../setup/uninstall.sh" --host claude-code --module memory-loop "$@" diff --git a/harness/modules/README.md b/harness/modules/README.md new file mode 100644 index 0000000..08edeb3 --- /dev/null +++ b/harness/modules/README.md @@ -0,0 +1,12 @@ +# Mnemon Harness Modules + +This directory contains canonical, host-agnostic loop modules. + +```text +harness/modules/ +├── memory-loop/ +└── skill-loop/ +``` + +Each module follows the Loop Module Standard and declares its assets in +`module.json`. Host-specific projection logic belongs under `harness/hosts/`. diff --git a/harness/memory-loop/GUIDE.md b/harness/modules/memory-loop/GUIDE.md similarity index 100% rename from harness/memory-loop/GUIDE.md rename to harness/modules/memory-loop/GUIDE.md diff --git a/harness/memory-loop/MEMORY.md b/harness/modules/memory-loop/MEMORY.md similarity index 100% rename from harness/memory-loop/MEMORY.md rename to harness/modules/memory-loop/MEMORY.md diff --git a/harness/modules/memory-loop/README.md b/harness/modules/memory-loop/README.md new file mode 100644 index 0000000..570ad8b --- /dev/null +++ b/harness/modules/memory-loop/README.md @@ -0,0 +1,110 @@ +# Mnemon Memory Loop Harness + +This directory is the canonical memory loop module. It is host-agnostic: a +capable host agent can read these Markdown assets, while host adapters project +the loop into concrete runtimes such as Claude Code or Codex. + +## File Tree + +```text +harness/modules/memory-loop/ +├── README.md +├── module.json +├── env.sh +├── GUIDE.md +├── MEMORY.md +├── hooks/ +│ ├── prime.md +│ ├── remind.md +│ ├── nudge.md +│ └── compact.md +├── skills/ +│ ├── memory_get.md +│ └── memory_set.md +├── subagents/ +│ └── dreaming.md +``` + +## Core Parts + +| Part | Role | +| --- | --- | +| HostAgent | The host agent runtime. It owns task execution, model judgment, and native hook/skill/subagent mechanisms. | +| `MEMORY.md` | Prompt-facing working memory. It is loaded at Prime and kept compact. | +| Mnemon | Long-term memory binary and store. It is installed separately and accessed through skill/subagent protocols. | + +## Support Assets + +| Asset | Purpose | +| --- | --- | +| `module.json` | Machine-readable loop manifest for standard lifecycle events, assets, state, and host adapters. | +| `env.sh` | Runtime config: memory directory, env path, and dreaming threshold. | +| `GUIDE.md` | Policy: when to read memory, when to write memory, and what is worth keeping. | +| `hooks/*.md` | Four lifecycle reminders: Prime, Remind, Nudge, and Compact. | +| `skills/memory_get.md` | Online long-term recall skill backed by `mnemon recall`. | +| `skills/memory_set.md` | Online working-memory update skill backed by `MEMORY.md` edits. | +| `subagents/dreaming.md` | Offline consolidation worker backed by Mnemon writes and `MEMORY.md` compaction. | +| Host adapter | Host-specific projection lives outside the module under `harness/hosts//`. | + +## Runtime Directory Protocol + +All reusable assets resolve their runtime files through one environment +config file and environment variables: + +```text +$MNEMON_MEMORY_LOOP_DIR/ +├── env.sh +├── GUIDE.md +└── MEMORY.md +``` + +`env.sh` defines: + +```bash +MNEMON_MEMORY_LOOP_ENV=/harness/memory-loop/env.sh +MNEMON_MEMORY_LOOP_DIR=/harness/memory-loop +MNEMON_MEMORY_LOOP_MAX_NON_EMPTY_LINES=200 +``` + +`memory_set.md`, `memory_get.md`, and `dreaming.md` should never hard-code a +Claude Code path. They should use `$MNEMON_MEMORY_LOOP_DIR` when it is available. +If the host runtime cannot pass environment variables to skills, the Prime hook +must inject the resolved path into the HostAgent context. + +`MNEMON_MEMORY_LOOP_MAX_NON_EMPTY_LINES` controls when hooks should suggest +`mnemon-dreaming` for an oversized `MEMORY.md`. + +## Boundary + +The harness does not provide a custom agent runtime. It provides Markdown +materials that a HostAgent can mount into its existing instruction, hook, skill, +and subagent systems. + +The key split is: + +```text +GUIDE.md decides when memory behavior is useful. +memory_get.md maps read-memory behavior to Mnemon recall. +memory_set.md maps write-memory behavior to MEMORY.md edits. +dreaming.md maps maintenance behavior to Mnemon write + MEMORY.md compaction. +``` + +## Claude Code Install + +Install into the current project: + +```bash +bash harness/setup/install.sh --host claude-code --module memory-loop +``` + +Install globally: + +```bash +bash harness/setup/install.sh --host claude-code --module memory-loop --global +``` + +Remove the installed Claude Code integration while preserving `MEMORY.md`: + +```bash +bash harness/setup/uninstall.sh --host claude-code --module memory-loop +``` diff --git a/harness/memory-loop/env.sh b/harness/modules/memory-loop/env.sh similarity index 100% rename from harness/memory-loop/env.sh rename to harness/modules/memory-loop/env.sh diff --git a/harness/memory-loop/hooks/compact.md b/harness/modules/memory-loop/hooks/compact.md similarity index 100% rename from harness/memory-loop/hooks/compact.md rename to harness/modules/memory-loop/hooks/compact.md diff --git a/harness/memory-loop/hooks/nudge.md b/harness/modules/memory-loop/hooks/nudge.md similarity index 100% rename from harness/memory-loop/hooks/nudge.md rename to harness/modules/memory-loop/hooks/nudge.md diff --git a/harness/memory-loop/hooks/prime.md b/harness/modules/memory-loop/hooks/prime.md similarity index 100% rename from harness/memory-loop/hooks/prime.md rename to harness/modules/memory-loop/hooks/prime.md diff --git a/harness/memory-loop/hooks/remind.md b/harness/modules/memory-loop/hooks/remind.md similarity index 100% rename from harness/memory-loop/hooks/remind.md rename to harness/modules/memory-loop/hooks/remind.md diff --git a/harness/memory-loop/module.json b/harness/modules/memory-loop/module.json similarity index 95% rename from harness/memory-loop/module.json rename to harness/modules/memory-loop/module.json index a5bfcfc..e0c104b 100644 --- a/harness/memory-loop/module.json +++ b/harness/modules/memory-loop/module.json @@ -41,6 +41,6 @@ ] }, "host_adapters": { - "claude-code": "setup/claude-code" + "claude-code": "../../hosts/claude-code" } } diff --git a/harness/memory-loop/skills/memory_get.md b/harness/modules/memory-loop/skills/memory_get.md similarity index 100% rename from harness/memory-loop/skills/memory_get.md rename to harness/modules/memory-loop/skills/memory_get.md diff --git a/harness/memory-loop/skills/memory_set.md b/harness/modules/memory-loop/skills/memory_set.md similarity index 100% rename from harness/memory-loop/skills/memory_set.md rename to harness/modules/memory-loop/skills/memory_set.md diff --git a/harness/memory-loop/subagents/dreaming.md b/harness/modules/memory-loop/subagents/dreaming.md similarity index 100% rename from harness/memory-loop/subagents/dreaming.md rename to harness/modules/memory-loop/subagents/dreaming.md diff --git a/harness/skill-loop/GUIDE.md b/harness/modules/skill-loop/GUIDE.md similarity index 100% rename from harness/skill-loop/GUIDE.md rename to harness/modules/skill-loop/GUIDE.md diff --git a/harness/modules/skill-loop/README.md b/harness/modules/skill-loop/README.md new file mode 100644 index 0000000..afa63e0 --- /dev/null +++ b/harness/modules/skill-loop/README.md @@ -0,0 +1,116 @@ +# Mnemon Skill Loop Harness + +This directory is the canonical skill loop module. It is host-agnostic: a host +agent keeps its native skill runtime, while Mnemon owns the canonical skill +lifecycle state and the evidence used to evolve it. + +## File Tree + +```text +harness/modules/skill-loop/ +├── README.md +├── module.json +├── env.sh +├── GUIDE.md +├── hooks/ +│ ├── prime.md +│ ├── remind.md +│ ├── nudge.md +│ └── compact.md +├── skills/ +│ ├── skill_observe.md +│ ├── skill_curate.md +│ └── skill_manage.md +├── subagents/ +│ └── curator.md +``` + +## Core Parts + +| Part | Role | +| --- | --- | +| HostAgent | Owns the ReAct loop, tool routing, native skill discovery, and subagent execution. | +| Host Skill Surface | The host-native skill directory, such as `.claude/skills`. It is a generated view. | +| Mnemon Skill Library | Canonical skill state under `mnemon-skill-loop/skills/{active,stale,archived}`. | + +## Support Assets + +| Asset | Purpose | +| --- | --- | +| `module.json` | Machine-readable loop manifest for standard lifecycle events, assets, state, and host adapters. | +| `env.sh` | Runtime config: canonical skill library, host skill surface, usage log, and proposal paths. | +| `GUIDE.md` | Policy for evidence, review triggers, lifecycle movement, and proposal-first changes. | +| `hooks/*.md` | Four lifecycle reminders. Prime syncs active skills; Nudge records evidence; Compact may trigger review; Remind is no-op by default. | +| `skills/skill_observe.md` | Online evidence capture protocol. | +| `skills/skill_curate.md` | Protocol for starting a curator review. | +| `skills/skill_manage.md` | Approved lifecycle mutation protocol. | +| `subagents/curator.md` | Background reviewer that proposes create, patch, consolidate, stale, archive, or restore actions. | +| Host adapter | Host-specific projection lives outside the module under `harness/hosts//`. | + +## Runtime Directory Protocol + +Installed runtime files resolve through one environment config: + +```text +$MNEMON_SKILL_LOOP_DIR/ +├── env.sh +├── GUIDE.md +├── skills/ +│ ├── active/ +│ ├── stale/ +│ ├── archived/ +│ └── .usage.jsonl +└── proposals/ +``` + +`env.sh` defines: + +```bash +MNEMON_SKILL_LOOP_ENV=/harness/skill-loop/env.sh +MNEMON_SKILL_LOOP_DIR=/harness/skill-loop +MNEMON_SKILL_LOOP_HOST_SKILLS_DIR=/skills +MNEMON_SKILL_LOOP_ACTIVE_DIR=$MNEMON_SKILL_LOOP_DIR/skills/active +MNEMON_SKILL_LOOP_STALE_DIR=$MNEMON_SKILL_LOOP_DIR/skills/stale +MNEMON_SKILL_LOOP_ARCHIVED_DIR=$MNEMON_SKILL_LOOP_DIR/skills/archived +MNEMON_SKILL_LOOP_USAGE_FILE=$MNEMON_SKILL_LOOP_DIR/skills/.usage.jsonl +MNEMON_SKILL_LOOP_PROPOSALS_DIR=$MNEMON_SKILL_LOOP_DIR/proposals +``` + +Protocol skills should never hard-code a Claude Code path. They should resolve +state from these variables or from the path injected by Prime. + +## Boundary + +The harness does not replace the host skill runtime. It only maintains canonical +skill state and projects `active` skills into the host skill surface at Prime. + +The key split is: + +```text +GUIDE.md decides when skill evolution behavior is useful. +skill_observe.md records evidence only. +curator.md reviews evidence and proposes changes. +skill_manage.md applies approved changes to canonical state. +prime.sh projects active canonical skills into the host skill surface. +``` + +## Claude Code Install + +Install into the current project: + +```bash +bash harness/setup/install.sh --host claude-code --module skill-loop +``` + +Install globally: + +```bash +bash harness/setup/install.sh --host claude-code --module skill-loop --global +``` + +Remove the installed Claude Code integration while preserving the canonical +skill library: + +```bash +bash harness/setup/uninstall.sh --host claude-code --module skill-loop +``` diff --git a/harness/skill-loop/env.sh b/harness/modules/skill-loop/env.sh similarity index 100% rename from harness/skill-loop/env.sh rename to harness/modules/skill-loop/env.sh diff --git a/harness/skill-loop/hooks/compact.md b/harness/modules/skill-loop/hooks/compact.md similarity index 100% rename from harness/skill-loop/hooks/compact.md rename to harness/modules/skill-loop/hooks/compact.md diff --git a/harness/skill-loop/hooks/nudge.md b/harness/modules/skill-loop/hooks/nudge.md similarity index 100% rename from harness/skill-loop/hooks/nudge.md rename to harness/modules/skill-loop/hooks/nudge.md diff --git a/harness/skill-loop/hooks/prime.md b/harness/modules/skill-loop/hooks/prime.md similarity index 100% rename from harness/skill-loop/hooks/prime.md rename to harness/modules/skill-loop/hooks/prime.md diff --git a/harness/skill-loop/hooks/remind.md b/harness/modules/skill-loop/hooks/remind.md similarity index 100% rename from harness/skill-loop/hooks/remind.md rename to harness/modules/skill-loop/hooks/remind.md diff --git a/harness/skill-loop/module.json b/harness/modules/skill-loop/module.json similarity index 95% rename from harness/skill-loop/module.json rename to harness/modules/skill-loop/module.json index 8d97cdc..1b76fa3 100644 --- a/harness/skill-loop/module.json +++ b/harness/modules/skill-loop/module.json @@ -43,6 +43,6 @@ ] }, "host_adapters": { - "claude-code": "setup/claude-code" + "claude-code": "../../hosts/claude-code" } } diff --git a/harness/skill-loop/skills/skill_curate.md b/harness/modules/skill-loop/skills/skill_curate.md similarity index 100% rename from harness/skill-loop/skills/skill_curate.md rename to harness/modules/skill-loop/skills/skill_curate.md diff --git a/harness/skill-loop/skills/skill_manage.md b/harness/modules/skill-loop/skills/skill_manage.md similarity index 100% rename from harness/skill-loop/skills/skill_manage.md rename to harness/modules/skill-loop/skills/skill_manage.md diff --git a/harness/skill-loop/skills/skill_observe.md b/harness/modules/skill-loop/skills/skill_observe.md similarity index 100% rename from harness/skill-loop/skills/skill_observe.md rename to harness/modules/skill-loop/skills/skill_observe.md diff --git a/harness/skill-loop/subagents/curator.md b/harness/modules/skill-loop/subagents/curator.md similarity index 100% rename from harness/skill-loop/subagents/curator.md rename to harness/modules/skill-loop/subagents/curator.md diff --git a/harness/setup/README.md b/harness/setup/README.md new file mode 100644 index 0000000..eb27a10 --- /dev/null +++ b/harness/setup/README.md @@ -0,0 +1,24 @@ +# Mnemon Harness Setup + +This directory contains the shared setup entrypoints for projecting canonical +Mnemon harness modules into host runtimes. + +```text +harness/setup/ +├── install.sh +├── status.sh +├── uninstall.sh +├── lib/ +└── schema/ +``` + +Use the shared entrypoints for new integrations: + +```bash +bash harness/setup/install.sh --host claude-code --module memory-loop +bash harness/setup/status.sh --host claude-code +bash harness/setup/uninstall.sh --host claude-code --module memory-loop +``` + +Host-specific projection logic lives under `harness/hosts//`. Loop assets +live under `harness/modules//`. diff --git a/harness/setup/install.sh b/harness/setup/install.sh new file mode 100755 index 0000000..024b4ff --- /dev/null +++ b/harness/setup/install.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Install Mnemon harness modules into a host runtime. + +Usage: + install.sh --host HOST --module MODULE [--module MODULE ...] [host options] + +Examples: + bash harness/setup/install.sh --host claude-code --module memory-loop + bash harness/setup/install.sh --host claude-code --module skill-loop --global +USAGE +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +HOST="" +MODULES=() +HOST_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --host) + HOST="${2:?missing value for --host}" + shift 2 + ;; + --module) + MODULES+=("${2:?missing value for --module}") + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + HOST_ARGS+=("$1") + shift + ;; + esac +done + +if [[ -z "${HOST}" ]]; then + echo "--host is required" >&2 + usage >&2 + exit 2 +fi +if [[ "${#MODULES[@]}" -eq 0 ]]; then + echo "at least one --module is required" >&2 + usage >&2 + exit 2 +fi + +PROJECTOR="${SCRIPT_DIR}/../hosts/${HOST}/projector.sh" +if [[ ! -x "${PROJECTOR}" ]]; then + echo "unsupported host or missing projector: ${HOST}" >&2 + exit 1 +fi + +for module in "${MODULES[@]}"; do + "${PROJECTOR}" install --module "${module}" "${HOST_ARGS[@]}" +done diff --git a/harness/setup/lib/paths.sh b/harness/setup/lib/paths.sh new file mode 100755 index 0000000..bb7b22a --- /dev/null +++ b/harness/setup/lib/paths.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash +set -euo pipefail + +mnemon_setup_dir() { + cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd +} + +mnemon_harness_dir() { + cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd +} + +mnemon_repo_root() { + cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd +} + +mnemon_module_dir() { + local module="$1" + local harness_dir + harness_dir="$(mnemon_harness_dir)" + printf '%s/modules/%s\n' "${harness_dir}" "${module}" +} + +mnemon_host_dir() { + local host="$1" + local harness_dir + harness_dir="$(mnemon_harness_dir)" + printf '%s/hosts/%s\n' "${harness_dir}" "${host}" +} + +mnemon_project_mnemon_dir() { + printf '%s\n' "${MNEMON_HARNESS_STATE_DIR:-.mnemon}" +} diff --git a/harness/setup/status.sh b/harness/setup/status.sh new file mode 100755 index 0000000..7881b32 --- /dev/null +++ b/harness/setup/status.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Show Mnemon harness projection status for a host runtime. + +Usage: + status.sh --host HOST [--module MODULE ...] [host options] + +Examples: + bash harness/setup/status.sh --host claude-code + bash harness/setup/status.sh --host claude-code --module memory-loop +USAGE +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +HOST="" +MODULES=() +HOST_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --host) + HOST="${2:?missing value for --host}" + shift 2 + ;; + --module) + MODULES+=("${2:?missing value for --module}") + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + HOST_ARGS+=("$1") + shift + ;; + esac +done + +if [[ -z "${HOST}" ]]; then + echo "--host is required" >&2 + usage >&2 + exit 2 +fi +if [[ "${#MODULES[@]}" -eq 0 ]]; then + MODULES=("memory-loop" "skill-loop") +fi + +PROJECTOR="${SCRIPT_DIR}/../hosts/${HOST}/projector.sh" +if [[ ! -x "${PROJECTOR}" ]]; then + echo "unsupported host or missing projector: ${HOST}" >&2 + exit 1 +fi + +for module in "${MODULES[@]}"; do + "${PROJECTOR}" status --module "${module}" "${HOST_ARGS[@]}" +done diff --git a/harness/setup/uninstall.sh b/harness/setup/uninstall.sh new file mode 100755 index 0000000..2922681 --- /dev/null +++ b/harness/setup/uninstall.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Uninstall Mnemon harness module projections from a host runtime. + +Usage: + uninstall.sh --host HOST --module MODULE [--module MODULE ...] [host options] + +Examples: + bash harness/setup/uninstall.sh --host claude-code --module memory-loop + bash harness/setup/uninstall.sh --host claude-code --module skill-loop --global +USAGE +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +HOST="" +MODULES=() +HOST_ARGS=() + +while [[ $# -gt 0 ]]; do + case "$1" in + --host) + HOST="${2:?missing value for --host}" + shift 2 + ;; + --module) + MODULES+=("${2:?missing value for --module}") + shift 2 + ;; + -h|--help) + usage + exit 0 + ;; + *) + HOST_ARGS+=("$1") + shift + ;; + esac +done + +if [[ -z "${HOST}" ]]; then + echo "--host is required" >&2 + usage >&2 + exit 2 +fi +if [[ "${#MODULES[@]}" -eq 0 ]]; then + echo "at least one --module is required" >&2 + usage >&2 + exit 2 +fi + +PROJECTOR="${SCRIPT_DIR}/../hosts/${HOST}/projector.sh" +if [[ ! -x "${PROJECTOR}" ]]; then + echo "unsupported host or missing projector: ${HOST}" >&2 + exit 1 +fi + +for module in "${MODULES[@]}"; do + "${PROJECTOR}" uninstall --module "${module}" "${HOST_ARGS[@]}" +done diff --git a/harness/skill-loop/README.md b/harness/skill-loop/README.md index c37392e..d092903 100644 --- a/harness/skill-loop/README.md +++ b/harness/skill-loop/README.md @@ -1,127 +1,21 @@ -# Mnemon Skill Loop Harness +# Mnemon Skill Loop Harness Compatibility Path -This directory is the first installable version of the skill loop harness. It is -agent-agnostic: a host agent keeps its native skill runtime, while Mnemon owns -the canonical skill lifecycle state and the evidence used to evolve it. - -## File Tree - -```text -harness/skill-loop/ -├── README.md -├── module.json -├── env.sh -├── GUIDE.md -├── hooks/ -│ ├── prime.md -│ ├── remind.md -│ ├── nudge.md -│ └── compact.md -├── skills/ -│ ├── skill_observe.md -│ ├── skill_curate.md -│ └── skill_manage.md -├── subagents/ -│ └── curator.md -└── setup/ - └── claude-code/ - ├── install.sh - ├── uninstall.sh - ├── hooks/ - │ ├── prime.sh - │ ├── remind.sh - │ ├── nudge.sh - │ └── compact.sh - └── scripts/ - └── update_settings.py -``` - -## Core Parts - -| Part | Role | -| --- | --- | -| HostAgent | Owns the ReAct loop, tool routing, native skill discovery, and subagent execution. | -| Host Skill Surface | The host-native skill directory, such as `.claude/skills`. It is a generated view. | -| Mnemon Skill Library | Canonical skill state under `mnemon-skill-loop/skills/{active,stale,archived}`. | - -## Support Assets - -| Asset | Purpose | -| --- | --- | -| `module.json` | Machine-readable loop manifest for standard lifecycle events, assets, state, and host adapters. | -| `env.sh` | Runtime config: canonical skill library, host skill surface, usage log, and proposal paths. | -| `GUIDE.md` | Policy for evidence, review triggers, lifecycle movement, and proposal-first changes. | -| `hooks/*.md` | Four lifecycle reminders. Prime syncs active skills; Nudge records evidence; Compact may trigger review; Remind is no-op by default. | -| `skills/skill_observe.md` | Online evidence capture protocol. | -| `skills/skill_curate.md` | Protocol for starting a curator review. | -| `skills/skill_manage.md` | Approved lifecycle mutation protocol. | -| `subagents/curator.md` | Background reviewer that proposes create, patch, consolidate, stale, archive, or restore actions. | -| `setup/claude-code/` | First concrete setup implementation for Claude Code. | - -## Runtime Directory Protocol - -Installed runtime files resolve through one environment config: - -```text -$MNEMON_SKILL_LOOP_DIR/ -├── env.sh -├── GUIDE.md -├── skills/ -│ ├── active/ -│ ├── stale/ -│ ├── archived/ -│ └── .usage.jsonl -└── proposals/ -``` - -`env.sh` defines: - -```bash -MNEMON_SKILL_LOOP_ENV=/mnemon-skill-loop/env.sh -MNEMON_SKILL_LOOP_DIR=/mnemon-skill-loop -MNEMON_SKILL_LOOP_HOST_SKILLS_DIR=/skills -MNEMON_SKILL_LOOP_ACTIVE_DIR=$MNEMON_SKILL_LOOP_DIR/skills/active -MNEMON_SKILL_LOOP_STALE_DIR=$MNEMON_SKILL_LOOP_DIR/skills/stale -MNEMON_SKILL_LOOP_ARCHIVED_DIR=$MNEMON_SKILL_LOOP_DIR/skills/archived -MNEMON_SKILL_LOOP_USAGE_FILE=$MNEMON_SKILL_LOOP_DIR/skills/.usage.jsonl -MNEMON_SKILL_LOOP_PROPOSALS_DIR=$MNEMON_SKILL_LOOP_DIR/proposals -``` - -Protocol skills should never hard-code a Claude Code path. They should resolve -state from these variables or from the path injected by Prime. - -## Boundary - -The harness does not replace the host skill runtime. It only maintains canonical -skill state and projects `active` skills into the host skill surface at Prime. - -The key split is: +The canonical Skill Loop module now lives at: ```text -GUIDE.md decides when skill evolution behavior is useful. -skill_observe.md records evidence only. -curator.md reviews evidence and proposes changes. -skill_manage.md applies approved changes to canonical state. -prime.sh projects active canonical skills into the host skill surface. +harness/modules/skill-loop/ ``` -## Claude Code Install - -Install into the current project: +This directory is kept so existing install commands continue to work: ```bash bash harness/skill-loop/setup/claude-code/install.sh +bash harness/skill-loop/setup/claude-code/uninstall.sh ``` -Install globally: - -```bash -bash harness/skill-loop/setup/claude-code/install.sh --global -``` - -Remove the installed Claude Code integration while preserving the canonical -skill library: +New setup entrypoint: ```bash -bash harness/skill-loop/setup/claude-code/uninstall.sh +bash harness/setup/install.sh --host claude-code --module skill-loop +bash harness/setup/uninstall.sh --host claude-code --module skill-loop ``` diff --git a/harness/skill-loop/setup/claude-code/install.sh b/harness/skill-loop/setup/claude-code/install.sh old mode 100644 new mode 100755 index 6d471f1..12fc693 --- a/harness/skill-loop/setup/claude-code/install.sh +++ b/harness/skill-loop/setup/claude-code/install.sh @@ -1,156 +1,5 @@ #!/usr/bin/env bash set -euo pipefail -usage() { - cat <<'USAGE' -Install the Mnemon skill loop harness into Claude Code. - -Usage: - install.sh [--global] [--config-dir DIR] [--host-skills-dir DIR] - [--with-remind] [--no-nudge] [--no-compact] - -Defaults: - --config-dir .claude - --host-skills-dir /skills - installs Prime, Nudge, and Compact hooks; Remind is disabled by default - -Examples: - bash harness/skill-loop/setup/claude-code/install.sh - bash harness/skill-loop/setup/claude-code/install.sh --global - bash harness/skill-loop/setup/claude-code/install.sh --host-skills-dir .claude/skills -USAGE -} - SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -HARNESS_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)" - -CONFIG_DIR=".claude" -HOST_SKILLS_DIR="" -ENABLE_REMIND=0 -ENABLE_NUDGE=1 -ENABLE_COMPACT=1 - -while [[ $# -gt 0 ]]; do - case "$1" in - --global) - CONFIG_DIR="${HOME}/.claude" - shift - ;; - --config-dir) - CONFIG_DIR="${2:?missing value for --config-dir}" - shift 2 - ;; - --host-skills-dir) - HOST_SKILLS_DIR="${2:?missing value for --host-skills-dir}" - shift 2 - ;; - --with-remind) - ENABLE_REMIND=1 - shift - ;; - --no-nudge) - ENABLE_NUDGE=0 - shift - ;; - --no-compact) - ENABLE_COMPACT=0 - shift - ;; - -h|--help) - usage - exit 0 - ;; - *) - echo "unknown argument: $1" >&2 - usage >&2 - exit 2 - ;; - esac -done - -if ! command -v python3 >/dev/null 2>&1; then - echo "python3 is required to update Claude Code settings.json" >&2 - exit 1 -fi - -if [[ -z "${HOST_SKILLS_DIR}" ]]; then - HOST_SKILLS_DIR="${CONFIG_DIR}/skills" -fi - -mkdir -p \ - "${CONFIG_DIR}/mnemon-skill-loop/skills/active" \ - "${CONFIG_DIR}/mnemon-skill-loop/skills/stale" \ - "${CONFIG_DIR}/mnemon-skill-loop/skills/archived" \ - "${CONFIG_DIR}/mnemon-skill-loop/proposals" \ - "${CONFIG_DIR}/mnemon-skill-loop/reports" \ - "${HOST_SKILLS_DIR}/skill_observe" \ - "${HOST_SKILLS_DIR}/skill_curate" \ - "${HOST_SKILLS_DIR}/skill_manage" \ - "${CONFIG_DIR}/agents" \ - "${CONFIG_DIR}/hooks/mnemon-skill-loop" - -install_file() { - local src="$1" - local dst="$2" - local mode="$3" - cp "$src" "$dst" - chmod "$mode" "$dst" -} - -install_file "${HARNESS_DIR}/GUIDE.md" "${CONFIG_DIR}/mnemon-skill-loop/GUIDE.md" 0644 -if [[ ! -f "${CONFIG_DIR}/mnemon-skill-loop/env.sh" ]]; then - install_file "${HARNESS_DIR}/env.sh" "${CONFIG_DIR}/mnemon-skill-loop/env.sh" 0755 -fi - -DEFAULT_HOST_SKILLS_DIR="${CONFIG_DIR}/skills" -if [[ "${HOST_SKILLS_DIR}" != "${DEFAULT_HOST_SKILLS_DIR}" ]]; then - cat > "${CONFIG_DIR}/mnemon-skill-loop/env.local.sh" <&2 - usage >&2 - exit 2 - ;; - esac -done - -if ! command -v python3 >/dev/null 2>&1; then - echo "python3 is required to update Claude Code settings.json" >&2 - exit 1 -fi - -ENV_PATH="${CONFIG_DIR}/mnemon-skill-loop/env.sh" -if [[ -f "${ENV_PATH}" ]]; then - # shellcheck source=/dev/null - source "${ENV_PATH}" -fi -HOST_SKILLS_DIR="${MNEMON_SKILL_LOOP_HOST_SKILLS_DIR:-${CONFIG_DIR}/skills}" - -python3 "${SCRIPT_DIR}/scripts/update_settings.py" uninstall --config-dir "${CONFIG_DIR}" - -if [[ -d "${HOST_SKILLS_DIR}" ]]; then - while IFS= read -r marker; do - rm -rf "$(dirname "${marker}")" - done < <(find "${HOST_SKILLS_DIR}" -mindepth 2 -maxdepth 2 -name .mnemon-skill-loop-generated -print 2>/dev/null) -fi - -rm -rf "${CONFIG_DIR}/hooks/mnemon-skill-loop" -rm -rf "${HOST_SKILLS_DIR}/skill_observe" -rm -rf "${HOST_SKILLS_DIR}/skill_curate" -rm -rf "${HOST_SKILLS_DIR}/skill_manage" -rm -f "${CONFIG_DIR}/agents/mnemon-skill-curator.md" - -if [[ "${PURGE_LIBRARY}" == "1" ]]; then - rm -rf "${CONFIG_DIR}/mnemon-skill-loop" -else - rm -f "${CONFIG_DIR}/mnemon-skill-loop/GUIDE.md" - rm -f "${CONFIG_DIR}/mnemon-skill-loop/env.local.sh" - rmdir "${CONFIG_DIR}/mnemon-skill-loop/reports" 2>/dev/null || true - rmdir "${CONFIG_DIR}/mnemon-skill-loop/proposals" 2>/dev/null || true - rmdir "${CONFIG_DIR}/mnemon-skill-loop" 2>/dev/null || true -fi - -echo "Removed Mnemon skill loop from ${CONFIG_DIR}." +exec "${SCRIPT_DIR}/../../../setup/uninstall.sh" --host claude-code --module skill-loop "$@" diff --git a/scripts/validate_harness_modules.sh b/scripts/validate_harness_modules.sh new file mode 100755 index 0000000..ff73e6c --- /dev/null +++ b/scripts/validate_harness_modules.sh @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +MODULES_DIR="${ROOT_DIR}/harness/modules" + +if ! command -v jq >/dev/null 2>&1; then + echo "jq is required" >&2 + exit 1 +fi + +validate_module() { + local module_dir="$1" + local manifest="${module_dir}/module.json" + local name + + if [[ ! -f "${manifest}" ]]; then + echo "missing module manifest: ${manifest}" >&2 + return 1 + fi + + jq . "${manifest}" >/dev/null + name="$(jq -r '.name // empty' "${manifest}")" + if [[ -z "${name}" ]]; then + echo "module manifest missing name: ${manifest}" >&2 + return 1 + fi + + while IFS= read -r rel; do + [[ -n "${rel}" ]] || continue + if [[ ! -e "${module_dir}/${rel}" ]]; then + echo "missing ${name} asset: ${rel}" >&2 + return 1 + fi + done < <( + jq -r ' + .assets.guide, + .assets.env, + ((.assets.runtime_files // [])[]), + (.assets.hooks[]), + (.assets.skills[]), + (.assets.subagents[]) + ' "${manifest}" + ) + + while IFS= read -r rel; do + [[ -n "${rel}" ]] || continue + if [[ ! -e "${module_dir}/${rel}" ]]; then + echo "missing ${name} host adapter path: ${rel}" >&2 + return 1 + fi + done < <(jq -r '.host_adapters[]' "${manifest}") + + echo "ok ${name}" +} + +for module_dir in "${MODULES_DIR}"/*; do + [[ -d "${module_dir}" ]] || continue + validate_module "${module_dir}" +done From 736c483c9a3bef583684b6e92339be263792460a Mon Sep 17 00:00:00 2001 From: Grivn Date: Thu, 14 May 2026 17:35:59 +0000 Subject: [PATCH 4/4] Add Codex app-server harness eval --- Makefile | 5 +- docs/harness/eval/CODEX_APP_SERVER.md | 36 +++ docs/zh/harness/eval/CODEX_APP_SERVER.md | 36 +++ harness/eval/README.md | 44 +++ harness/hosts/README.md | 6 +- harness/hosts/codex/host.json | 18 ++ harness/hosts/codex/projector.sh | 375 +++++++++++++++++++++++ harness/modules/memory-loop/module.json | 3 +- harness/modules/skill-loop/module.json | 3 +- harness/setup/README.md | 1 + scripts/codex_app_server_eval.py | 371 ++++++++++++++++++++++ 11 files changed, 894 insertions(+), 4 deletions(-) create mode 100644 docs/harness/eval/CODEX_APP_SERVER.md create mode 100644 docs/zh/harness/eval/CODEX_APP_SERVER.md create mode 100644 harness/eval/README.md create mode 100644 harness/hosts/codex/host.json create mode 100755 harness/hosts/codex/projector.sh create mode 100755 scripts/codex_app_server_eval.py diff --git a/Makefile b/Makefile index 2e00777..75edd9e 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ ifeq ($(GOBIN),) GOBIN := $(shell go env GOPATH)/bin endif -.PHONY: deps build install uninstall test unit vet harness-validate docker-build docker-run compose-up compose-down compose-dev release-snapshot clean help +.PHONY: deps build install uninstall test unit vet harness-validate codex-app-eval docker-build docker-run compose-up compose-down compose-dev release-snapshot clean help .DEFAULT_GOAL := help @@ -48,6 +48,9 @@ vet: ## Run go vet static analysis harness-validate: ## Validate harness module manifests and declared asset paths bash scripts/validate_harness_modules.sh +codex-app-eval: ## Run real Codex app-server harness smoke eval + python3 scripts/codex_app_server_eval.py + # ── Containers / Deployment ────────────────────────────────────────── docker-build: ## Build runtime Docker image diff --git a/docs/harness/eval/CODEX_APP_SERVER.md b/docs/harness/eval/CODEX_APP_SERVER.md new file mode 100644 index 0000000..fb4ed29 --- /dev/null +++ b/docs/harness/eval/CODEX_APP_SERVER.md @@ -0,0 +1,36 @@ +# Codex App-Server Eval + +This eval mode uses the real Codex app-server rather than a mock server. It +creates an isolated run directory under `.testdata`, projects Mnemon loop +modules into a generated workspace, then starts: + +```bash +codex app-server --listen stdio:// +``` + +The default smoke flow sends JSON-RPC requests for `initialize`, `skills/list`, +and `thread/start`. This verifies that the real Codex app-server can read the +harness-injected `.codex` skills and `.mnemon` state: + +```bash +make codex-app-eval +``` + +To trigger a real Codex turn, opt in explicitly: + +```bash +python3 scripts/codex_app_server_eval.py --agent-turn +``` + +A real turn uses local Codex authentication and may consume model credits. + +Each run writes: + +```text +.testdata/codex-app-eval// +├── workspace/ # isolated project root seen by Codex +├── workspace/.codex/ # Codex host projection +├── .mnemon/ # Mnemon canonical harness state +├── logs/ # app-server stderr +└── reports/ # JSON eval report +``` diff --git a/docs/zh/harness/eval/CODEX_APP_SERVER.md b/docs/zh/harness/eval/CODEX_APP_SERVER.md new file mode 100644 index 0000000..05d094b --- /dev/null +++ b/docs/zh/harness/eval/CODEX_APP_SERVER.md @@ -0,0 +1,36 @@ +# Codex App-Server Eval + +这个 eval 模式使用真实的 Codex app-server,而不是 mock server。它会在 +`.testdata` 下创建一次性的隔离运行目录,把 Mnemon loop module 投影到生成的 +workspace 中,然后启动: + +```bash +codex app-server --listen stdio:// +``` + +默认 smoke 流程会通过 JSON-RPC 调用 `initialize`、`skills/list` 和 +`thread/start`,验证真实 Codex app-server 能读取被 harness 注入的 `.codex` +技能和 `.mnemon` 状态: + +```bash +make codex-app-eval +``` + +如果需要触发真实 Codex turn,可以显式开启: + +```bash +python3 scripts/codex_app_server_eval.py --agent-turn +``` + +真实 turn 会使用本机 Codex 认证,并可能消耗模型额度。 + +每次运行都会生成: + +```text +.testdata/codex-app-eval// +├── workspace/ # Codex 看到的隔离项目目录 +├── workspace/.codex/ # Codex host projection +├── .mnemon/ # Mnemon canonical harness state +├── logs/ # app-server stderr +└── reports/ # JSON eval report +``` diff --git a/harness/eval/README.md b/harness/eval/README.md new file mode 100644 index 0000000..63737fd --- /dev/null +++ b/harness/eval/README.md @@ -0,0 +1,44 @@ +# Mnemon Harness Eval + +This directory documents eval modes for host-wrapped loop testing. + +## Codex App-Server Eval + +The Codex app-server eval uses the real Codex app-server protocol instead of a +mock server. It creates an isolated run directory under `.testdata`, installs +Mnemon loop modules into a generated workspace, starts: + +```bash +codex app-server --listen stdio:// +``` + +Then it sends JSON-RPC requests for `initialize`, `skills/list`, and +`thread/start`. The default path is a smoke check that does not start a model +turn: + +```bash +make codex-app-eval +``` + +To run an actual Codex turn, use: + +```bash +python3 scripts/codex_app_server_eval.py --agent-turn +``` + +The real turn may use the local Codex authentication and consume model credits. +Each run writes a JSON report and app-server stderr log under: + +```text +.testdata/codex-app-eval// +``` + +## Isolation Model + +Each eval run has: + +- `workspace/`: a throwaway project root read by Codex +- `workspace/.codex/`: projected Codex skills +- `.mnemon/`: canonical Mnemon harness state +- `logs/`: app-server logs +- `reports/`: machine-readable eval reports diff --git a/harness/hosts/README.md b/harness/hosts/README.md index 656f9ab..fee983e 100644 --- a/harness/hosts/README.md +++ b/harness/hosts/README.md @@ -5,8 +5,12 @@ Host adapters project canonical loop modules into a concrete runtime surface. ```text harness/hosts/ ├── claude-code/ -└── codex/ # future +└── codex/ ``` Adapters should keep host-specific behavior here. Loop modules should stay host-agnostic under `harness/modules//`. + +The Codex adapter projects protocol skills into repo-local `.codex/skills` and +keeps canonical loop state under `.mnemon/harness/`. This shape lets the +real Codex app-server load the projected skills from an isolated eval workspace. diff --git a/harness/hosts/codex/host.json b/harness/hosts/codex/host.json new file mode 100644 index 0000000..1163289 --- /dev/null +++ b/harness/hosts/codex/host.json @@ -0,0 +1,18 @@ +{ + "schema_version": 1, + "name": "codex", + "display_name": "Codex", + "description": "Projects Mnemon harness modules into Codex repo-local skills and app-server readable state.", + "projection_surfaces": [ + ".codex/skills", + ".codex/mnemon-memory-loop", + ".codex/mnemon-skill-loop", + ".mnemon/hosts/codex/manifest.json" + ], + "supports": { + "skills": true, + "hooks": false, + "subagents": false, + "app_server_eval": true + } +} diff --git a/harness/hosts/codex/projector.sh b/harness/hosts/codex/projector.sh new file mode 100755 index 0000000..ab8ea7e --- /dev/null +++ b/harness/hosts/codex/projector.sh @@ -0,0 +1,375 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'USAGE' +Project Mnemon harness modules into Codex. + +Usage: + projector.sh install --module MODULE [options] + projector.sh status --module MODULE [options] + projector.sh uninstall --module MODULE [options] + +Common options: + --global + --config-dir DIR + +Memory loop install options: + --store NAME + +Skill loop install options: + --host-skills-dir DIR + +Uninstall options: + --purge-memory + --purge-library +USAGE +} + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# shellcheck source=../../setup/lib/paths.sh +source "${SCRIPT_DIR}/../../setup/lib/paths.sh" + +ACTION="${1:-}" +if [[ -z "${ACTION}" ]]; then + usage >&2 + exit 2 +fi +shift + +MODULE="" +CONFIG_DIR=".codex" +CONFIG_DIR_EXPLICIT=0 +GLOBAL=0 +STORE_NAME="" +HOST_SKILLS_DIR="" +PURGE_MEMORY=0 +PURGE_LIBRARY=0 + +while [[ $# -gt 0 ]]; do + case "$1" in + --module) + MODULE="${2:?missing value for --module}" + shift 2 + ;; + --global) + GLOBAL=1 + CONFIG_DIR="${HOME}/.codex" + shift + ;; + --config-dir) + CONFIG_DIR="${2:?missing value for --config-dir}" + CONFIG_DIR_EXPLICIT=1 + shift 2 + ;; + --store) + STORE_NAME="${2:?missing value for --store}" + shift 2 + ;; + --host-skills-dir) + HOST_SKILLS_DIR="${2:?missing value for --host-skills-dir}" + shift 2 + ;; + --purge-memory) + PURGE_MEMORY=1 + shift + ;; + --purge-library) + PURGE_LIBRARY=1 + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + echo "unknown argument: $1" >&2 + usage >&2 + exit 2 + ;; + esac +done + +if [[ -z "${MODULE}" ]]; then + echo "--module is required" >&2 + usage >&2 + exit 2 +fi +if [[ "${MODULE}" != "memory-loop" && "${MODULE}" != "skill-loop" ]]; then + echo "unsupported module for Codex: ${MODULE}" >&2 + exit 1 +fi + +MODULE_DIR="$(mnemon_module_dir "${MODULE}")" +if [[ ! -d "${MODULE_DIR}" ]]; then + echo "module directory not found: ${MODULE_DIR}" >&2 + exit 1 +fi + +if [[ "${GLOBAL}" == "1" && "${CONFIG_DIR_EXPLICIT}" == "0" ]]; then + MNEMON_DIR="${MNEMON_HARNESS_STATE_DIR:-${HOME}/.mnemon}" +else + MNEMON_DIR="${MNEMON_HARNESS_STATE_DIR:-.mnemon}" +fi +CANONICAL_MODULE_DIR="${MNEMON_DIR}/harness/${MODULE}" +HOST_MANIFEST_DIR="${MNEMON_DIR}/hosts/codex" +HOST_MANIFEST="${HOST_MANIFEST_DIR}/manifest.json" + +install_file() { + local src="$1" + local dst="$2" + local mode="$3" + mkdir -p "$(dirname "${dst}")" + cp "${src}" "${dst}" + chmod "${mode}" "${dst}" +} + +ensure_python() { + if ! command -v python3 >/dev/null 2>&1; then + echo "python3 is required" >&2 + exit 1 + fi +} + +ensure_mnemon_binary() { + if ! command -v mnemon >/dev/null 2>&1; then + echo "mnemon binary not found in PATH. Build or install it before running Codex memory-loop evals." >&2 + exit 1 + fi +} + +copy_common_canonical_assets() { + mkdir -p "${CANONICAL_MODULE_DIR}" + install_file "${MODULE_DIR}/GUIDE.md" "${CANONICAL_MODULE_DIR}/GUIDE.md" 0644 + install_file "${MODULE_DIR}/env.sh" "${CANONICAL_MODULE_DIR}/env.sh" 0755 + install_file "${MODULE_DIR}/module.json" "${CANONICAL_MODULE_DIR}/module.json" 0644 +} + +write_host_manifest() { + local projection_path="$1" + local module_version + module_version="$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("version",""))' "${MODULE_DIR}/module.json")" + mkdir -p "${HOST_MANIFEST_DIR}" + MNEMON_HOST_MANIFEST="${HOST_MANIFEST}" \ + MNEMON_HOST_MODULE="${MODULE}" \ + MNEMON_HOST_MODULE_VERSION="${module_version}" \ + MNEMON_HOST_PROJECT_ROOT="$(pwd)" \ + MNEMON_HOST_MNEMON_DIR="${MNEMON_DIR}" \ + MNEMON_HOST_STORE="${STORE_NAME:-default}" \ + MNEMON_HOST_PROJECTION_PATH="${projection_path}" \ + python3 - <<'PY' +import json +import os +from datetime import datetime, timezone +from pathlib import Path + +path = Path(os.environ["MNEMON_HOST_MANIFEST"]) +if path.exists() and path.stat().st_size: + data = json.loads(path.read_text()) +else: + data = {"schema_version": 1, "host": "codex", "loops": {}} + +data["schema_version"] = 1 +data["host"] = "codex" +data["updated_at"] = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") +data["project_root"] = os.environ["MNEMON_HOST_PROJECT_ROOT"] +data["mnemon_dir"] = os.environ["MNEMON_HOST_MNEMON_DIR"] +data["store"] = os.environ["MNEMON_HOST_STORE"] +data.setdefault("loops", {})[os.environ["MNEMON_HOST_MODULE"]] = { + "module_path": f"{os.environ['MNEMON_HOST_MNEMON_DIR']}/harness/{os.environ['MNEMON_HOST_MODULE']}", + "module_version": os.environ["MNEMON_HOST_MODULE_VERSION"], + "projection_path": os.environ["MNEMON_HOST_PROJECTION_PATH"], + "lifecycle_mapping": { + "prime": "thread/start developer instructions", + "remind": "user prompt guidance", + "nudge": "turn completion guidance", + "compact": "thread compact guidance", + }, + "surfaces": { + "skills": f"{os.environ['MNEMON_HOST_PROJECTION_PATH']}/skills", + "runtime": f"{os.environ['MNEMON_HOST_PROJECTION_PATH']}/mnemon-{os.environ['MNEMON_HOST_MODULE']}", + }, +} +path.write_text(json.dumps(data, indent=2) + "\n") +PY +} + +remove_host_manifest_module() { + [[ -f "${HOST_MANIFEST}" ]] || return 0 + MNEMON_HOST_MANIFEST="${HOST_MANIFEST}" MNEMON_HOST_MODULE="${MODULE}" python3 - <<'PY' +import json +import os +from pathlib import Path + +path = Path(os.environ["MNEMON_HOST_MANIFEST"]) +data = json.loads(path.read_text()) +loops = data.get("loops") +if isinstance(loops, dict): + loops.pop(os.environ["MNEMON_HOST_MODULE"], None) +if not data.get("loops"): + path.unlink() +else: + path.write_text(json.dumps(data, indent=2) + "\n") +PY +} + +write_runtime_env() { + local runtime_dir="$1" + local env_name="$2" + local loop_dir_var="$3" + mkdir -p "${runtime_dir}" + cat > "${runtime_dir}/env.sh" <> "${skill_path}" </dev/null | sed 's/^[* ]*//' | grep -qx "${STORE_NAME}"; then + mnemon store create "${STORE_NAME}" >/dev/null + fi + mnemon store set "${STORE_NAME}" >/dev/null + fi + + write_host_manifest "${CONFIG_DIR}" + echo "Installed Mnemon memory loop for Codex." + echo "Config: ${CONFIG_DIR}" + echo "State: ${CANONICAL_MODULE_DIR}" +} + +install_skill_loop() { + ensure_python + [[ -n "${HOST_SKILLS_DIR}" ]] || HOST_SKILLS_DIR="${CONFIG_DIR}/skills" + copy_common_canonical_assets + mkdir -p \ + "${CANONICAL_MODULE_DIR}/skills/active" \ + "${CANONICAL_MODULE_DIR}/skills/stale" \ + "${CANONICAL_MODULE_DIR}/skills/archived" \ + "${CANONICAL_MODULE_DIR}/proposals" \ + "${CANONICAL_MODULE_DIR}/reports" \ + "${HOST_SKILLS_DIR}/skill_observe" \ + "${HOST_SKILLS_DIR}/skill_curate" \ + "${HOST_SKILLS_DIR}/skill_manage" \ + "${CONFIG_DIR}/mnemon-skill-loop" + write_runtime_env "${CONFIG_DIR}/mnemon-skill-loop" "MNEMON_SKILL_LOOP_ENV" "MNEMON_SKILL_LOOP_DIR" + cat >> "${CONFIG_DIR}/mnemon-skill-loop/env.sh" </dev/null || true + fi + remove_host_manifest_module + echo "Removed Mnemon memory loop from ${CONFIG_DIR}." +} + +uninstall_skill_loop() { + local env_path="${CONFIG_DIR}/mnemon-skill-loop/env.sh" + if [[ -f "${env_path}" ]]; then + # shellcheck source=/dev/null + source "${env_path}" + fi + local host_skills_dir="${MNEMON_SKILL_LOOP_HOST_SKILLS_DIR:-${HOST_SKILLS_DIR:-${CONFIG_DIR}/skills}}" + rm -rf "${host_skills_dir}/skill_observe" + rm -rf "${host_skills_dir}/skill_curate" + rm -rf "${host_skills_dir}/skill_manage" + rm -rf "${CONFIG_DIR}/mnemon-skill-loop" + if [[ "${PURGE_LIBRARY}" == "1" ]]; then + rm -rf "${CANONICAL_MODULE_DIR}" + else + rm -f "${CANONICAL_MODULE_DIR}/GUIDE.md" "${CANONICAL_MODULE_DIR}/env.sh" "${CANONICAL_MODULE_DIR}/module.json" + rmdir "${CANONICAL_MODULE_DIR}/reports" 2>/dev/null || true + rmdir "${CANONICAL_MODULE_DIR}/proposals" 2>/dev/null || true + rmdir "${CANONICAL_MODULE_DIR}" 2>/dev/null || true + fi + remove_host_manifest_module + echo "Removed Mnemon skill loop from ${CONFIG_DIR}." +} + +case "${ACTION}:${MODULE}" in + install:memory-loop) install_memory_loop ;; + install:skill-loop) install_skill_loop ;; + status:memory-loop|status:skill-loop) status_module ;; + uninstall:memory-loop) uninstall_memory_loop ;; + uninstall:skill-loop) uninstall_skill_loop ;; + *) + echo "unsupported action/module: ${ACTION}/${MODULE}" >&2 + exit 1 + ;; +esac diff --git a/harness/modules/memory-loop/module.json b/harness/modules/memory-loop/module.json index e0c104b..e7f9698 100644 --- a/harness/modules/memory-loop/module.json +++ b/harness/modules/memory-loop/module.json @@ -41,6 +41,7 @@ ] }, "host_adapters": { - "claude-code": "../../hosts/claude-code" + "claude-code": "../../hosts/claude-code", + "codex": "../../hosts/codex" } } diff --git a/harness/modules/skill-loop/module.json b/harness/modules/skill-loop/module.json index 1b76fa3..31141f9 100644 --- a/harness/modules/skill-loop/module.json +++ b/harness/modules/skill-loop/module.json @@ -43,6 +43,7 @@ ] }, "host_adapters": { - "claude-code": "../../hosts/claude-code" + "claude-code": "../../hosts/claude-code", + "codex": "../../hosts/codex" } } diff --git a/harness/setup/README.md b/harness/setup/README.md index eb27a10..59cbefb 100644 --- a/harness/setup/README.md +++ b/harness/setup/README.md @@ -18,6 +18,7 @@ Use the shared entrypoints for new integrations: bash harness/setup/install.sh --host claude-code --module memory-loop bash harness/setup/status.sh --host claude-code bash harness/setup/uninstall.sh --host claude-code --module memory-loop +bash harness/setup/install.sh --host codex --module memory-loop ``` Host-specific projection logic lives under `harness/hosts//`. Loop assets diff --git a/scripts/codex_app_server_eval.py b/scripts/codex_app_server_eval.py new file mode 100755 index 0000000..8fed386 --- /dev/null +++ b/scripts/codex_app_server_eval.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python3 +"""Run Mnemon harness checks against the real Codex app-server.""" + +from __future__ import annotations + +import argparse +import json +import os +import queue +import shutil +import subprocess +import sys +import threading +import time +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +class JsonRpcError(RuntimeError): + pass + + +class CodexAppServer: + def __init__(self, env: dict[str, str], cwd: Path, stderr_log: Path) -> None: + self.env = env + self.cwd = cwd + self.stderr_log = stderr_log + self.proc: subprocess.Popen[str] | None = None + self.next_id = 1 + self.responses: dict[int, dict[str, Any]] = {} + self.notifications: list[dict[str, Any]] = [] + self.lines: queue.Queue[str | None] = queue.Queue() + self.reader: threading.Thread | None = None + self.stderr_reader: threading.Thread | None = None + + def start(self) -> None: + self.stderr_log.parent.mkdir(parents=True, exist_ok=True) + err = self.stderr_log.open("w", encoding="utf-8") + self.proc = subprocess.Popen( + ["codex", "app-server", "--listen", "stdio://"], + cwd=self.cwd, + env=self.env, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + bufsize=1, + ) + + def read_stdout() -> None: + assert self.proc is not None and self.proc.stdout is not None + try: + for line in self.proc.stdout: + self.lines.put(line) + finally: + self.lines.put(None) + + def read_stderr() -> None: + assert self.proc is not None and self.proc.stderr is not None + try: + for line in self.proc.stderr: + err.write(line) + err.flush() + finally: + err.close() + + self.reader = threading.Thread(target=read_stdout, daemon=True) + self.stderr_reader = threading.Thread(target=read_stderr, daemon=True) + self.reader.start() + self.stderr_reader.start() + + def close(self) -> None: + if self.proc is None: + return + if self.proc.poll() is None: + self.proc.terminate() + try: + self.proc.wait(timeout=5) + except subprocess.TimeoutExpired: + self.proc.kill() + self.proc.wait(timeout=5) + + def request(self, method: str, params: dict[str, Any] | None = None, timeout: float = 30.0) -> dict[str, Any]: + if self.proc is None or self.proc.stdin is None: + raise JsonRpcError("app-server is not running") + request_id = self.next_id + self.next_id += 1 + payload: dict[str, Any] = {"jsonrpc": "2.0", "id": request_id, "method": method} + if params is not None: + payload["params"] = params + self.proc.stdin.write(json.dumps(payload) + "\n") + self.proc.stdin.flush() + return self._wait_response(request_id, timeout) + + def _wait_response(self, request_id: int, timeout: float) -> dict[str, Any]: + deadline = time.monotonic() + timeout + while time.monotonic() < deadline: + if request_id in self.responses: + response = self.responses.pop(request_id) + if "error" in response: + raise JsonRpcError(json.dumps(response["error"], indent=2)) + return response.get("result", {}) + + remaining = max(0.1, deadline - time.monotonic()) + try: + line = self.lines.get(timeout=min(0.5, remaining)) + except queue.Empty: + if self.proc is not None and self.proc.poll() is not None: + raise JsonRpcError(f"app-server exited with code {self.proc.returncode}") + continue + + if line is None: + raise JsonRpcError("app-server stdout closed") + line = line.strip() + if not line: + continue + try: + message = json.loads(line) + except json.JSONDecodeError as exc: + raise JsonRpcError(f"invalid JSON-RPC line: {line}") from exc + + if "id" in message and message.get("id") is not None: + self.responses[int(message["id"])] = message + else: + self.notifications.append(message) + + raise JsonRpcError(f"timed out waiting for response id {request_id}") + + def wait_notification(self, method: str, timeout: float = 120.0) -> dict[str, Any]: + deadline = time.monotonic() + timeout + start = 0 + while time.monotonic() < deadline: + for item in self.notifications[start:]: + if item.get("method") == method: + return item + start = len(self.notifications) + try: + line = self.lines.get(timeout=0.5) + except queue.Empty: + if self.proc is not None and self.proc.poll() is not None: + raise JsonRpcError(f"app-server exited with code {self.proc.returncode}") + continue + if line is None: + raise JsonRpcError("app-server stdout closed") + line = line.strip() + if not line: + continue + message = json.loads(line) + if "id" in message and message.get("id") is not None: + self.responses[int(message["id"])] = message + else: + self.notifications.append(message) + if message.get("method") == method: + return message + raise JsonRpcError(f"timed out waiting for notification {method}") + + +def repo_root() -> Path: + return Path(__file__).resolve().parents[1] + + +def utc_run_id() -> str: + return datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") + + +def run(cmd: list[str], cwd: Path, env: dict[str, str]) -> None: + subprocess.run(cmd, cwd=cwd, env=env, check=True) + + +def ensure_mnemon_binary(root: Path, run_dir: Path, env: dict[str, str]) -> dict[str, str]: + if shutil.which("mnemon", path=env.get("PATH")): + return env + bin_dir = run_dir / "bin" + bin_dir.mkdir(parents=True, exist_ok=True) + run(["go", "build", "-o", str(bin_dir / "mnemon"), "."], root, env) + next_env = dict(env) + next_env["PATH"] = f"{bin_dir}{os.pathsep}{next_env.get('PATH', '')}" + return next_env + + +def setup_workspace(args: argparse.Namespace, root: Path) -> tuple[Path, Path, Path, dict[str, str]]: + run_root = Path(args.run_root) if args.run_root else root / ".testdata" / "codex-app-eval" / utc_run_id() + workspace = run_root / "workspace" + mnemon_dir = run_root / ".mnemon" + workspace.mkdir(parents=True, exist_ok=True) + mnemon_dir.mkdir(parents=True, exist_ok=True) + + (workspace / "README.md").write_text( + "# Mnemon Codex App-Server Eval Workspace\n\n" + "This workspace is generated by scripts/codex_app_server_eval.py.\n", + encoding="utf-8", + ) + + env = dict(os.environ) + env["MNEMON_HARNESS_STATE_DIR"] = str(mnemon_dir) + if args.isolated_codex_home: + codex_home = run_root / "codex-home" + codex_home.mkdir(parents=True, exist_ok=True) + env["CODEX_HOME"] = str(codex_home) + env = ensure_mnemon_binary(root, run_root, env) + + install = root / "harness" / "setup" / "install.sh" + modules = args.modules + for module in modules: + cmd = ["bash", str(install), "--host", "codex", "--module", module, "--config-dir", str(workspace / ".codex")] + run(cmd, workspace, env) + return run_root, workspace, mnemon_dir, env + + +def collect_skill_names(skills_result: dict[str, Any]) -> set[str]: + names: set[str] = set() + + def walk(value: Any) -> None: + if isinstance(value, dict): + name = value.get("name") + if isinstance(name, str): + names.add(name) + for child in value.values(): + walk(child) + elif isinstance(value, list): + for child in value: + walk(child) + + walk(skills_result) + return names + + +def run_eval(args: argparse.Namespace) -> dict[str, Any]: + root = repo_root() + run_dir, workspace, mnemon_dir, env = setup_workspace(args, root) + report_dir = run_dir / "reports" + report_dir.mkdir(parents=True, exist_ok=True) + logs_dir = run_dir / "logs" + logs_dir.mkdir(parents=True, exist_ok=True) + + server = CodexAppServer(env=env, cwd=workspace, stderr_log=logs_dir / "codex-app-server.stderr.log") + report: dict[str, Any] = { + "schema_version": 1, + "run_dir": str(run_dir), + "workspace": str(workspace), + "mnemon_dir": str(mnemon_dir), + "modules": args.modules, + "agent_turn": args.agent_turn, + "started_at": datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"), + } + + try: + server.start() + initialized = server.request( + "initialize", + {"clientInfo": {"name": "mnemon-codex-app-server-eval", "version": "0.1.0"}}, + timeout=30, + ) + skills = server.request("skills/list", {"cwds": [str(workspace)], "forceReload": True}, timeout=30) + skill_names = collect_skill_names(skills) + expected = set(args.expected_skills) + missing = sorted(expected - skill_names) + if missing: + raise JsonRpcError(f"missing projected Codex skills: {', '.join(missing)}") + + thread = server.request( + "thread/start", + { + "cwd": str(workspace), + "approvalPolicy": "never", + "sandbox": "danger-full-access", + "ephemeral": True, + "developerInstructions": ( + "You are running inside a Mnemon harness eval workspace. " + "Use repo-local Codex skills when they are relevant. " + f"Mnemon state is isolated at {mnemon_dir}." + ), + }, + timeout=30, + ) + thread_id = thread.get("thread", {}).get("id") + if not isinstance(thread_id, str) or not thread_id: + raise JsonRpcError("thread/start did not return a thread id") + + report["initialize"] = initialized + report["skill_names"] = sorted(skill_names) + report["thread_id"] = thread_id + + if args.agent_turn: + server.request( + "turn/start", + { + "threadId": thread_id, + "input": [{"type": "text", "text": args.prompt}], + "cwd": str(workspace), + "approvalPolicy": "never", + "sandboxPolicy": {"type": "dangerFullAccess"}, + }, + timeout=30, + ) + completed = server.wait_notification("turn/completed", timeout=args.turn_timeout) + report["turn_completed"] = completed + + report["status"] = "ok" + return report + except Exception as exc: + report["status"] = "failed" + report["error"] = str(exc) + raise + finally: + server.close() + report["finished_at"] = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") + report_path = report_dir / "codex-app-server-eval.json" + report_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") + print(f"report: {report_path}") + + +def parse_args(argv: list[str]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--run-root", help="Use a specific eval run directory instead of .testdata/codex-app-eval/.") + parser.add_argument( + "--module", + dest="modules", + action="append", + choices=["memory-loop", "skill-loop"], + default=[], + help="Harness module to install. May be repeated. Defaults to memory-loop.", + ) + parser.add_argument( + "--expected-skill", + dest="expected_skills", + action="append", + default=[], + help="Projected Codex skill name that must appear in skills/list. Defaults are derived from selected modules.", + ) + parser.add_argument("--agent-turn", action="store_true", help="Start a real Codex turn after app-server smoke checks.") + parser.add_argument( + "--prompt", + default=( + "In one short sentence, confirm that you can see the Mnemon repo-local skills. " + "Do not modify files." + ), + help="Prompt used with --agent-turn.", + ) + parser.add_argument("--turn-timeout", type=float, default=180.0, help="Seconds to wait for turn/completed.") + parser.add_argument( + "--isolated-codex-home", + action="store_true", + help="Set CODEX_HOME inside the eval run directory. This is suitable for smoke checks and may not have auth for real turns.", + ) + args = parser.parse_args(argv) + if not args.modules: + args.modules = ["memory-loop"] + if not args.expected_skills: + expected: list[str] = [] + if "memory-loop" in args.modules: + expected.extend(["memory_get", "memory_set"]) + if "skill-loop" in args.modules: + expected.extend(["skill_observe", "skill_curate", "skill_manage"]) + args.expected_skills = expected + return args + + +def main(argv: list[str]) -> int: + try: + report = run_eval(parse_args(argv)) + except Exception as exc: + print(f"codex app-server eval failed: {exc}", file=sys.stderr) + return 1 + print(json.dumps({"status": report["status"], "run_dir": report["run_dir"]}, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main(sys.argv[1:]))