Build Layered Soul template and helper tooling (Sam & Codex)

Merges the public Forgejo seed repo with the prepared Layered Soul skeleton, expands the README, adds validation/rendering/private-source planning helper tooling, and documents how private hermes-soul connects without copying private runtime state.\n\nChecks: prettier on markdown; python3 -m json.tool manifests; python3 scripts/layered_soul.py validate .; render-prompt smoke; plan-private-source smoke against /home/clawdie/ai/hermes-soul; git diff --check
This commit is contained in:
Sam & Claude 2026-06-13 21:49:43 +02:00
commit 8f2db52336
7 changed files with 442 additions and 4 deletions

View file

@ -4,4 +4,6 @@
- Do not import raw sessions into another harness by default.
- Curate memories before adding them under `memories/curated/`.
- Keep Hermes-native runtime configuration in `hermes-soul`; this repository is the cross-harness contract.
- Public examples may reference private source repositories by URL/name, but must not quote or copy their private contents.
- Use `scripts/layered_soul.py validate .` before committing structural changes.
- When adapting for Colibri, reviewed skills map to `system_skills`, curated memory maps to `system_brain`, and converted operational tasks map to `system_ops`.

9
LICENSE Normal file
View file

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2026 clawdie
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

103
README.md
View file

@ -1,10 +1,31 @@
# Layered Soul
Layered Soul is the clean cross-harness soul repository for Clawdie agents.
Layered Soul is a public template for harness-agnostic agent identity and context bundles.
It carries durable identity, reviewed user context, and approved skills in a format that Pi, Hermes, Colibri, Codex, Claude Code, Zot, and future harnesses can adapt without copying a whole runtime backup.
It is designed for Clawdie-compatible agents that may run through Pi, Hermes,
Colibri, Codex, Claude Code, Zot, or future harnesses. The repository carries
durable identity, reviewed user context, curated memories, and approved skills
without copying a full runtime backup.
`hermes-soul` can continue to exist as the Hermes-native backup from Debby Linux. This repo is the shared source contract.
## Why this exists
Agent runtimes already keep useful state, but their native backups are not safe
or portable as-is. For example, a private `hermes-soul` repository may contain
Hermes sessions, sanitized config, cron state, scripts, and memory files from a
Debby Linux host. That is useful for Hermes restore, but it should not be pushed
publicly or blindly imported into other harnesses.
`layered-soul` is the clean public layer:
```text
private runtime backup reviewed export portable public/private soul
---------------------- --------------- ----------------------------
hermes-soul/ ─────► review queue ─────► layered-soul/
sessions/ no raw sessions SOUL.md
config.yaml no secrets USER.md
memories/*.md curated summaries IDENTITY.md
skills/**/*.md selected skills AGENTS.md
```
## Core files
@ -19,11 +40,85 @@ It carries durable identity, reviewed user context, and approved skills in a for
- `skills/` — reviewed reusable procedures that can seed `system_skills` or harness-native skill directories
- `memories/curated/` — reviewed memory summaries that can seed `system_brain`
- `adapters/` — notes for materializing the same soul into specific harnesses
- `examples/private-sources/` — safe example configs for connecting private runtime backups
- `scripts/layered_soul.py` — stdlib helper for validation, prompt rendering, and private-source planning
## Rules
- No secrets.
- No raw chat logs by default.
- No harness lock files or runtime caches.
- Raw Hermes sessions stay in `hermes-soul` unless the operator requests summarization.
- Runtime backups such as `hermes-soul` stay private unless the operator deliberately creates a sanitized export.
- Durable memory returns to the Layered Memory Fabric.
## Quick start
Validate the repository:
```sh
python3 scripts/layered_soul.py validate .
```
Render a compact prompt bundle for a task harness:
```sh
python3 scripts/layered_soul.py render-prompt . --output /tmp/layered-soul-prompt.md
```
Inspect a private Hermes backup without copying private content:
```sh
python3 scripts/layered_soul.py plan-private-source \
examples/private-sources/hermes-soul.example.json \
--source-root ~/hermes-soul
```
The planner reports what could be reviewed/exported and what must remain private.
It does not copy raw sessions or secrets.
## Connecting `hermes-soul` properly
Use `hermes-soul` as the private Hermes-native backup and `layered-soul` as the
portable contract.
Recommended flow:
1. Back up Hermes into the private `hermes-soul` repo from Debby Linux.
2. Run the private-source planner against that checkout.
3. Review candidate memories/skills manually.
4. Copy only sanitized summaries or selected skills into this repo.
5. Validate before committing.
Example private-source config:
```json
{
"name": "hermes-soul-private",
"kind": "hermes-soul",
"repo": "git@code.smilepowered.org:clawdie/hermes-soul.git",
"visibility": "private",
"export_policy": {
"raw_sessions": "exclude",
"runtime_config": "exclude",
"secrets": "exclude",
"memories": "review-required",
"skills": "review-required"
}
}
```
See `docs/CONNECT-HERMES-SOUL.md` for the longer playbook.
## Template usage
This repository can be used as a Forgejo template. Create concrete soul repos
from it when you want a separate identity bundle for a person, agent, project,
or customer environment.
Suggested split:
```text
clawdie/layered-soul public template/schema
clawdie/hermes-soul private Hermes runtime backup
clawdie/<agent>-layered-soul curated identity instance when needed
```

