diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index db64c24..f810a32 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -4,14 +4,14 @@ "name": "TerminallyLazy" }, "description": "Claude Code plugin marketplace for Tree Ring Memory.", - "version": "0.1.0", + "version": "0.2.0", "plugins": [ { "name": "tree-ring-memory", "source": "./", "displayName": "Tree Ring Memory", "description": "Local-first memory lifecycle guidance for Claude Code using Tree Ring Memory.", - "version": "0.1.0", + "version": "0.2.0", "author": { "name": "TerminallyLazy", "url": "https://github.com/TerminallyLazy" diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 5c0490a..ccb55ea 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "tree-ring-memory", "displayName": "Tree Ring Memory", - "version": "0.1.0", + "version": "0.2.0", "description": "Local-first memory lifecycle guidance for Claude Code using Tree Ring Memory.", "author": { "name": "TerminallyLazy", diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index c62052c..9c1cdff 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -6,24 +6,58 @@ on: branches: - main +permissions: + contents: read + +env: + TREE_RING_VERSION: "0.13.0" + TREE_RING_LINUX_X86_64_SHA256: "cbe4c108c8881b2df1b72a26bfc86396dcdccba66fc8b976f340012e8c095e7d" + jobs: validate: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - - name: Validate JSON + - name: Install checksum-pinned Tree Ring v0.13 + shell: bash run: | - jq -e . .claude-plugin/plugin.json >/dev/null - jq -e . .claude-plugin/marketplace.json >/dev/null + set -euo pipefail + archive_name="tree-ring-memory-${TREE_RING_VERSION}-linux-x86_64.tar.gz" + archive_path="${RUNNER_TEMP}/${archive_name}" + release_url="https://github.com/TerminallyLazy/Tree-Ring-Memory/releases/download/v${TREE_RING_VERSION}/${archive_name}" + extract_root="${RUNNER_TEMP}/tree-ring-${TREE_RING_VERSION}" - - name: Validate plugin shape - run: | - test -f .claude-plugin/plugin.json - test -f .claude-plugin/marketplace.json - test -f skills/tree-ring-memory/SKILL.md - test -f commands/tree-ring-recall.md - test -f commands/tree-ring-capture.md - test -f commands/tree-ring-audit.md - grep -q '"name": "tree-ring-memory"' .claude-plugin/plugin.json - grep -q '"source": "./"' .claude-plugin/marketplace.json + curl \ + --proto '=https' \ + --tlsv1.2 \ + --fail \ + --location \ + --silent \ + --show-error \ + --output "${archive_path}" \ + "${release_url}" + printf '%s %s\n' \ + "${TREE_RING_LINUX_X86_64_SHA256}" \ + "${archive_path}" \ + | sha256sum --check --status + + while IFS= read -r entry; do + case "${entry}" in + /*|../*|*/../*|*/..) exit 1 ;; + esac + done < <(tar -tzf "${archive_path}") + + mkdir -p "${extract_root}" + tar -xzf "${archive_path}" -C "${extract_root}" + binary_path="${extract_root}/tree-ring-memory-${TREE_RING_VERSION}-linux-x86_64/tree-ring" + test -x "${binary_path}" + test "$("${binary_path}" --version)" = "tree-ring ${TREE_RING_VERSION}" + printf 'TREE_RING_BIN=%s\n' "${binary_path}" >> "${GITHUB_ENV}" + + - name: Validate plugin guidance + run: python3 scripts/validate.py + + - name: Smoke Tree Ring v0.13 integration + shell: bash + run: bash scripts/smoke_v013.sh diff --git a/.gitignore b/.gitignore index 78d3a83..7a3fd52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .DS_Store +__pycache__/ +*.py[cod] *.log tmp/ dist/ diff --git a/README.md b/README.md index 769efe4..37eebe1 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ action is useful, source-linked, and privacy-safe. - Evidence-backed outcomes through `tree-ring evidence`. - Explicit forgetting, redaction, and supersession guidance. - DOX and Revolve adapter usage with dry-run-first guardrails. +- Same-host multi-agent identity, idempotency, filtered fan-in, and optional + coordinator write-policy guidance. ## Install Tree Ring Memory @@ -30,6 +32,18 @@ brew tap TerminallyLazy/tree-ring brew install tree-ring ``` +The multi-agent and coordinator guidance requires Tree Ring Memory v0.13.0 or +newer: + +```bash +tree-ring --version +tree-ring policy --help +``` + +Before a v0.13 binary upgrades an existing store to schema v3, stop every +Tree Ring process, checkpoint and back up the complete store, and upgrade every +CLI, plugin, and bundled worker. Mixed v0.12/v0.13 operation is unsupported. + For other install paths, use the canonical project README: @@ -65,6 +79,11 @@ The skill looks for project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` first. If they are absent, it falls back to the public CLI commands documented in the main framework repository. +For fan-out/fan-in, the supported shared-root boundary is cooperative Tree Ring +processes on one host and a local filesystem. Identity and scope route memory; +they are not read access-control boundaries. Cross-host and network-filesystem +workflows need per-host stores plus explicit, source-preserving fan-in. + ## Canonical Project - Framework repo: @@ -76,4 +95,8 @@ in the main framework repository. This plugin ships instructions only. It does not include remote MCP servers, webhooks, analytics, credentials, or networked runtime code. +Coordinated mode uses a one-time capability only through +`TREE_RING_COORDINATOR_TOKEN`. Keep it out of prompts, command arguments, +memory, logs, source references, and ordinary worker environments. + See [SECURITY.md](SECURITY.md) for disclosure and privacy guidance. diff --git a/SECURITY.md b/SECURITY.md index fba83b0..32ad56e 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -22,6 +22,19 @@ details without explicit user instruction. Use `tree-ring audit`, `tree-ring forget`, redaction, and supersession when memory is wrong, sensitive, stale, or replaced by newer evidence. +## Coordinated Mode + +Tree Ring Memory v0.13 Coordinated mode uses a one-time capability supplied +only through `TREE_RING_COORDINATOR_TOKEN`. Never place its value in a prompt, +CLI argument, memory event, log, source reference, committed file, or ordinary +worker environment. Tree Ring stores only a hash of the capability. + +The policy is operational write authorization in official Rust and CLI paths. +It is not a read ACL, an operating-system security boundary, or protection +against an adversary who controls the database files or process environment. +The supported shared-root boundary is cooperative processes on one host using a +local filesystem; it does not establish cross-host or network-filesystem safety. + ## Reporting A Vulnerability Open a private vulnerability report on the canonical framework repository: diff --git a/commands/tree-ring-audit.md b/commands/tree-ring-audit.md index b668e91..240b8ac 100644 --- a/commands/tree-ring-audit.md +++ b/commands/tree-ring-audit.md @@ -9,12 +9,24 @@ allowed-tools: ["Bash"] Audit memory when work is closing, when privacy may matter, or when older entries may be stale. -Start with a non-destructive audit: +Before any v0.13 command other than `policy status` or `policy audit` opens an +existing pre-v0.13 store, stop every Tree Ring process, checkpoint and back up +the complete store, and upgrade every CLI, plugin, and bundled worker. +Mixed-version operation is unsupported. + +These policy preflight commands never create or migrate a store: + +```bash +tree-ring policy status +tree-ring policy audit --limit 100 +``` + +After the verified schema-v3 upgrade, start with non-mutating inspection: ```bash tree-ring audit --audit-type sensitive tree-ring consolidate --period-type manual --dry-run -tree-ring maintain --repair-fts +tree-ring maintain ``` If the user names a focus, use it to narrow recall before changing memory: @@ -27,8 +39,21 @@ Use explicit forget operations only when justified: ```bash tree-ring forget mem_example --mode redact --reason "remove sensitive detail" -tree-ring forget mem_example --mode supersede --reason "newer decision replaced this" tree-ring forget mem_example --mode delete --reason "should not be retained" ``` -Do not delete, redact, or supersede memory without a clear reason. +The CLI forget modes are `redact` and `delete`. For an explicit supersession, +use the TUI's `/supersede ` lifecycle action. + +In Coordinated mode, forget/redact, supersede, persisted consolidation, and +applied maintenance such as `--repair-fts` require +`TREE_RING_COORDINATOR_TOKEN` in the coordinator process environment. Never put +the capability value in a prompt, CLI argument, memory, log, source reference, +or committed file. `policy status`, `policy audit`, ordinary audit, +consolidation dry-run, and plain `maintain` do not change memory content on an +already-upgraded schema-v3 store. Only the two policy commands are guaranteed +not to create or migrate a store. + +Do not delete, redact, or supersede memory without a clear reason. The +coordinator policy is not a read ACL, and its shared-root support is bounded to +cooperative processes on one host using a local filesystem. diff --git a/commands/tree-ring-capture.md b/commands/tree-ring-capture.md index 773902f..567429b 100644 --- a/commands/tree-ring-capture.md +++ b/commands/tree-ring-capture.md @@ -9,7 +9,12 @@ allowed-tools: ["Bash"] Capture only durable, useful memory. Do not store transcripts, secrets, credentials, raw chain-of-thought, or unverified claims as truth. -Use the user's argument as the memory summary: +Read project-local `.tree-ring/SKILL.md` and `.tree-ring/CLI.md` first when +present. Verify `tree-ring --version`; the coordination fields and policy +commands require v0.13.0 or newer. + +For a single agent or a store in Open mode, use the user's argument as the +memory summary: ```bash tree-ring remember "$ARGUMENTS" \ @@ -17,8 +22,26 @@ tree-ring remember "$ARGUMENTS" \ --scope project ``` -Adjust `--event-type`, `--project`, and `--tag` values to match the actual -memory: +For an ordinary worker in Coordinated mode, use agent scope with the +server/task-derived identity and stable retry metadata: + +```bash +tree-ring remember "$ARGUMENTS" \ + --event-type lesson \ + --scope agent \ + --agent-profile "$TREE_RING_AGENT_PROFILE" \ + --workflow-id "$TREE_RING_WORKFLOW_ID" \ + --session-id "$TREE_RING_SESSION_ID" \ + --operation-id "" \ + --source-ref "" +``` + +Do not invent identity values. Reuse the same session and operation IDs only +for an exact retry; conflicting reuse fails closed. Keep +`TREE_RING_COORDINATOR_TOKEN` unset in ordinary worker environments. + +Adjust `--event-type`, scope, project, identity, source, and tags to match the +actual memory: - `decision` for selected architecture, release, storage, or policy choices - `lesson` for validated work patterns @@ -28,3 +51,11 @@ memory: If the memory comes from a run, test, evaluation, incident, PR, or checkpoint, prefer `tree-ring evidence` with an `--evidence-ref` and outcome. + +In Coordinated mode, project/shared writes, `tree-ring evidence`, heartwood, +and lifecycle mutations require the one-time coordinator capability already +present only in the coordinator process environment. Never put its value in the +prompt, a CLI argument, memory, logs, source references, or committed files. + +The shared-root concurrency contract covers cooperative processes on one host +and a local filesystem. Identity and scope are routing fields, not read ACLs. diff --git a/commands/tree-ring-recall.md b/commands/tree-ring-recall.md index ed5607d..ed9f4ab 100644 --- a/commands/tree-ring-recall.md +++ b/commands/tree-ring-recall.md @@ -11,8 +11,8 @@ Recall useful project memory before acting on context-dependent work. 1. Check for project-local guidance first: ```bash - test -f .tree-ring/SKILL.md && sed -n '1,220p' .tree-ring/SKILL.md - test -f .tree-ring/CLI.md && sed -n '1,220p' .tree-ring/CLI.md + test -f .tree-ring/SKILL.md && sed -n '1,520p' .tree-ring/SKILL.md + test -f .tree-ring/CLI.md && sed -n '1,520p' .tree-ring/CLI.md ``` 2. Use the user's argument as the focused recall query when present: @@ -24,6 +24,21 @@ Recall useful project memory before acting on context-dependent work. 3. If no argument is provided, choose a narrow query from the current task and include project scope when known. -4. Treat recall as context, not authority. Prefer source-linked, +4. In a fan-out worker, filter recall with its agent profile plus the shared + workflow and current session. At coordinator fan-in, omit only the + agent-profile filter deliberately so all worker results remain visible: + + ```bash + env -u TREE_RING_AGENT_PROFILE tree-ring recall "$ARGUMENTS" \ + --workflow-id "$TREE_RING_WORKFLOW_ID" \ + --session-id "$TREE_RING_SESSION_ID" \ + --scope agent + ``` + +5. Treat recall as context, not authority. Prefer source-linked, high-confidence, non-superseded entries and verify drift-prone facts before relying on them. + +Identity and scope partition and route local memory; they are not read ACLs. +The supported shared-root boundary is cooperative processes on one host and a +local filesystem, not cross-host or network-filesystem coordination. diff --git a/scripts/smoke_v013.sh b/scripts/smoke_v013.sh new file mode 100644 index 0000000..abebdbc --- /dev/null +++ b/scripts/smoke_v013.sh @@ -0,0 +1,212 @@ +#!/usr/bin/env bash +set -euo pipefail + +tree_ring_bin="${TREE_RING_BIN:-tree-ring}" +if [[ "${tree_ring_bin}" == */* ]]; then + test -x "${tree_ring_bin}" +else + command -v "${tree_ring_bin}" >/dev/null +fi +test "$("${tree_ring_bin}" --version)" = "tree-ring 0.13.0" + +smoke_base="${RUNNER_TEMP:-${TMPDIR:-/tmp}}" +smoke_base="${smoke_base%/}" +smoke_dir=$(mktemp -d "${smoke_base}/tree-ring-claude-smoke.XXXXXX") +case "${smoke_dir}" in + "${smoke_base}"/tree-ring-claude-smoke.*) ;; + *) exit 91 ;; +esac + +cleanup() { + find "${smoke_dir}" -depth -delete +} +trap cleanup EXIT + +hash_file() { + local file_path=$1 + if command -v sha256sum >/dev/null; then + sha256sum "${file_path}" | awk '{print $1}' + else + shasum -a 256 "${file_path}" | awk '{print $1}' + fi +} + +snapshot_tree() { + local root_path=$1 + find "${root_path}" -type f | LC_ALL=C sort | while IFS= read -r file_path; do + printf '%s %s\n' \ + "$(hash_file "${file_path}")" \ + "${file_path#"${root_path}"/}" + done +} + +# Policy inspection must not initialize a missing root. +missing_root="${smoke_dir}/missing-store" +if "${tree_ring_bin}" --root "${missing_root}" policy status >/dev/null 2>&1; then + exit 92 +fi +if "${tree_ring_bin}" --root "${missing_root}" policy audit --limit 100 \ + >/dev/null 2>&1; then + exit 93 +fi +test ! -e "${missing_root}" + +# Policy inspection must not migrate or add sidecars to a legacy schema-v2 DB. +legacy_root="${smoke_dir}/legacy-v2" +legacy_db="${legacy_root}/memory.sqlite3" +mkdir -p "${legacy_root}" +TREE_RING_SMOKE_LEGACY_DB="${legacy_db}" python3 - <<'PY' +import os +import sqlite3 + +database = os.environ["TREE_RING_SMOKE_LEGACY_DB"] +connection = sqlite3.connect(database) +connection.execute("CREATE TABLE sentinel (value TEXT NOT NULL)") +connection.execute("INSERT INTO sentinel(value) VALUES ('unchanged')") +connection.execute("PRAGMA user_version = 2") +connection.commit() +connection.close() +PY +snapshot_tree "${legacy_root}" > "${smoke_dir}/legacy-before.sha256" +"${tree_ring_bin}" --root "${legacy_root}" policy status >/dev/null 2>&1 || true +"${tree_ring_bin}" --root "${legacy_root}" policy audit --limit 100 \ + >/dev/null 2>&1 || true +snapshot_tree "${legacy_root}" > "${smoke_dir}/legacy-after.sha256" +cmp "${smoke_dir}/legacy-before.sha256" "${smoke_dir}/legacy-after.sha256" + +# Exercise the documented same-host Coordinated-mode workflow. +store_root="${smoke_dir}/coordinated-store" +"${tree_ring_bin}" --root "${store_root}" init >/dev/null +grant_json=$( + "${tree_ring_bin}" \ + --root "${store_root}" \ + --json \ + policy enable \ + --coordinator smoke-coordinator +) +coordinator_capability=$( + python3 -c \ + 'import json, sys; print(json.load(sys.stdin)["capability"])' \ + <<<"${grant_json}" +) +test "${#coordinator_capability}" -gt 20 + +worker_env=( + env + -u TREE_RING_COORDINATOR_TOKEN + TREE_RING_AGENT_PROFILE=worker-storage + TREE_RING_WORKFLOW_ID=release-smoke + TREE_RING_SESSION_ID=attempt-1 +) +first_write=$( + "${worker_env[@]}" "${tree_ring_bin}" \ + --root "${store_root}" \ + --json \ + remember "Storage validation completed." \ + --event-type lesson \ + --scope agent \ + --operation-id validate-storage-v1 \ + --source-ref runs/release-smoke/worker-storage.json +) +retry_write=$( + "${worker_env[@]}" "${tree_ring_bin}" \ + --root "${store_root}" \ + --json \ + remember "Storage validation completed." \ + --event-type lesson \ + --scope agent \ + --operation-id validate-storage-v1 \ + --source-ref runs/release-smoke/worker-storage.json +) +first_id=$( + python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' \ + <<<"${first_write}" +) +retry_id=$( + python3 -c 'import json, sys; print(json.load(sys.stdin)["id"])' \ + <<<"${retry_write}" +) +test "${first_id}" = "${retry_id}" + +if "${worker_env[@]}" "${tree_ring_bin}" \ + --root "${store_root}" \ + remember "Conflicting retry." \ + --event-type lesson \ + --scope agent \ + --operation-id validate-storage-v1 \ + --source-ref runs/release-smoke/worker-storage.json \ + >/dev/null 2>&1; then + exit 94 +fi +if "${worker_env[@]}" "${tree_ring_bin}" \ + --root "${store_root}" \ + remember "Unauthorized shared result." \ + --event-type lesson \ + --scope project \ + --operation-id worker-shared-v1 \ + --source-ref runs/release-smoke/worker-shared.json \ + >/dev/null 2>&1; then + exit 95 +fi + +fan_in=$( + env \ + -u TREE_RING_AGENT_PROFILE \ + -u TREE_RING_COORDINATOR_TOKEN \ + TREE_RING_WORKFLOW_ID=release-smoke \ + TREE_RING_SESSION_ID=attempt-1 \ + "${tree_ring_bin}" \ + --root "${store_root}" \ + --json \ + recall "storage validation" \ + --scope agent +) +python3 -c \ + 'import json, sys +value = json.load(sys.stdin) +results = value if isinstance(value, list) else value["results"] +raise SystemExit(0 if len(results) == 1 else 1)' \ + <<<"${fan_in}" + +TREE_RING_COORDINATOR_TOKEN="${coordinator_capability}" \ +TREE_RING_AGENT_PROFILE=coordinator \ +TREE_RING_WORKFLOW_ID=release-smoke \ +TREE_RING_SESSION_ID=attempt-1 \ + "${tree_ring_bin}" \ + --root "${store_root}" \ + remember "Coordinator-approved shared result." \ + --event-type lesson \ + --scope project \ + --operation-id coordinator-shared-v1 \ + --source-ref runs/release-smoke/coordinator.json \ + >/dev/null + +status_output=$( + "${tree_ring_bin}" --root "${store_root}" policy status +) +audit_output=$( + "${tree_ring_bin}" --root "${store_root}" policy audit --limit 100 +) +if [[ "${status_output}${audit_output}" == *"${coordinator_capability}"* ]]; then + exit 96 +fi +if grep -R -a -F -- "${coordinator_capability}" "${store_root}" >/dev/null; then + exit 97 +fi + +# Inspection on an upgraded store must leave every tracked store byte unchanged. +snapshot_tree "${store_root}" > "${smoke_dir}/upgraded-before.sha256" +"${tree_ring_bin}" --root "${store_root}" policy status >/dev/null +"${tree_ring_bin}" --root "${store_root}" policy audit --limit 100 >/dev/null +"${tree_ring_bin}" --root "${store_root}" audit --audit-type sensitive >/dev/null +"${tree_ring_bin}" \ + --root "${store_root}" \ + consolidate \ + --period-type manual \ + --dry-run \ + >/dev/null +"${tree_ring_bin}" --root "${store_root}" maintain >/dev/null +snapshot_tree "${store_root}" > "${smoke_dir}/upgraded-after.sha256" +cmp "${smoke_dir}/upgraded-before.sha256" "${smoke_dir}/upgraded-after.sha256" + +printf 'Tree Ring v0.13 integration smoke passed\n' diff --git a/scripts/validate.py b/scripts/validate.py new file mode 100644 index 0000000..53ab1b8 --- /dev/null +++ b/scripts/validate.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +"""Validate Tree Ring Memory Claude plugin packaging and safety guidance.""" + +from __future__ import annotations + +import json +import re +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +EXPECTED_WRAPPER_VERSION = "0.2.0" + + +def read(relative: str) -> str: + """Read a UTF-8 repository file.""" + return (ROOT / relative).read_text(encoding="utf-8") + + +def require_markers(relative: str, markers: list[str]) -> None: + """Require every contract marker in a repository file.""" + text = read(relative) + missing = [marker for marker in markers if marker not in text] + if missing: + raise SystemExit(f"{relative} is missing: {', '.join(missing)}") + + +def validate_manifests() -> None: + """Validate package identity and synchronized wrapper versions.""" + plugin = json.loads(read(".claude-plugin/plugin.json")) + marketplace = json.loads(read(".claude-plugin/marketplace.json")) + + if plugin.get("name") != "tree-ring-memory": + raise SystemExit("plugin.json name must be tree-ring-memory") + if plugin.get("version") != EXPECTED_WRAPPER_VERSION: + raise SystemExit("plugin.json wrapper version is stale") + if marketplace.get("version") != EXPECTED_WRAPPER_VERSION: + raise SystemExit("marketplace wrapper version is stale") + + plugins = marketplace.get("plugins") + if not isinstance(plugins, list) or len(plugins) != 1: + raise SystemExit("marketplace.json must contain exactly one plugin") + if plugins[0].get("source") != "./": + raise SystemExit("marketplace plugin source must be ./") + if plugins[0].get("version") != EXPECTED_WRAPPER_VERSION: + raise SystemExit("marketplace plugin version is stale") + + +def validate_skill() -> None: + """Validate the bundled skill's v0.13 behavioral contract.""" + relative = "skills/tree-ring-memory/SKILL.md" + text = read(relative) + if not text.startswith("---\n"): + raise SystemExit("SKILL.md must start with YAML frontmatter") + require_markers( + relative, + [ + "tree-ring 0.13.0", + "--agent-profile", + "--workflow-id", + "--session-id", + "--operation-id", + "--source-ref", + "TREE_RING_COORDINATOR_TOKEN", + "one host", + "local filesystem", + "not a read ACL", + "schema v3", + "Mixed-version operation is unsupported", + ], + ) + + +def validate_readme() -> None: + """Validate public install, migration, and boundary guidance.""" + require_markers( + "README.md", + [ + "v0.13.0", + "schema v3", + "Mixed v0.12/v0.13 operation is unsupported", + "one host", + "local filesystem", + "not read access-control boundaries", + "TREE_RING_COORDINATOR_TOKEN", + ], + ) + + +def validate_commands() -> None: + """Validate safe capture, recall, audit, and lifecycle commands.""" + require_markers( + "commands/tree-ring-capture.md", + [ + "--scope agent", + "--agent-profile", + "--workflow-id", + "--session-id", + "--operation-id", + "--source-ref", + "TREE_RING_COORDINATOR_TOKEN", + ], + ) + require_markers( + "commands/tree-ring-recall.md", + [ + "env -u TREE_RING_AGENT_PROFILE", + "--workflow-id", + "--session-id", + "--scope agent", + "not read ACLs", + ], + ) + require_markers( + "commands/tree-ring-audit.md", + [ + "tree-ring policy status", + "tree-ring policy audit --limit 100", + "The CLI forget modes are `redact` and `delete`", + "`/supersede `", + "never create or migrate a store", + "Mixed-version operation is unsupported", + ], + ) + + all_markdown = "\n".join( + path.read_text(encoding="utf-8") + for path in ROOT.rglob("*.md") + if ".git" not in path.parts + ) + if re.search(r"tree-ring\s+forget[^\n]*--mode\s+supersede", all_markdown): + raise SystemExit("found unsupported `forget --mode supersede` guidance") + if "--coordinator-token" in all_markdown: + raise SystemExit("coordinator capability must never be described as a CLI flag") + + audit = read("commands/tree-ring-audit.md") + inspection_section = audit.split( + "After the verified schema-v3 upgrade, start with non-mutating inspection:", + maxsplit=1, + )[1].split("If the user names a focus", maxsplit=1)[0] + for apply_flag in ("--repair-fts", "--apply-expired", "--apply-secret-redactions"): + if apply_flag in inspection_section: + raise SystemExit( + f"non-mutating inspection contains mutating flag {apply_flag}" + ) + + +def validate_security_boundary() -> None: + """Validate coordinator capability and supported-boundary warnings.""" + require_markers( + "SECURITY.md", + [ + "TREE_RING_COORDINATOR_TOKEN", + "not a read ACL", + "one host", + "local filesystem", + "network-filesystem safety", + ], + ) + + +def validate_workflow() -> None: + """Validate that CI executes a checksum-pinned v0.13 runtime smoke.""" + require_markers( + ".github/workflows/validate.yml", + [ + "actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd", + 'TREE_RING_VERSION: "0.13.0"', + "cbe4c108c8881b2df1b72a26bfc86396dcdccba66fc8b976f340012e8c095e7d", + "sha256sum --check --status", + "bash scripts/smoke_v013.sh", + ], + ) + require_markers( + "scripts/smoke_v013.sh", + [ + "tree-ring 0.13.0", + "TREE_RING_COORDINATOR_TOKEN", + "--operation-id", + "policy status", + "policy audit --limit 100", + "legacy-v2", + ], + ) + + +def main() -> None: + """Run every package, documentation, security, and CI contract check.""" + validate_manifests() + validate_skill() + validate_readme() + validate_commands() + validate_security_boundary() + validate_workflow() + print("Tree Ring Memory Claude plugin validation passed") + + +if __name__ == "__main__": + main() diff --git a/skills/tree-ring-memory/SKILL.md b/skills/tree-ring-memory/SKILL.md index b1b1b25..3a578e0 100644 --- a/skills/tree-ring-memory/SKILL.md +++ b/skills/tree-ring-memory/SKILL.md @@ -35,8 +35,12 @@ On macOS ARM64: ```bash brew tap TerminallyLazy/tree-ring brew install tree-ring +tree-ring --version ``` +The multi-agent and coordinator commands below require `tree-ring 0.13.0` or +newer. + ## When To Recall Recall before: @@ -80,6 +84,10 @@ tree-ring remember "Use project-scoped recall before changing release behavior." --tag workflow ``` +That project-scoped example is valid in Open mode. In Coordinated mode, an +ordinary worker must use matching agent scope and identity; shared/project +writes require the coordinator capability. + Use `tree-ring evidence` when the lesson comes from an evaluation, checkpoint, experiment, branch, incident, or reviewed run artifact. @@ -90,6 +98,9 @@ tree-ring evidence "Migration smoke test passed with project-local memory." \ --score 0.91 ``` +In Coordinated mode, `tree-ring evidence` is a non-agent write and requires the +coordinator capability. + Evidence outcome mapping: - `promoted`: durable heartwood from supported evidence @@ -97,6 +108,91 @@ Evidence outcome mapping: - `deferred`: seed for promising unresolved options - `observed`: outer-ring evaluation result +## Multi-Agent Coordination + +For same-host fan-out/fan-in: + +- Give each worker a distinct `--agent-profile`. +- Share one `--workflow-id` across the workflow. +- Use a new `--session-id` for each execution attempt. +- Give every logical write a stable `--operation-id` and durable + `--source-ref`. +- Reuse the same session and operation IDs for an exact retry. Conflicting + reuse fails closed. +- At fan-in, recall with the shared workflow, session, and intended scope. + Deliberately omit the agent-profile filter when the coordinator needs results + from every worker. + +Example worker write: + +```bash +tree-ring remember "Storage validation completed." \ + --event-type lesson \ + --scope agent \ + --agent-profile worker-storage \ + --workflow-id release-readiness \ + --session-id attempt-1 \ + --operation-id validate-storage-v1 \ + --source-ref runs/release-readiness/worker-storage.json +``` + +Example coordinator fan-in recall: + +```bash +tree-ring recall "release readiness" \ + --workflow-id release-readiness \ + --session-id attempt-1 \ + --scope agent +``` + +Scope and identity fields partition and route local memory; they are not read +access-control boundaries. A shared SQLite root supports cooperative concurrent +processes on one host using a local filesystem. Cross-host or +network-filesystem workflows must use per-host stores plus an explicit, +source-preserving fan-in. + +## Coordinated Write Policy + +Stores remain in backward-compatible Open mode until a coordinator explicitly +enables Coordinated mode: + +```bash +tree-ring policy enable --coordinator release-coordinator +export TREE_RING_COORDINATOR_TOKEN='' +tree-ring policy status +tree-ring policy audit --limit 100 +tree-ring policy rotate --coordinator release-coordinator-next +export TREE_RING_COORDINATOR_TOKEN='' +tree-ring policy disable +unset TREE_RING_COORDINATOR_TOKEN +``` + +Replace the environment value immediately after rotation. There is no token CLI +flag. Never place the capability in prompts, memory events, logs, source +references, or committed files. Inject it only into coordinator processes and +remove it from every ordinary worker environment. + +In Coordinated mode, an ordinary worker may create only non-heartwood +`scope=agent` memory whose `agent_profile` matches its `--agent-profile` or +`TREE_RING_AGENT_PROFILE`. Shared or non-agent writes, heartwood, imports, +persisted DOX/Revolve sync, persisted consolidation, ring changes, +supersede/delete/redact, and applied maintenance require the coordinator +capability. On an already-upgraded schema-v3 store, recall, export, adapter +dry-runs, consolidation dry-runs, and plain report-only maintenance do not +change memory content. Only `policy status` and `policy audit` are guaranteed +never to create or migrate a store, so use them for pre-upgrade policy +inspection. + +This is operational write authorization in official Rust and CLI paths. It is +not a read ACL or protection against an adversary who controls local files or +the process environment. + +Before a v0.13 binary first upgrades an existing store to schema v3, stop every +Tree Ring process, checkpoint and back up the complete store, and upgrade every +CLI, plugin, and bundled worker. Schema v3 fences memory inserts, updates, and +deletes from v0.12 writers. Mixed-version operation is unsupported. Roll back +only by stopping all processes and restoring the pre-upgrade backup. + ## Ring Selection Use these rings: @@ -137,9 +233,12 @@ If memory is wrong, private, stale, or superseded: tree-ring forget mem_example --mode delete --reason "example cleanup" tree-ring audit --audit-type sensitive tree-ring consolidate --period-type manual --dry-run -tree-ring maintain --apply-expired --repair-fts +tree-ring maintain ``` +Delete, redact, applied maintenance, and other lifecycle mutations require the +coordinator capability when the store is in Coordinated mode. + ## Source Adapters Run adapter commands with `--dry-run` first. Sync only concise, source-linked