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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/skill-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ DNA Memory 将“长期记忆”和“行为 Skill”分开治理:
Hermes 的目标目录必须链接到同一个 `skill_root` 真源。不要复制三份后分别
修改,否则召回边界和隐私限制会随客户端漂移。

`platform_skill_roots` 的键可以使用 `platform:instance`,例如
`hermes:dev`。当注册表目标为 `hermes` 时,`skills sync` 会自动展开到
所有已配置的 `hermes:*` 实例;新增 Hermes profile 后先登记其 Skill 根目录,
再运行 `skills sync --apply`。不需要在注册表中重复列出每个 profile。

Memory Loop 必须传真实客户端元数据:Hermes 使用 `client=hermes`。
只有运行时明确暴露稳定会话 ID 时才传 `session_id`;否则省略,禁止使用
`session_12345` 等占位值。

对依赖历史的实质任务,Memory Loop 是强制前置步骤;“继续之前的工作”、
“按上次方案”、已有项目路径、用户长期偏好、已知错误和开放事项都属于触发
条件。翻译、当前时间、一步格式化等完全自包含任务仍然跳过召回。
Expand Down
11 changes: 10 additions & 1 deletion scripts/skill_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,16 @@ def build_sync_plan(shared_root: Path, platform_roots: Dict[str, Path], registry
plan = []
for name, settings in sorted(registry.get("skills", {}).items()):
source = shared_root / name
for platform in settings.get("targets", []):
platforms = []
for target in settings.get("targets", []):
matches = [
platform for platform in platform_roots
if platform == target or platform.startswith(f"{target}:")
]
for platform in matches or [target]:
if platform not in platforms:
platforms.append(platform)
for platform in platforms:
root = platform_roots.get(platform)
target = (root / name) if root else Path("<unconfigured>") / name
if root is None:
Expand Down
5 changes: 4 additions & 1 deletion skills/dna-memory-loop/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@ names an existing project or path, or depends on a durable preference, known err
workflow, project state, or open loop.

1. Extract one to four distinctive terms from the request, project, error, or expected result.
2. Call `memory_recall` separately for each term. Include the real client and session ID when available.
2. Call `memory_recall` separately for each term. Always pass the real client:
use `client=codex`, `client=claude-code`, or `client=hermes` for those runtimes.
Never invent client or session metadata. Pass the current stable session ID only
when the runtime exposes it; otherwise omit session_id instead of using a placeholder.
3. Deduplicate by memory ID. Inject at most five memories and about 2,000 tokens total.
4. Use only relevant results. Current files, processes, remote state, and tests override stale memory.

Expand Down
9 changes: 9 additions & 0 deletions tests/test_bundled_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,12 @@ def test_memory_loop_requires_recall_for_history_dependent_work():
assert "one to four distinctive terms" in body
assert "simple, self-contained" in body
assert "Memory failure must not block" in body


def test_memory_loop_requires_truthful_client_metadata():
body = (ROOT / "skills/dna-memory-loop/SKILL.md").read_text(encoding="utf-8")

assert "client=hermes" in body
assert "Never invent client or session metadata" in body
assert "omit session_id" in body
assert "placeholder" in body
18 changes: 18 additions & 0 deletions tests/test_skill_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,21 @@ def test_sync_plan_never_overwrites_conflicts(tmp_path):
assert actions[("shared-ok", "codex")] == "ok"
assert actions[("shared-ok", "claude")] == "blocked_conflict"
assert actions[("missing-target", "codex")] == "missing_source"


def test_sync_plan_expands_platform_target_to_named_instances(tmp_path):
shared = tmp_path / "shared"
source = skill(shared, "memory-loop", "---\nname: memory-loop\n---\n")
roots = {
"hermes": tmp_path / "hermes-default",
"hermes:htian": tmp_path / "hermes-htian",
"codex": tmp_path / "codex",
}
registry = {"skills": {"memory-loop": {"targets": ["hermes"]}}}

plan = build_sync_plan(shared, roots, registry)

assert [(item.platform, item.action, item.source) for item in plan] == [
("hermes", "create_link", str(source)),
("hermes:htian", "create_link", str(source)),
]
Loading