View file

@ -0,0 +1,61 @@
# Connecting private `hermes-soul` to Layered Soul
`hermes-soul` and `layered-soul` have different jobs.
| Repo | Visibility | Purpose |
| -------------- | ----------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `hermes-soul` | private | Hermes-native runtime backup from Debby Linux: sessions, sanitized config, scripts, cron, memories, skills. |
| `layered-soul` | public/template or curated instance | Harness-agnostic identity and reviewed context with no secrets or raw sessions. |
Do not mirror `hermes-soul` into `layered-soul`. Use a review/export workflow.
## Recommended workflow
1. Back up Hermes normally into the private `hermes-soul` repo.
2. Run the planner from this repo:
```sh
python3 scripts/layered_soul.py plan-private-source \
examples/private-sources/hermes-soul.example.json \
--source-root ~/hermes-soul
```
3. Review the candidate memory and skill files locally.
4. Write sanitized summaries into this repo:
```text
USER.md
memories/curated/hermes-memory-summary.md
skills/<selected-skill>/SKILL.md
```
5. Validate before committing:
```sh
python3 scripts/layered_soul.py validate .
```
## What may move across
- Reviewed user context from `memories/USER.md`.
- Reviewed memory summaries from `memories/MEMORY.md`.
- Selected `skills/**/*.md` that are safe and useful across harnesses.
- Converted cron/task ideas after they are rewritten as explicit Ops manifests.
## What must stay private
- Raw `sessions/` chat logs.
- `config.yaml` and platform runtime config.
- `channel_directory.json`.
- Tokens, API keys, auth files, browser profiles, lock files, caches, bytecode.
## Example source config
See:
```text
examples/private-sources/hermes-soul.example.json
```
The config is safe to publish because it describes the boundary, not the private
content.

View file

@ -0,0 +1,24 @@
{
"name": "hermes-soul-private",
"kind": "hermes-soul",
"repo": "git@code.smilepowered.org:clawdie/hermes-soul.git",
"visibility": "private",
"local_path": "~/hermes-soul",
"export_policy": {
"raw_sessions": "exclude",
"runtime_config": "exclude",
"channel_directory": "exclude",
"cron_jobs": "review-and-convert-to-ops-manifest",
"secrets": "exclude",
"memories": "review-required",
"skills": "review-required",
"scripts": "review-required"
},
"candidate_mappings": {
"memories/USER.md": "review queue -> USER.md or memories/curated/hermes-user-summary.md",
"memories/MEMORY.md": "review queue -> memories/curated/hermes-memory-summary.md",
"skills/**/*.md": "review queue -> skills/ or Colibri system_skills import",
"cron/*": "review queue -> system_ops task manifest"
},
"never_copy": ["sessions/*", "config.yaml", "channel_directory.json", "*.lock", "**/__pycache__/**", "**/*.pyc"]
}

View file

@ -4,6 +4,7 @@
"display_name": "Layered Soul",
"description": "Cross-harness durable identity and reviewed context for Clawdie-compatible agents.",
"source_of_truth": "git",
"template": true,
"core_files": {
"soul": "SOUL.md",
"user": "USER.md",
@ -14,6 +15,10 @@
"skills": ["skills/**/*.md"],
"curated_memory": ["memories/curated/**/*.md"]
},
"tooling": {
"helper": "scripts/layered_soul.py",
"private_source_examples": ["examples/private-sources/hermes-soul.example.json"]
},
"archive_policy": {
"raw_sessions": "excluded-by-default",
"runtime_config": "adapter-local",

242
scripts/layered_soul.py Executable file
View file

@ -0,0 +1,242 @@
#!/usr/bin/env python3
"""Layered Soul helper.
Stdlib-only utilities for validating a Layered Soul repository, rendering a
prompt bundle for task harnesses, and planning safe exports from private runtime
backups such as hermes-soul.
"""
from __future__ import annotations
import argparse
import fnmatch
import json
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any
FORBIDDEN_PATH_PATTERNS = [
".env",
"*.key",
"*.pem",
"*.token",
"config.yaml",
"channel_directory.json",
"sessions/*",
"cron/*",
"**/__pycache__/*",
"**/*.pyc",
"**/*.lock",
]
CORE_FILE_KEYS = ["soul", "user", "identity", "harness_rules"]
@dataclass
class Finding:
level: str
message: str
def load_json(path: Path) -> Any:
try:
return json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
raise SystemExit(f"missing JSON file: {path}")
except json.JSONDecodeError as exc:
raise SystemExit(f"invalid JSON in {path}: {exc}")
def rel_paths(root: Path) -> list[str]:
paths: list[str] = []
for path in root.rglob("*"):
if ".git" in path.parts:
continue
if path.is_file():
paths.append(path.relative_to(root).as_posix())
return sorted(paths)
def matches_any(path: str, patterns: list[str]) -> bool:
return any(fnmatch.fnmatch(path, pattern) for pattern in patterns)
def validate(root: Path) -> list[Finding]:
findings: list[Finding] = []
manifest_path = root / "manifest.json"
if not manifest_path.exists():
return [Finding("error", "manifest.json is required")]
manifest = load_json(manifest_path)
if manifest.get("schema") != "clawdie.layered-soul.v1":
findings.append(
Finding(
"error",
"manifest.schema must be clawdie.layered-soul.v1",
)
)
core_files = manifest.get("core_files", {})
for key in CORE_FILE_KEYS:
value = core_files.get(key)
if not value:
findings.append(Finding("error", f"manifest.core_files.{key} is required"))
continue
path = root / value
if not path.exists():
findings.append(Finding("error", f"core file missing: {value}"))
elif path.stat().st_size == 0:
findings.append(Finding("warn", f"core file is empty: {value}"))
privacy = manifest.get("privacy", {})
if privacy.get("secrets") != "excluded":
findings.append(Finding("warn", "privacy.secrets should be 'excluded'"))
if privacy.get("operator_review_required_before_cross_harness_import") is not True:
findings.append(
Finding(
"warn",
"operator_review_required_before_cross_harness_import should be true",
)
)
for path in rel_paths(root):
if matches_any(path, FORBIDDEN_PATH_PATTERNS):
findings.append(Finding("error", f"forbidden runtime/private path: {path}"))
return findings
def render_prompt(root: Path, include_curated: bool) -> str:
manifest = load_json(root / "manifest.json")
core_files = manifest.get("core_files", {})
sections: list[str] = []
for key in CORE_FILE_KEYS:
rel = core_files.get(key)
if not rel:
continue
path = root / rel
if not path.exists():
continue
content = path.read_text(encoding="utf-8").strip()
if content:
sections.append(f"<!-- {rel} -->\n\n{content}")
if include_curated:
for path in sorted((root / "memories" / "curated").glob("*.md")):
content = path.read_text(encoding="utf-8").strip()
if content:
rel = path.relative_to(root).as_posix()
sections.append(f"<!-- {rel} -->\n\n{content}")
return "\n\n---\n\n".join(sections).strip() + "\n"
def plan_private_source(config_path: Path, source_root: Path | None) -> dict[str, Any]:
config = load_json(config_path)
source = source_root or Path(config.get("local_path", ""))
if not str(source):
raise SystemExit("provide --source-root or local_path in the source config")
source = source.expanduser().resolve()
def count(pattern: str) -> int:
return len([p for p in source.glob(pattern) if p.is_file()])
exists = source.exists()
report: dict[str, Any] = {
"config": str(config_path),
"name": config.get("name"),
"kind": config.get("kind"),
"repo": config.get("repo"),
"visibility": config.get("visibility"),
"source_root": str(source),
"source_exists": exists,
"policy": config.get("export_policy", {}),
"recommended_flow": [
"inspect candidates locally",
"summarize or redact private memory",
"copy only reviewed output into layered-soul",
"run: python3 scripts/layered_soul.py validate .",
],
}
if exists:
report["candidate_counts"] = {
"memory_markdown": count("memories/*.md"),
"skill_markdown": count("skills/**/*.md"),
"session_archives_excluded": count("sessions/*"),
"cron_archives_excluded": count("cron/*"),
}
report["candidate_paths"] = {
"user_memory": "memories/USER.md" if (source / "memories" / "USER.md").exists() else None,
"general_memory": "memories/MEMORY.md" if (source / "memories" / "MEMORY.md").exists() else None,
"skills_root": "skills/" if (source / "skills").exists() else None,
}
return report
def print_findings(findings: list[Finding]) -> int:
errors = 0
for finding in findings:
print(f"{finding.level.upper()}: {finding.message}")
if finding.level == "error":
errors += 1
if not findings:
print("OK: layered soul repository looks valid")
elif errors == 0:
print("OK: no validation errors")
return 1 if errors else 0
def cmd_validate(args: argparse.Namespace) -> int:
return print_findings(validate(Path(args.root).resolve()))
def cmd_render_prompt(args: argparse.Namespace) -> int:
root = Path(args.root).resolve()
rendered = render_prompt(root, include_curated=args.include_curated)
if args.output:
Path(args.output).write_text(rendered, encoding="utf-8")
else:
print(rendered, end="")
return 0
def cmd_plan_private_source(args: argparse.Namespace) -> int:
report = plan_private_source(
Path(args.config).resolve(),
Path(args.source_root).expanduser() if args.source_root else None,
)
print(json.dumps(report, indent=2, sort_keys=True))
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(description="Layered Soul helper")
sub = parser.add_subparsers(dest="command", required=True)
validate_cmd = sub.add_parser("validate", help="validate a Layered Soul repository")
validate_cmd.add_argument("root", nargs="?", default=".")
validate_cmd.set_defaults(func=cmd_validate)
render_cmd = sub.add_parser("render-prompt", help="render core files as a prompt bundle")
render_cmd.add_argument("root", nargs="?", default=".")
render_cmd.add_argument("--include-curated", action="store_true")
render_cmd.add_argument("--output")
render_cmd.set_defaults(func=cmd_render_prompt)
plan_cmd = sub.add_parser("plan-private-source", help="inspect a private source export plan")
plan_cmd.add_argument("config")
plan_cmd.add_argument("--source-root")
plan_cmd.set_defaults(func=cmd_plan_private_source)
return parser
def main(argv: list[str]) -> int:
parser = build_parser()
args = parser.parse_args(argv)
return args.func(args)
if __name__ == "__main__":
raise SystemExit(main(sys.argv[1:]))