From 75f20b16fc0a0e8cd7cf508cb159c513dcd97778 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:31:21 -0700 Subject: [PATCH 01/16] feat: leg_corpus disk export + PEFT train/eval scripts Write full leg_block packs to data/lora-export (disk_export_path) so MCP chat stays token-lean; add export/train/eval/grow scripts for Gemma LoRA path. --- crates/engram-server/src/leg_corpus.rs | 79 ++++++- scripts/eval_leg_geometry_gate.py | 229 ++++++++++++++++++ scripts/export_leg_corpus_jsonl.py | 149 ++++++++++++ scripts/grow_leg_sft.sh | 16 ++ scripts/peft_leg_geometry_train.py | 309 +++++++++++++++++++++++++ 5 files changed, 781 insertions(+), 1 deletion(-) create mode 100644 scripts/eval_leg_geometry_gate.py create mode 100755 scripts/export_leg_corpus_jsonl.py create mode 100755 scripts/grow_leg_sft.sh create mode 100755 scripts/peft_leg_geometry_train.py diff --git a/crates/engram-server/src/leg_corpus.rs b/crates/engram-server/src/leg_corpus.rs index 505a604..b9b6c4c 100644 --- a/crates/engram-server/src/leg_corpus.rs +++ b/crates/engram-server/src/leg_corpus.rs @@ -63,6 +63,8 @@ pub struct CorpusBuildResult { pub candidates: usize, pub export: ScrubExportResult, pub homotopy: HomotopyReport, + /// Absolute path of full pack dump written for PEFT export (if any). + pub disk_export_path: Option, } #[derive(Debug, Clone)] @@ -124,6 +126,10 @@ pub fn build_training_corpus( ); let homotopy = verify_pack_homotopy(&export.packs, config.coherence_min); + // Full pack dump for PEFT (chat MCP truncates large packs arrays). + // ENGRAM_LORA_EXPORT_DIR overrides; else data/lora-export under cwd if present. + let disk_export_path = write_full_pack_export(corpus_concept, &export.packs, &homotopy); + if persist_manifest { let manifest = json!({ "format": "leg_corpus_manifest_v1", @@ -132,6 +138,7 @@ pub fn build_training_corpus( "candidate_count": candidates.len(), "pack_count": export.packs.len(), "denied_count": export.denied.len(), + "disk_export_path": disk_export_path, "homotopy": { "checked": homotopy.checked, "passed": homotopy.passed, @@ -162,6 +169,67 @@ pub fn build_training_corpus( candidates: candidates.len(), export, homotopy, + disk_export_path, + } +} + +/// Write full `leg_corpus_batch_v1` JSON to disk for PEFT JSONL export. +/// Returns absolute path string when successful. +fn write_full_pack_export( + corpus_concept: &str, + packs: &[Value], + homotopy: &HomotopyReport, +) -> Option { + let dir = std::env::var("ENGRAM_LORA_EXPORT_DIR").unwrap_or_else(|_| { + let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")); + let candidate = cwd.join("data/lora-export"); + if candidate.is_dir() || cwd.join("data").is_dir() { + candidate.to_string_lossy().into_owned() + } else { + // Fall back under store-adjacent default in home + dirs_fallback_lora_dir() + } + }); + let dir_path = std::path::PathBuf::from(&dir); + if let Err(e) = std::fs::create_dir_all(&dir_path) { + eprintln!("[leg_corpus] mkdir {dir}: {e}"); + return None; + } + let safe_name = corpus_concept.replace([':', '/', '\\'], "_"); + let file = dir_path.join(format!("{safe_name}_batch.json")); + let batch = json!({ + "format": "leg_corpus_batch_v1", + "corpus_concept": corpus_concept, + "pack_format": PACK_FORMAT, + "pack_count": packs.len(), + "homotopy": { + "checked": homotopy.checked, + "passed": homotopy.passed, + "mean_coherence": homotopy.mean_coherence, + "min_coherence": homotopy.min_coherence, + }, + "packs": packs, + }); + match serde_json::to_vec_pretty(&batch) { + Ok(bytes) => { + if let Err(e) = std::fs::write(&file, bytes) { + eprintln!("[leg_corpus] write {}: {e}", file.display()); + return None; + } + Some(file.to_string_lossy().into_owned()) + } + Err(e) => { + eprintln!("[leg_corpus] serialize packs: {e}"); + None + } + } +} + +fn dirs_fallback_lora_dir() -> String { + if let Ok(home) = std::env::var("HOME") { + format!("{home}/.engram/lora-export") + } else { + "/tmp/engram-lora-export".into() } } @@ -175,6 +243,7 @@ pub fn corpus_response(result: &CorpusBuildResult) -> Value { "denied_count": result.export.denied.len(), "failed_coherence_count": result.export.failed_coherence.len(), "minted_derivatives": result.export.minted, + "disk_export_path": result.disk_export_path, "homotopy": { "checked": result.homotopy.checked, "passed": result.homotopy.passed, @@ -182,7 +251,15 @@ pub fn corpus_response(result: &CorpusBuildResult) -> Value { "min_coherence": result.homotopy.min_coherence, "failed": result.homotopy.failed, }, - "packs": result.export.packs, + // Omit full packs from MCP chat path when disk dump exists (token economy). + // Clients that need packs: read disk_export_path or set ENGRAM_LORA_EXPORT_INLINE=1. + "packs": if result.disk_export_path.is_some() + && std::env::var("ENGRAM_LORA_EXPORT_INLINE").ok().as_deref() != Some("1") + { + Value::Array(vec![]) + } else { + Value::Array(result.export.packs.clone()) + }, "denied": result.export.denied, "failed_coherence": result.export.failed_coherence, }) diff --git a/scripts/eval_leg_geometry_gate.py b/scripts/eval_leg_geometry_gate.py new file mode 100644 index 0000000..4bc15ab --- /dev/null +++ b/scripts/eval_leg_geometry_gate.py @@ -0,0 +1,229 @@ +#!/usr/bin/env python3 +"""eval_gate: fixed geometry probes base vs PEFT adapter (QLoRA 4bit). + +Writes data/lora-export/eval_gate_metrics.json +Exit 0 if gate pass: >=2 probes score adapter >= base (keyword hit rate) OR +adapter mean score > base mean by epsilon. +""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +import time +from pathlib import Path + +PROBES = [ + { + "id": "crs", + "prompt": "In Engram geometric memory, what is CRS and the grounded threshold?", + "keywords": ["0.74", "crs", "confidence", "grounded"], + }, + { + "id": "op_bind", + "prompt": "What does OP_BIND do in Engram VSA / FHRR?", + "keywords": ["bind", "relation", "op_bind", "vsa", "holographic"], + }, + { + "id": "fhrr", + "prompt": "What is FHRR in Engram?", + "keywords": ["fhrr", "fourier", "holographic", "phase", "8192"], + }, + { + "id": "ritual", + "prompt": "Name the lean Engram agent wake tool ritual.", + "keywords": ["session_start", "wake", "ack", "continuation", "lean"], + }, + { + "id": "lexicon", + "prompt": "What is a lexicon:word atom used for in Engram PEFT corpus?", + "keywords": ["lexicon", "word", "geometry", "training", "mint"], + }, +] + + +def score_text(text: str, keywords: list[str]) -> float: + t = text.lower() + hits = sum(1 for k in keywords if k.lower() in t) + return hits / max(len(keywords), 1) + + +def gen(model, tok, prompt: str, max_new: int = 48) -> str: + import torch + + msgs = f"<|user|>\n{prompt}\n<|assistant|>\n" + device = next(model.parameters()).device + inputs = tok(msgs, return_tensors="pt") + inputs = {k: v.to(device) for k, v in inputs.items()} + with torch.no_grad(): + out = model.generate( + **inputs, + max_new_tokens=max_new, + do_sample=False, + pad_token_id=tok.pad_token_id or tok.eos_token_id, + ) + full = tok.decode(out[0], skip_special_tokens=True) + if "<|assistant|>" in full: + return full.split("<|assistant|>")[-1].strip() + return full[len(msgs) :].strip() if full.startswith(msgs[:20]) else full + + +def load_model(base: str, adapter: str | None, fourbit: bool): + import torch + from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig + + tok = AutoTokenizer.from_pretrained(base, trust_remote_code=True) + if tok.pad_token is None: + tok.pad_token = tok.eos_token + if not getattr(tok, "chat_template", None): + tok.chat_template = ( + "{% for message in messages %}" + "{{'<|' + message['role'] + '|>\\n' + message['content'] + '\\n'}}" + "{% endfor %}" + ) + quant = None + if fourbit: + quant = BitsAndBytesConfig( + load_in_4bit=True, + bnb_4bit_quant_type="nf4", + bnb_4bit_compute_dtype=torch.bfloat16, + bnb_4bit_use_double_quant=True, + ) + model = AutoModelForCausalLM.from_pretrained( + base, + quantization_config=quant, + device_map="auto", + trust_remote_code=True, + ) + if adapter: + from peft import PeftModel + + model = PeftModel.from_pretrained(model, adapter) + model.eval() + return model, tok + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument( + "--base-model", + default="/home/a/.cache/huggingface/hub/models--google--gemma-4-12B", + ) + ap.add_argument( + "--adapter", + default="data/lora-export/adapters/leg_geometry_lora_v1", + ) + ap.add_argument( + "--metrics-out", + default="data/lora-export/eval_gate_metrics.json", + ) + ap.add_argument("--no-4bit", action="store_true") + ap.add_argument("--adapter-only", action="store_true", help="Skip base pass (VRAM)") + args = ap.parse_args() + fourbit = not args.no_4bit + t0 = time.time() + out_path = Path(args.metrics_out) + out_path.parent.mkdir(parents=True, exist_ok=True) + + results = [] + base_scores = [] + ad_scores = [] + + try: + if not args.adapter_only: + print("loading base...", flush=True) + base_m, tok = load_model(args.base_model, None, fourbit) + for p in PROBES: + text = gen(base_m, tok, p["prompt"]) + s = score_text(text, p["keywords"]) + base_scores.append(s) + results.append( + { + "id": p["id"], + "mode": "base", + "score": s, + "preview": text[:200], + } + ) + del base_m + import torch + + torch.cuda.empty_cache() + + print("loading adapter...", flush=True) + ad_m, tok = load_model(args.base_model, args.adapter, fourbit) + for p in PROBES: + text = gen(ad_m, tok, p["prompt"]) + s = score_text(text, p["keywords"]) + ad_scores.append(s) + results.append( + { + "id": p["id"], + "mode": "adapter", + "score": s, + "preview": text[:200], + } + ) + del ad_m + except Exception as e: + payload = { + "version": 1, + "status": "failed", + "error": repr(e), + "elapsed_s": round(time.time() - t0, 2), + } + out_path.write_text(json.dumps(payload, indent=2) + "\n") + print(json.dumps(payload)) + return 2 + + wins = 0 + per_probe = [] + if base_scores and len(base_scores) == len(ad_scores): + for i, p in enumerate(PROBES): + b, a = base_scores[i], ad_scores[i] + win = a >= b + if win and a > 0: + wins += 1 + elif a > b: + wins += 1 + per_probe.append( + {"id": p["id"], "base": b, "adapter": a, "adapter_ge_base": a >= b} + ) + mean_b = sum(base_scores) / len(base_scores) + mean_a = sum(ad_scores) / len(ad_scores) + passed = wins >= 2 or mean_a > mean_b + 0.02 + else: + mean_b = None + mean_a = sum(ad_scores) / max(len(ad_scores), 1) + # adapter-only: pass if mean keyword hit >= 0.25 + passed = mean_a >= 0.25 + for i, p in enumerate(PROBES): + per_probe.append( + {"id": p["id"], "base": None, "adapter": ad_scores[i], "adapter_ge_base": None} + ) + wins = sum(1 for s in ad_scores if s >= 0.25) + + payload = { + "version": 1, + "status": "ok" if passed else "fail", + "passed": passed, + "wins_adapter_ge_base": wins, + "mean_base": mean_b, + "mean_adapter": mean_a, + "per_probe": per_probe, + "results_preview": results, + "base_model": args.base_model, + "adapter": args.adapter, + "adapter_only": args.adapter_only, + "elapsed_s": round(time.time() - t0, 2), + "gate_rule": "wins>=2 or mean_adapter>mean_base+0.02 (or adapter-only mean>=0.25)", + } + out_path.write_text(json.dumps(payload, indent=2) + "\n") + print(json.dumps({k: payload[k] for k in ("status", "passed", "wins_adapter_ge_base", "mean_base", "mean_adapter", "elapsed_s")})) + return 0 if passed else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/export_leg_corpus_jsonl.py b/scripts/export_leg_corpus_jsonl.py new file mode 100755 index 0000000..8c16715 --- /dev/null +++ b/scripts/export_leg_corpus_jsonl.py @@ -0,0 +1,149 @@ +#!/usr/bin/env python3 +"""Export Engram leg_block_pack_v1 batches to chat/instruction JSONL for LoRA. + +Accepts either: + - leg_corpus_batch_v1 JSON (full MCP mcp_engram_leg_corpus build response) + - leg_corpus_manifest_v1 JSON (packs or packs_preview) + +Does NOT train adapters — only materializes supervised rows from scrubbed_provlog. + +Usage: + # Preferred: full pack dump from engram (after MCP restart on binary with disk export): + # mcp_engram_leg_corpus(action=build) → writes + # $ENGRAM_LORA_EXPORT_DIR/_batch.json (or data/lora-export/) + # response.disk_export_path points at the file (packs omitted from chat). + + python3 scripts/export_leg_corpus_jsonl.py \\ + --input data/lora-export/training_corpus_leg_geometry_v1_batch.json \\ + --output data/lora-export/leg_geometry_sft.jsonl + + # Also append hermies / agent TRAINING tuples if present: + python3 scripts/export_leg_corpus_jsonl.py \\ + --input data/lora-export/training_corpus_leg_geometry_v1_batch.json \\ + --output data/lora-export/leg_geometry_sft.jsonl \\ + --extra-tuples data/lora-export/extra_training_tuples.jsonl + +PEFT train (out of band, example — install peft/transformers yourself): + # Prefer GPU; hermies server uses -ngl 0 and is embeddings-first. + # Use this JSONL as --dataset for your chosen SFT trainer; do not claim + # Engram trained a LoRA until train metrics + adapter path exist. + +Env: + ENGRAM_LORA_EXPORT_DIR directory for full pack dumps (server-side) + ENGRAM_LORA_EXPORT_INLINE=1 include packs array in MCP response (default: omit when dump exists) +""" + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any, Dict, Iterable, List + + +def load_packs(doc: Dict[str, Any]) -> List[Dict[str, Any]]: + if "packs" in doc and isinstance(doc["packs"], list): + return doc["packs"] + if "packs_preview" in doc and isinstance(doc["packs_preview"], list): + return doc["packs_preview"] + # Nested under markdown-extracted blob + for key in ("result", "data"): + if key in doc and isinstance(doc[key], dict): + return load_packs(doc[key]) + return [] + + +def pack_to_row(pack: Dict[str, Any]) -> Dict[str, Any] | None: + text = (pack.get("scrubbed_provlog") or "").strip() + if not text: + return None + src = pack.get("source_concept") or pack.get("geometry_ref") or "unknown" + crs = pack.get("crs") + coh = pack.get("semantic_coherence") + system = ( + "You are an Engram geometric-memory agent. Answer from non-flat " + "substrate concepts (FHRR/VSA, CRS≥0.74, ProvLog, Merkle, rituals)." + ) + user = ( + f"Recall and restate the load-bearing content of corpus pack " + f"`{src}` (crs={crs}, semantic_coherence={coh}) for training fidelity." + ) + return { + "messages": [ + {"role": "system", "content": system}, + {"role": "user", "content": user}, + {"role": "assistant", "content": text[:12000]}, + ], + "meta": { + "source_concept": src, + "crs": crs, + "semantic_coherence": coh, + "format": pack.get("format"), + "zedos_tag": pack.get("zedos_tag"), + }, + } + + +def iter_extra_tuples(path: Path) -> Iterable[Dict[str, Any]]: + if not path.exists(): + return + with path.open() as f: + for line in f: + line = line.strip() + if not line: + continue + yield json.loads(line) + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--input", "-i", required=True, type=Path) + ap.add_argument("--output", "-o", required=True, type=Path) + ap.add_argument("--extra-tuples", type=Path, default=None) + ap.add_argument("--min-crs", type=float, default=0.74) + args = ap.parse_args() + + raw = args.input.read_text(encoding="utf-8") + # Allow markdown-wrapped ```json ... ``` + if "```json" in raw: + raw = raw.split("```json", 1)[1].split("```", 1)[0] + doc = json.loads(raw) + packs = load_packs(doc) + rows: List[Dict[str, Any]] = [] + skipped = 0 + for p in packs: + crs = p.get("crs") + if isinstance(crs, (int, float)) and crs < args.min_crs: + skipped += 1 + continue + row = pack_to_row(p) + if row is None: + skipped += 1 + continue + rows.append(row) + + if args.extra_tuples: + for t in iter_extra_tuples(args.extra_tuples): + rows.append(t) + + args.output.parent.mkdir(parents=True, exist_ok=True) + with args.output.open("w", encoding="utf-8") as f: + for r in rows: + f.write(json.dumps(r, ensure_ascii=False) + "\n") + + print( + json.dumps( + { + "packs_in": len(packs), + "rows_out": len(rows), + "skipped": skipped, + "output": str(args.output), + } + ) + ) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/grow_leg_sft.sh b/scripts/grow_leg_sft.sh new file mode 100755 index 0000000..d614562 --- /dev/null +++ b/scripts/grow_leg_sft.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +# Continuity-per-token: refresh disk leg_corpus packs → SFT JSONL (no chat dumps). +# Prefer MCP leg_corpus build first (writes disk_export_path); this only re-exports. +set -euo pipefail +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +IN="${1:-$ROOT/data/lora-export/training_corpus_leg_geometry_v1_batch.json}" +OUT="${2:-$ROOT/data/lora-export/leg_geometry_sft.jsonl}" +EXTRA="${3:-$ROOT/data/lora-export/extra_training_tuples.jsonl}" +if [[ ! -f "$IN" ]]; then + echo "missing pack batch: $IN (run mcp_engram_leg_corpus build first)" >&2 + exit 2 +fi +ARGS=(--input "$IN" --output "$OUT") +[[ -f "$EXTRA" ]] && ARGS+=(--extra-tuples "$EXTRA") +python3 "$ROOT/scripts/export_leg_corpus_jsonl.py" "${ARGS[@]}" +wc -l "$OUT" diff --git a/scripts/peft_leg_geometry_train.py b/scripts/peft_leg_geometry_train.py new file mode 100755 index 0000000..f7f10ab --- /dev/null +++ b/scripts/peft_leg_geometry_train.py @@ -0,0 +1,309 @@ +#!/usr/bin/env python3 +"""PEFT/LoRA (QLoRA) SFT on leg_geometry_sft.jsonl — out-of-band train. + +Usage: + .venv-peft/bin/python scripts/peft_leg_geometry_train.py \\ + --dataset data/lora-export/leg_geometry_sft.jsonl \\ + --out data/lora-export/adapters/leg_geometry_lora_v1 \\ + --base-model /home/a/.cache/huggingface/hub/models--google--gemma-4-12B \\ + --max-steps 30 --load-in-4bit + +Writes data/lora-export/peft_metrics.json on completion or hard fail. +""" + +from __future__ import annotations + +import argparse +import json +import sys +import time +from pathlib import Path + + +def write_metrics(path: Path, payload: dict) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8") + + +def main() -> int: + ap = argparse.ArgumentParser(description=__doc__) + ap.add_argument("--dataset", type=Path, required=True) + ap.add_argument("--out", type=Path, required=True) + ap.add_argument("--base-model", type=str, default="") + ap.add_argument("--max-steps", type=int, default=30) + ap.add_argument("--max-length", type=int, default=512) + ap.add_argument("--load-in-4bit", action="store_true", default=True) + ap.add_argument("--no-4bit", action="store_true") + ap.add_argument( + "--metrics-out", + type=Path, + default=Path("data/lora-export/peft_metrics.json"), + ) + args = ap.parse_args() + use_4bit = args.load_in_4bit and not args.no_4bit + + if not args.dataset.exists(): + print(f"missing dataset: {args.dataset}", file=sys.stderr) + return 2 + rows = sum(1 for line in args.dataset.open() if line.strip()) + if rows < 1: + print("empty dataset", file=sys.stderr) + return 2 + + try: + import torch + import peft # noqa: F401 + import transformers # noqa: F401 + except ImportError as e: + write_metrics( + args.metrics_out, + { + "version": 1, + "status": "blocked_no_torch_env", + "error": str(e), + "dataset_rows": rows, + "dataset": str(args.dataset), + }, + ) + print(f"PEFT env missing: {e}", file=sys.stderr) + return 3 + + if not args.base_model: + write_metrics( + args.metrics_out, + { + "version": 1, + "status": "blocked_no_base_model", + "dataset_rows": rows, + "hint": "Pass --base-model (local HF dir or id).", + }, + ) + print("Set --base-model", file=sys.stderr) + return 4 + + t0 = time.time() + write_metrics( + args.metrics_out, + { + "version": 1, + "status": "running", + "stage": "peft_metrics", + "dataset_rows": rows, + "dataset": str(args.dataset), + "base_model": args.base_model, + "load_in_4bit": use_4bit, + "max_steps": args.max_steps, + "pid": __import__("os").getpid(), + }, + ) + + try: + from datasets import load_dataset + from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training + from transformers import ( + AutoModelForCausalLM, + AutoTokenizer, + BitsAndBytesConfig, + ) + from trl import SFTConfig, SFTTrainer + except ImportError as e: + write_metrics( + args.metrics_out, + { + "version": 1, + "status": "blocked_import", + "error": str(e), + "dataset_rows": rows, + }, + ) + print(f"train stack import failed: {e}", file=sys.stderr) + return 3 + + tok = AutoTokenizer.from_pretrained(args.base_model, trust_remote_code=True) + if tok.pad_token is None: + tok.pad_token = tok.eos_token + # Local Gemma-4 snapshot may lack chat_template; TRL SFT requires one if + # "messages" columns remain. Prefer plain-text field only (see map below). + if not getattr(tok, "chat_template", None): + tok.chat_template = ( + "{% for message in messages %}" + "{{'<|' + message['role'] + '|>\\n' + message['content'] + '\\n'}}" + "{% endfor %}" + ) + + quant = None + if use_4bit: + quant = BitsAndBytesConfig( + load_in_4bit=True, + bnb_4bit_quant_type="nf4", + bnb_4bit_compute_dtype=torch.bfloat16, + bnb_4bit_use_double_quant=True, + ) + + try: + model = AutoModelForCausalLM.from_pretrained( + args.base_model, + quantization_config=quant, + device_map="auto", + trust_remote_code=True, + torch_dtype=torch.bfloat16 if not use_4bit else None, + ) + except Exception as e: + # Gemma4 unified may need AutoModel + try: + from transformers import AutoModel + + model = AutoModel.from_pretrained( + args.base_model, + quantization_config=quant, + device_map="auto", + trust_remote_code=True, + ) + print(f"fallback AutoModel: {e}", file=sys.stderr) + except Exception as e2: + write_metrics( + args.metrics_out, + { + "version": 1, + "status": "failed_model_load", + "error": f"{e!r} | fallback {e2!r}", + "dataset_rows": rows, + "base_model": args.base_model, + }, + ) + print(f"model load failed: {e2}", file=sys.stderr) + return 5 + + if use_4bit: + model = prepare_model_for_kbit_training(model) + if hasattr(model, "gradient_checkpointing_enable"): + model.gradient_checkpointing_enable() + if hasattr(model, "config"): + model.config.use_cache = False + + # Broad target modules — Gemma/LLaMA-style names + peft_config = LoraConfig( + r=8, + lora_alpha=16, + lora_dropout=0.05, + bias="none", + task_type="CAUSAL_LM", + target_modules=[ + "q_proj", + "k_proj", + "v_proj", + "o_proj", + "gate_proj", + "up_proj", + "down_proj", + ], + ) + try: + model = get_peft_model(model, peft_config) + except Exception as e: + write_metrics( + args.metrics_out, + { + "version": 1, + "status": "failed_peft_attach", + "error": repr(e), + "dataset_rows": rows, + }, + ) + print(f"peft attach failed: {e}", file=sys.stderr) + return 6 + + def to_text(ex: dict) -> dict: + msgs = ex.get("messages") or [] + parts = [] + for m in msgs: + role = m.get("role", "") + content = m.get("content", "") + parts.append(f"<|{role}|>\n{content}") + return {"text": "\n".join(parts)} + + ds = load_dataset("json", data_files=str(args.dataset), split="train") + # Drop messages/meta so TRL does not force apply_chat_template on incomplete tok + drop = [c for c in ds.column_names if c != "text"] + ds = ds.map(to_text, remove_columns=drop) + + args.out.mkdir(parents=True, exist_ok=True) + sft_kwargs = dict( + output_dir=str(args.out), + max_steps=args.max_steps, + per_device_train_batch_size=1, + gradient_accumulation_steps=8, + learning_rate=2e-4, + logging_steps=5, + save_steps=args.max_steps, + bf16=True, + report_to=[], + gradient_checkpointing=True, + optim="paged_adamw_8bit", + ) + # TRL version variance: dataset_text_field / max_seq_length may live on config + try: + sft_args = SFTConfig( + **sft_kwargs, + dataset_text_field="text", + max_length=args.max_length, + ) + except TypeError: + try: + sft_args = SFTConfig( + **sft_kwargs, + dataset_text_field="text", + max_seq_length=args.max_length, + ) + except TypeError: + sft_args = SFTConfig(**sft_kwargs) + + try: + trainer = SFTTrainer( + model=model, + args=sft_args, + train_dataset=ds, + processing_class=tok, + ) + except TypeError: + trainer = SFTTrainer( + model=model, + args=sft_args, + train_dataset=ds, + tokenizer=tok, + dataset_text_field="text", + max_seq_length=args.max_length, + ) + + result = trainer.train() + trainer.save_model(str(args.out)) + tok.save_pretrained(str(args.out)) + + loss = None + if result is not None and getattr(result, "training_loss", None) is not None: + loss = float(result.training_loss) + elif result is not None and getattr(result, "metrics", None): + loss = result.metrics.get("train_loss") + + payload = { + "version": 1, + "status": "ok", + "stage": "peft_metrics", + "dataset_rows": rows, + "dataset": str(args.dataset), + "adapter_path": str(args.out), + "base_model": args.base_model, + "max_steps": args.max_steps, + "load_in_4bit": use_4bit, + "loss": loss, + "elapsed_s": round(time.time() - t0, 2), + "torch": torch.__version__, + "cuda": torch.cuda.is_available(), + } + write_metrics(args.metrics_out, payload) + print(json.dumps({"status": "ok", "adapter_path": str(args.out), "loss": loss})) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From e30e328b319c78c8a74de1fa65cceceeabb381b5 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:46:56 -0700 Subject: [PATCH 02/16] docs: Glass-Box RSI v1 design spec (hybrid goals + LEG home) Approved design for process verify packets, parent/child fire goals, typed loop gates, and LEG Browser split-home glass box. Spec only; implementation follows writing-plans after user review. --- .../specs/2026-07-10-glassbox-rsi-design.md | 229 ++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md diff --git a/docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md b/docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md new file mode 100644 index 0000000..bbff946 --- /dev/null +++ b/docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md @@ -0,0 +1,229 @@ +# Glass-Box RSI v1 — Design Spec + +**Date:** 2026-07-10 +**Status:** Approved in dialogue (architecture, goals/verify, LEG home, errors/CI/testing) +**Primary goal:** `goal:engram_mvp_v1` +**Related parents (to mint):** `goal:dual_rsi_program`, `goal:ship_substrate`, `goal:glassbox_leg` + +## Problem + +Dual RSI and related scheduled loops compound agent memory (`.leg3`, PEFT, ship/PR) under token budget, but: + +1. Loop fires often claim success without a structured **verify packet**. +2. Human visibility lags — **LEG Browser** was last evolved as a beta glass-box and is not wired to dual RSI / Gemma stage / PR / MCP restart state. +3. CI can be partial (e.g. one matrix job fail, one pass) while agents still need an honest “ready-to-merge” signal. +4. Goal stack is not systematically used as a **fire-level verification loop**. + +## Goals (product) + +- Every scheduled fire is a **child goal** under a durable parent, completed only after a **typed verify**. +- **LEG Browser** is the human mirror of process + verify state (split home). +- Prefer existing MCP + REST; add aggregate API only if hydrate is too chatty. +- No silent success: stage flip, ship claim, or “healthy” only after verify. + +## Non-goals (v1) + +- Auto-merge PRs. +- LEG editing goals or running loops from the browser. +- React rewrite of LEG. +- Auto-restart MCP without `ENGRAM_ALLOW_MCP_RESTART=1`. +- Full GGUF LoRA serve for Gemma4 (blocked by llama.cpp tensor map; tracked separately). + +--- + +## §1 Architecture + +``` +LEG Browser :8765 (?view=glassbox) + │ REST read-only + ▼ +engram serve :3456 → .leg3 (~/.engram) + ▲ + │ MCP write (verify before complete) +Loop fires (Dual RSI, Ship, PR Watch, MCP Stale, Aliveness, …) +``` + +**Rules** + +1. No silent success without typed verify. +2. LEG is read-only; does not execute loops. +3. Phase A (process contract) before Phase B (LEG home). +4. Prefer `/api/block`, presentation, activity SSE; optional later `GET /api/glassbox`. + +--- + +## §2 Goal + verify contract + +### Parent goals (durable) + +| Parent | Owns | +|--------|------| +| `goal:dual_rsi_program` | Tracks S/G/M, stage machine, corpus/PEFT | +| `goal:ship_substrate` | Dirty-tree → test → PR | +| `goal:glassbox_leg` | LEG split home + any glassbox API | + +Parents **serve** `goal:engram_mvp_v1`. + +### Child fire goals + +Mint at fire start: + +`goal:fire___` + +Required fields (goal text and/or related `metric:verify_*`): + +| Field | Meaning | +|-------|---------| +| `parent` | Durable parent goal id | +| `loop` | `dual_rsi` \| `ship_gate` \| `pr_watch` \| `mcp_stale` \| `aliveness` \| … | +| `track` | `S` \| `G` \| `M` \| null | +| `intent` | One line | +| `verify_type` | Typed gate id | +| `verify_status` | `pending` \| `pass` \| `fail` | +| `verify_evidence` | Paths, test summary, CI URL, metric concept | +| `falsify` | What would reverse this fire | + +### Typed gates + +| Loop | `verify_type` | Pass means | +|------|---------------|------------| +| Dual RSI **S** | `substrate_local` | Disk artifact and/or targeted test + integrity sample; no pack dump in chat | +| Dual RSI **G** | `gemma_stage` | Stage advanced + metric atom status ok (`peft_metrics` / `eval_gate` / future `gguf_lora`) | +| Dual RSI **M** | `meta_policy` | dual_loop updated with rationale; optional scar | +| Ship | `ship_local` | Tests green + commit + PR URL, or explicit `ship_skip` if no code dirty | +| PR watch | `ci_status` | Check rollup recorded; all **required** checks SUCCESS for ready-to-merge; else not ready | +| MCP stale | `binary_vs_proc` | FRESH / STALE / OFFLINE atom; restart only if allowed | +| Aliveness | `metrics_atom` | `metric:dual_rsi_aliveness_*` written and related | + +### Lifecycle + +``` +session_start + → ensure parent related to engram_mvp_v1 + → mint child goal (active, verify=pending) + → act (one track / one ship / one pr check) + → run typed verify + → IF pass: complete child + dual_loop update + → IF fail: block/abandon child + scar if repeated + dual_loop blockers + → session_end (must include child goal id + verify_status) +``` + +### `helper:rsi_dual_loop_state` schema extensions + +```json +{ + "open_pr": "url|null", + "mcp_restart_required": false, + "last_fire_goal": "goal:fire_...", + "last_verify": { "type": "...", "status": "pass|fail", "at": "ISO-8601" }, + "parents": ["goal:dual_rsi_program", "goal:ship_substrate", "goal:glassbox_leg"], + "track_next": "S|G|M", + "gemma": { "stage": "...", "adapter_path": "...", "sft_rows": 0 } +} +``` + +--- + +## §3 LEG split home + +**Entry:** `./scripts/leg --live` → `http://127.0.0.1:8765/?view=glassbox` + +### Layout + +| Region | Content | +|--------|---------| +| **Top — health strip** | fidelity, mean hub CRS, hermies cos, gemma stage, track_next, open_pr, CI pill, mcp_restart_required, last_verify | +| **Center — goals + last fire** | Parent cards (`dual_rsi_program`, `ship_substrate`, `glassbox_leg`) with last child fire, verify pass/fail, evidence one-liner | +| **Right — activity** | Existing SSE / activity feed; click → block inspector | + +### Data sources (Phase B1 — no new API required) + +| UI | Source | +|----|--------| +| Health | `helper:rsi_dual_loop_state`, latest aliveness metric, `/health` | +| Parents / fires | `/api/block/goal:*` + relations | +| open_pr | dual_loop field; external link only | +| Activity | existing feed | + +Optional Phase B2: `GET /api/glassbox` one-shot aggregate if multi-fetch is too slow. + +### Interaction + +- Parent → list child fires (newest first). +- Fire → verify packet + falsify. +- Amber banner if `mcp_restart_required=true`. +- Read-only; no “run loop” in v1. + +### Out of scope for LEG v1 + +React rewrite; goal editing; auto-merge; full CI log streaming. + +--- + +## §4 Errors, CI, testing + +### Failure table + +| Failure | Process | LEG | +|---------|---------|-----| +| Verify fail | Child → blocked; scar if repeated; no stage flip | Red fire card | +| Flaky / partial CI | Not ready-to-merge until all required checks SUCCESS | Yellow CI pill | +| dual_loop missing | Still mint child; scar thin handoff | Amber unknown | +| MCP STALE | `mcp_restart_required=true`; no auto-kill unless allowed | Restart banner | +| Doom loop (same fail 2×) | Scar + stop fixing that fire | Scarred fire | +| Ship skip (clean tree) | Child complete with `ship_skip` | Grey skip, not green hero | + +### CI policy + +- Ship verify = **local** tests only. +- PR watch verify = **remote** rollup; ready only if every required check is SUCCESS. +- No auto-merge in v1. +- One narrow CI fix max per PR-watch fire; second same failure → scar. + +### Testing the program + +| Layer | What | +|-------|------| +| Schema | dual_loop field parse; verify packet presence | +| Loop dry-run | Throwaway namespace: mint child → pass/fail → status | +| LEG | Static glassbox fixture + live checklist | +| Regression | Ship cannot claim PR without URL; PR watch cannot mark ready on red CI | + +--- + +## Phased delivery + +| Phase | Deliverable | +|-------|-------------| +| **A1** | Mint parent goals; dual_loop schema fields; rewrite loop prompts with fire goal + typed verify | +| **A2** | Land/fix PR #58 CI; document MCP restart after server binary merge | +| **B1** | LEG `?view=glassbox` split home on existing APIs | +| **B2** | Optional `/api/glassbox` aggregate | +| **C** | Deep links (CI refresh, stage diagram) | + +--- + +## Live context (2026-07-10) + +- Dual RSI: eval_gate **pass**; SFT ~51 rows; PEFT adapter on disk; GGUF convert **blocked** (Gemma4 tensor map). +- PR: https://github.com/staticroostermedia-arch/engram/pull/58 — open; mixed build-and-test historically. +- Control: `helper:rsi_dual_loop_state`. +- LEG: `tools/leg-browser/index.html`, `docs/LEG_BROWSER.md`, `./scripts/leg --live`. + +--- + +## Success criteria + +1. A Dual RSI fire that skips verify cannot flip stage in dual_loop without a failing/pending child goal visible in LEG (or dual_loop last_verify fail). +2. Ship fire either opens PR with local green tests or records `ship_skip`. +3. PR watch never reports ready-to-merge with a required check FAILURE. +4. LEG glassbox view shows health strip + three parent cards + last fire within one live load. +5. Human can answer “what did the last fire claim and prove?” from LEG alone without chat scrollback. + +--- + +## Open questions (post-v1) + +- When llama.cpp maps Gemma4 LoRA tensors, re-open G track `gguf_lora` with `gemma_stage` verify. +- Whether consciousness L7 should get parent `goal:consciousness_loop` or stay meta-only. +- Token budget: reduce L7 cadence while L1 runs if wallet contention continues. From fd420f107655fb1af80542f2ffa3ac29b8649d6f Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:48:32 -0700 Subject: [PATCH 03/16] docs: Glass-Box RSI v1 implementation plan Task breakdown for schemas, loop prompts v2, parent goal runbook, LEG ?view=glassbox split home, and PR #58 CI follow-up. --- .../plans/2026-07-10-glassbox-rsi.md | 720 ++++++++++++++++++ 1 file changed, 720 insertions(+) create mode 100644 docs/superpowers/plans/2026-07-10-glassbox-rsi.md diff --git a/docs/superpowers/plans/2026-07-10-glassbox-rsi.md b/docs/superpowers/plans/2026-07-10-glassbox-rsi.md new file mode 100644 index 0000000..aa162ba --- /dev/null +++ b/docs/superpowers/plans/2026-07-10-glassbox-rsi.md @@ -0,0 +1,720 @@ +# Glass-Box RSI v1 Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Make every scheduled Engram loop fire a hybrid parent/child goal with a typed verify packet, and give humans a LEG Browser split-home view of that process. + +**Architecture:** Process contract first (parent goals + dual_loop schema + loop prompt rewrites), then LEG `?view=glassbox` reading existing REST (`/api/block`, `/health`, activity). No auto-merge, no LEG-run-loops, optional `/api/glassbox` only if multi-fetch is too slow. + +**Tech Stack:** Engram MCP goals (`mcp_engram_goal_*`), `helper:rsi_dual_loop_state`, Python JSON schema tests, vanilla JS SPA (`tools/leg-browser/index.html`), `engram serve` REST, scheduler prompts. + +**Spec:** `docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md` + +--- + +## File map + +| Path | Responsibility | +|------|----------------| +| `docs/schemas/dual_loop_state_v1.json` | JSON Schema for dual_loop control register | +| `docs/schemas/fire_verify_packet_v1.json` | JSON Schema for per-fire verify payload embedded in child goals | +| `docs/skills/engram-glassbox-rsi.md` | Operator skill: fire lifecycle + typed gates | +| `docs/skills/loop-prompts/dual_rsi_v2.md` | Canonical Dual RSI prompt (goal+verify) | +| `docs/skills/loop-prompts/ship_gate_v2.md` | Canonical Ship Gate prompt | +| `docs/skills/loop-prompts/pr_watch_v2.md` | Canonical PR Watch prompt | +| `docs/skills/loop-prompts/mcp_stale_v2.md` | Canonical MCP Stale prompt | +| `docs/skills/loop-prompts/aliveness_bench_v2.md` | Canonical Aliveness prompt | +| `scripts/validate_dual_loop_schema.py` | Offline schema validator for dual_loop + verify samples | +| `scripts/test_glassbox_schemas.py` | Unit tests for schema validators | +| `tools/leg-browser/index.html` | Glassbox view UI (CSS + HTML shell + `loadGlassbox`) | +| `tools/leg-browser/fixtures/glassbox-sample.json` | Static fixture for offline glassbox smoke | +| `docs/LEG_BROWSER.md` | Document `?view=glassbox` | +| `docs/AGENT_MEMORY_CONTRACT.md` | One short section: fire goals + verify (pointer to skill) | + +**Do not create** `/api/glassbox` in Phase B1 unless Task 8 proves multi-fetch is unusable (>3s cold). + +--- + +### Task 1: dual_loop + verify JSON schemas + +**Files:** +- Create: `docs/schemas/dual_loop_state_v1.json` +- Create: `docs/schemas/fire_verify_packet_v1.json` +- Create: `scripts/validate_dual_loop_schema.py` +- Create: `scripts/test_glassbox_schemas.py` + +- [ ] **Step 1: Write failing test** + +Create `scripts/test_glassbox_schemas.py`: + +```python +#!/usr/bin/env python3 +"""Unit tests for Glass-Box RSI schemas.""" +from __future__ import annotations + +import json +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "scripts")) + +from validate_dual_loop_schema import validate_dual_loop, validate_verify_packet # noqa: E402 + + +class TestDualLoopSchema(unittest.TestCase): + def test_minimal_valid(self): + doc = { + "version": 1, + "track_next": "G", + "track_last": "S", + "open_pr": None, + "mcp_restart_required": False, + "last_fire_goal": "goal:fire_dual_rsi_test_1", + "last_verify": { + "type": "substrate_local", + "status": "pass", + "at": "2026-07-10T00:00:00Z", + }, + "parents": ["goal:dual_rsi_program"], + "gemma": {"stage": "eval_gate", "sft_rows": 51}, + } + errs = validate_dual_loop(doc) + self.assertEqual(errs, []) + + def test_missing_track_next_fails(self): + errs = validate_dual_loop({"version": 1}) + self.assertTrue(any("track_next" in e for e in errs)) + + def test_verify_packet_pass(self): + pkt = { + "parent": "goal:dual_rsi_program", + "loop": "dual_rsi", + "track": "S", + "intent": "grow corpus", + "verify_type": "substrate_local", + "verify_status": "pass", + "verify_evidence": "data/lora-export/leg_geometry_sft.jsonl rows=51", + "falsify": "disk export missing", + } + self.assertEqual(validate_verify_packet(pkt), []) + + def test_verify_status_invalid(self): + pkt = { + "parent": "goal:x", + "loop": "ship_gate", + "track": None, + "intent": "ship", + "verify_type": "ship_local", + "verify_status": "maybe", + "verify_evidence": "n/a", + "falsify": "n/a", + } + errs = validate_verify_packet(pkt) + self.assertTrue(any("verify_status" in e for e in errs)) + + +if __name__ == "__main__": + unittest.main() +``` + +- [ ] **Step 2: Run test — expect import failure** + +```bash +cd /home/a/Documents/Engram +python3 scripts/test_glassbox_schemas.py -v +``` + +Expected: `ModuleNotFoundError: validate_dual_loop_schema` or import error. + +- [ ] **Step 3: Implement schemas + validator** + +`docs/schemas/dual_loop_state_v1.json` — require at least: `version`, `track_next`, `mcp_restart_required`, `parents` (array), `last_verify` object optional with `type`/`status`/`at`. + +`docs/schemas/fire_verify_packet_v1.json` — require all fields from spec §2. + +`scripts/validate_dual_loop_schema.py`: + +```python +#!/usr/bin/env python3 +"""Validate dual_loop / fire verify packets (stdlib only).""" +from __future__ import annotations + +from typing import Any + +VERIFY_TYPES = { + "substrate_local", + "gemma_stage", + "meta_policy", + "ship_local", + "ship_skip", + "ci_status", + "binary_vs_proc", + "metrics_atom", +} +VERIFY_STATUS = {"pending", "pass", "fail"} +TRACKS = {"S", "G", "M", None} + + +def validate_dual_loop(doc: dict[str, Any]) -> list[str]: + errs: list[str] = [] + if not isinstance(doc, dict): + return ["root must be object"] + if doc.get("version") != 1: + errs.append("version must be 1") + if doc.get("track_next") not in ("S", "G", "M"): + errs.append("track_next must be S|G|M") + if "mcp_restart_required" in doc and not isinstance(doc["mcp_restart_required"], bool): + errs.append("mcp_restart_required must be bool") + if "parents" in doc and not isinstance(doc["parents"], list): + errs.append("parents must be array") + lv = doc.get("last_verify") + if lv is not None: + if not isinstance(lv, dict): + errs.append("last_verify must be object") + else: + if lv.get("status") not in VERIFY_STATUS: + errs.append("last_verify.status invalid") + if "type" not in lv: + errs.append("last_verify.type required") + return errs + + +def validate_verify_packet(doc: dict[str, Any]) -> list[str]: + errs: list[str] = [] + for k in ( + "parent", + "loop", + "intent", + "verify_type", + "verify_status", + "verify_evidence", + "falsify", + ): + if k not in doc: + errs.append(f"missing {k}") + if doc.get("verify_type") not in VERIFY_TYPES: + errs.append("verify_type invalid") + if doc.get("verify_status") not in VERIFY_STATUS: + errs.append("verify_status invalid") + if "track" in doc and doc["track"] not in TRACKS: + errs.append("track must be S|G|M|null") + return errs + + +if __name__ == "__main__": + import json + import sys + from pathlib import Path + + path = Path(sys.argv[1]) if len(sys.argv) > 1 else None + if not path: + print("usage: validate_dual_loop_schema.py [dual_loop|verify]") + sys.exit(2) + doc = json.loads(path.read_text()) + mode = sys.argv[2] if len(sys.argv) > 2 else "dual_loop" + errs = validate_dual_loop(doc) if mode == "dual_loop" else validate_verify_packet(doc) + if errs: + print("FAIL", errs) + sys.exit(1) + print("OK") +``` + +Also write minimal JSON Schema files documenting the same fields (for humans; Python validator is source of truth for tests). + +- [ ] **Step 4: Run tests — expect pass** + +```bash +python3 scripts/test_glassbox_schemas.py -v +``` + +Expected: `OK` / all tests passed. + +- [ ] **Step 5: Commit** + +```bash +git add docs/schemas/dual_loop_state_v1.json docs/schemas/fire_verify_packet_v1.json \ + scripts/validate_dual_loop_schema.py scripts/test_glassbox_schemas.py +git commit -m "feat(glassbox): dual_loop + fire verify schemas and validators" +``` + +--- + +### Task 2: Operator skill — glassbox RSI fire lifecycle + +**Files:** +- Create: `docs/skills/engram-glassbox-rsi.md` +- Modify: `docs/skills/README.md` (add one row if the file has a skill table) +- Modify: `SKILLS.md` (one bullet linking glassbox skill) + +- [ ] **Step 1: Write skill content** + +`docs/skills/engram-glassbox-rsi.md` must include: + +1. When to use (any scheduled Dual RSI / Ship / PR / Stale / Aliveness fire). +2. Parent goal table from spec. +3. Child goal mint recipe: + +```text +mcp_engram_goal_create( + goal_id="fire___", + parent="goal:dual_rsi_program", # or ship_substrate etc. + statement="...", + priority="medium", + affirm="...", deny="...", reconcile="..." +) +``` + +4. Verify packet YAML block to paste into goal update note / `metric:verify_` remember text. +5. Typed gate table from spec. +6. HARD: no stage flip / PR claim / ready-to-merge without verify_status=pass. +7. Pointer to loop prompt files under `docs/skills/loop-prompts/`. + +- [ ] **Step 2: Link from SKILLS.md** + +Add under public skills: + +```markdown +- [docs/skills/engram-glassbox-rsi.md](docs/skills/engram-glassbox-rsi.md) — Hybrid fire goals + typed verify for scheduled loops + LEG glass box. +``` + +- [ ] **Step 3: Commit** + +```bash +git add docs/skills/engram-glassbox-rsi.md SKILLS.md docs/skills/README.md +git commit -m "docs(skills): engram-glassbox-rsi fire lifecycle skill" +``` + +--- + +### Task 3: Canonical loop prompts v2 (goal + verify) + +**Files:** +- Create: `docs/skills/loop-prompts/dual_rsi_v2.md` +- Create: `docs/skills/loop-prompts/ship_gate_v2.md` +- Create: `docs/skills/loop-prompts/pr_watch_v2.md` +- Create: `docs/skills/loop-prompts/mcp_stale_v2.md` +- Create: `docs/skills/loop-prompts/aliveness_bench_v2.md` +- Create: `docs/skills/loop-prompts/README.md` + +- [ ] **Step 1: Write dual_rsi_v2.md** + +Must be paste-ready for `scheduler_create`. Structure: + +```markdown +# Dual RSI v2 (glassbox) + +Interval: 20m (user schedules) + +``` +DUAL RSI v2 — ONE track + fire goal + typed verify + +1. session_start(intent="dual_rsi") +2. ack_wake_queue +3. Ensure parents exist (read_concept goal:dual_rsi_program; if missing goal_create dual_rsi_program serving engram_mvp_v1) +4. read_concept(helper:rsi_dual_loop_state) → TRACK=track_next +5. goal_create fire_dual_rsi__ parent=goal:dual_rsi_program + statement="Dual RSI track TRACK one win" + note verify_status=pending verify_type=substrate_local|gemma_stage|meta_policy +6. Execute ONE track only (S/G/M rules from v1 HARD constraints unchanged) +7. Typed verify: + S: disk path exists OR cargo/test summary + integrity sample + G: stage metric file/status ok + M: dual_loop rationale written +8. goal_update_status fire → completed|blocked + remember metric:verify_* if useful +9. update helper:rsi_dual_loop_state (track_last, track_next, last_fire_goal, last_verify) +10. session_end(summary includes fire goal id + verify_status) + +HARD: no packs in chat; no multi-track; no stage flip if verify fail +``` +``` + +- [ ] **Step 2: Write ship_gate_v2.md, pr_watch_v2.md, mcp_stale_v2.md, aliveness_bench_v2.md** + +Same structure as v1 prompts already used in schedulers, plus steps 3–5 and 7–9 from dual_rsi_v2 pattern: + +- ship: parent `goal:ship_substrate`, verify `ship_local` or `ship_skip` +- pr_watch: parent `goal:ship_substrate`, verify `ci_status`, ready only if all required checks SUCCESS +- mcp_stale: parent `goal:dual_rsi_program` or ship, verify `binary_vs_proc` +- aliveness: parent `goal:dual_rsi_program`, verify `metrics_atom` + +- [ ] **Step 3: README for loop-prompts** + +```markdown +# Loop prompts (Glass-Box RSI v2) + +Canonical scheduler bodies. Reschedule with scheduler_create after editing. +Do not leave verify out — LEG glassbox depends on fire goals + last_verify. +``` + +- [ ] **Step 4: Commit** + +```bash +git add docs/skills/loop-prompts/ +git commit -m "docs: glassbox loop prompts v2 with fire goals and typed verify" +``` + +--- + +### Task 4: Mint durable parent goals (operator script + dry-run notes) + +**Files:** +- Create: `scripts/mint_glassbox_parent_goals.md` (runbook using MCP, not auto-MCP from shell) + +MCP has no stable non-interactive batch in-repo without a client; ship a **runbook** agents execute once. + +- [ ] **Step 1: Write runbook** + +`scripts/mint_glassbox_parent_goals.md`: + +```markdown +# One-time: mint Glass-Box RSI parent goals + +Via Engram MCP (search_tool then use_tool): + +1. mcp_engram_goal_create goal_id=dual_rsi_program statement="Dual RSI substrate+Gemma stage machine with typed verify" parent=goal:engram_mvp_v1 priority=high +2. mcp_engram_goal_create goal_id=ship_substrate statement="Ship substrate code with local verify then PR" parent=goal:engram_mvp_v1 priority=high +3. mcp_engram_goal_create goal_id=glassbox_leg statement="LEG Browser split-home glass box for process visibility" parent=goal:engram_mvp_v1 priority=medium +4. mcp_engram_update helper:rsi_dual_loop_state append parents list + schema fields +5. mcp_engram_promote_hot each goal:* and helper:rsi_dual_loop_state +6. Verify: goal_get_children / goal_status on dual_rsi_program +``` + +- [ ] **Step 2: Commit** + +```bash +git add scripts/mint_glassbox_parent_goals.md +git commit -m "docs: runbook to mint glassbox parent goals via MCP" +``` + +--- + +### Task 5: AGENT_MEMORY_CONTRACT pointer + +**Files:** +- Modify: `docs/AGENT_MEMORY_CONTRACT.md` (add short section near lean tools table) + +- [ ] **Step 1: Insert section** + +After lean tools table (or Continuity nudges), add: + +```markdown +## Glass-Box RSI (scheduled fires) + +Scheduled Dual RSI / Ship / PR / Stale / Aliveness fires **mint a child `goal:fire_*`**, run a **typed verify**, then update `helper:rsi_dual_loop_state.last_verify`. Do not flip stages or claim ship/PR ready without `verify_status=pass`. + +See: [docs/skills/engram-glassbox-rsi.md](skills/engram-glassbox-rsi.md), [docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md](superpowers/specs/2026-07-10-glassbox-rsi-design.md). +``` + +- [ ] **Step 2: Commit** + +```bash +git add docs/AGENT_MEMORY_CONTRACT.md +git commit -m "docs: AGENT_MEMORY_CONTRACT glassbox fire-goal pointer" +``` + +--- + +### Task 6: LEG glassbox static fixture + +**Files:** +- Create: `tools/leg-browser/fixtures/glassbox-sample.json` + +- [ ] **Step 1: Write fixture** + +```json +{ + "dual_loop": { + "version": 1, + "track_next": "S", + "track_last": "G", + "open_pr": "https://github.com/staticroostermedia-arch/engram/pull/58", + "mcp_restart_required": false, + "last_fire_goal": "goal:fire_dual_rsi_demo_1", + "last_verify": { + "type": "gemma_stage", + "status": "pass", + "at": "2026-07-10T20:00:00Z" + }, + "parents": [ + "goal:dual_rsi_program", + "goal:ship_substrate", + "goal:glassbox_leg" + ], + "gemma": { + "stage": "eval_gate", + "sft_rows": 51, + "eval_passed": true + } + }, + "aliveness": { + "fidelity": 0.94, + "mean_hub_crs": 0.89, + "hermies_cos": 0.71 + }, + "parents": [ + { + "id": "goal:dual_rsi_program", + "status": "active", + "last_fire": "goal:fire_dual_rsi_demo_1", + "last_verify_status": "pass" + }, + { + "id": "goal:ship_substrate", + "status": "active", + "last_fire": "goal:fire_ship_demo_1", + "last_verify_status": "pass" + }, + { + "id": "goal:glassbox_leg", + "status": "active", + "last_fire": null, + "last_verify_status": "pending" + } + ], + "last_fire": { + "id": "goal:fire_dual_rsi_demo_1", + "intent": "eval_gate advance", + "verify_type": "gemma_stage", + "verify_status": "pass", + "verify_evidence": "eval_gate_metrics.json passed=true", + "falsify": "eval fail on re-run" + } +} +``` + +- [ ] **Step 2: Commit** + +```bash +git add tools/leg-browser/fixtures/glassbox-sample.json +git commit -m "feat(leg-browser): glassbox sample fixture for offline smoke" +``` + +--- + +### Task 7: LEG `?view=glassbox` UI (split home) + +**Files:** +- Modify: `tools/leg-browser/index.html` (large file — touch only new CSS class block, one view shell, and boot routing) + +**Insertion strategy (do not rewrite the whole SPA):** + +1. CSS: after existing `.glass-box-tagline` block (~line 529), add `.gb-*` styles for strip + three-column layout. +2. HTML: add `` near main app root (after brand header ~1696). +3. JS: add `const GLASSBOX = new URLSearchParams(location.search).get('view') === 'glassbox';` near boot; if true, hide main rails and show `#glassbox-view`, call `loadGlassbox()`. +4. `loadGlassbox()` live path: fetch `/api/block/helper:rsi_dual_loop_state`, parse JSON from text if fenced, fetch parent goals and last_fire_goal blocks, fetch `/health`, optional recent aliveness via `/api/recent?n=10` filter `metric:dual_rsi_aliveness`. +5. Offline: if `?fixture=1` or live fetch fails, load `./fixtures/glassbox-sample.json` (only works when served from tools/leg-browser directory). + +- [ ] **Step 1: Add CSS for glassbox** + +Minimal classes: + +```css +.gb-root { display: flex; flex-direction: column; gap: 12px; padding: 12px; } +.gb-strip { display: flex; flex-wrap: wrap; gap: 8px; } +.gb-chip { border: 1px solid #333; border-radius: 6px; padding: 4px 8px; font-size: 12px; } +.gb-chip.pass { border-color: #2a6; } +.gb-chip.fail { border-color: #a33; } +.gb-chip.warn { border-color: #a80; } +.gb-main { display: grid; grid-template-columns: 1fr 280px; gap: 12px; min-height: 60vh; } +.gb-parents { display: flex; flex-direction: column; gap: 8px; } +.gb-card { border: 1px solid #333; border-radius: 8px; padding: 10px; cursor: pointer; } +.gb-activity { border: 1px solid #333; border-radius: 8px; padding: 8px; overflow: auto; max-height: 70vh; } +@media (max-width: 900px) { .gb-main { grid-template-columns: 1fr; } } +``` + +- [ ] **Step 2: Add HTML shell** + +```html + +``` + +- [ ] **Step 3: Implement loadGlassbox JS** + +Key behaviors: + +- Parse dual_loop text: if contains ` ```json `, extract fence; else try `JSON.parse` whole body. +- Render chips for fidelity (from aliveness or n/a), stage, track_next, open_pr (link), last_verify status, mcp_restart_required. +- Parent cards: for each id in `parents`, fetch `/api/block/{id}` for status snippet. +- Last fire: fetch `last_fire_goal` block; show verify fields from text if present. +- Activity: reuse existing activity poll if `loadActivity` exists; else fetch `/api/activity?limit=20`. +- Click parent/fire: call existing `openBlock(concept)` or inspector if available. + +- [ ] **Step 4: Manual smoke** + +```bash +# static fixture mode (from tools/leg-browser) +cd /home/a/Documents/Engram/tools/leg-browser +python3 -m http.server 8766 & +# open http://127.0.0.1:8766/index.html?view=glassbox&fixture=1 +# expect: three parent cards, chips populated from fixture, no console errors +kill %1 +``` + +Live (optional if serve up): + +```bash +./scripts/leg --live +# open http://127.0.0.1:8765/?view=glassbox +# expect: dual_loop chips or graceful fallback to fixture message +``` + +- [ ] **Step 5: Commit** + +```bash +git add tools/leg-browser/index.html +git commit -m "feat(leg-browser): glassbox split-home view (?view=glassbox)" +``` + +--- + +### Task 8: Document LEG glassbox + optional API decision + +**Files:** +- Modify: `docs/LEG_BROWSER.md` +- Modify: `tools/leg-browser/README.md` + +- [ ] **Step 1: LEG_BROWSER.md section** + +```markdown +## Glass-Box RSI view + +```bash +./scripts/leg --live +# open http://127.0.0.1:8765/?view=glassbox +``` + +Shows health strip (dual_loop + aliveness), parent program goals, last fire verify, and activity. Read-only. Requires process contract (fire goals + dual_loop fields) for full fidelity; otherwise chips show unknown. + +Offline fixture: serve `tools/leg-browser` and open `?view=glassbox&fixture=1`. +``` + +- [ ] **Step 2: tools/leg-browser/README.md** — same short blurb. + +- [ ] **Step 3: Decision note for /api/glassbox** + +If live multi-fetch >3s on 80k stalk in practice, file follow-up: implement `GET /api/glassbox` in `serve.rs` returning dual_loop + parents + last_fire only. **Not required for B1 acceptance.** + +- [ ] **Step 4: Commit** + +```bash +git add docs/LEG_BROWSER.md tools/leg-browser/README.md +git commit -m "docs: LEG glassbox view usage" +``` + +--- + +### Task 9: Reschedule guidance (operator, not code) + +**Files:** +- Create: `docs/skills/loop-prompts/RESCHEDULE.md` + +- [ ] **Step 1: Write RESCHEDULE.md** + +List current job IDs (update when known): Dual RSI 20m, Hermies 2h, Meta 8h, Aliveness 1d, Research 3d, Consciousness 30m, Ship 1d, PR 2h, MCP stale 1d. + +For each: cancel old with `scheduler_delete`, create new with body from `*_v2.md`. + +Note: PR watch only while open_pr set. + +- [ ] **Step 2: Commit** + +```bash +git add docs/skills/loop-prompts/RESCHEDULE.md +git commit -m "docs: how to reschedule loops onto glassbox v2 prompts" +``` + +--- + +### Task 10: PR #58 CI honesty (ship substrate, not LEG) + +**Files:** none new; operational steps on branch `feat/leg-corpus-disk-export-peft-path` + +- [ ] **Step 1: Inspect failed job** + +```bash +gh run view 29121690135 --log-failed 2>&1 | tail -80 +``` + +(or current failed run id from `gh pr checks 58`) + +- [ ] **Step 2: One narrow fix** (only if failure is real and reproducible) + +Typical: clippy, fmt, unused import in `leg_corpus.rs`. Fix only that; re-run: + +```bash +cargo test -p engram-server --tests +cargo fmt --all -- --check +cargo clippy -p engram-server -- -D warnings +``` + +- [ ] **Step 3: Push if fix needed** + +```bash +git push +gh pr checks 58 +``` + +- [ ] **Step 4: Commit only if code changed** + +```bash +git commit -m "fix(ci): address PR #58 build-and-test failure" +``` + +Acceptance: PR watch can report ready only when **all required** checks SUCCESS. + +--- + +## Spec coverage checklist + +| Spec item | Task | +|-----------|------| +| dual_loop schema fields | Task 1 | +| fire verify packet | Task 1 | +| Parent goals | Task 4 | +| Child fire lifecycle | Task 2–3 | +| Typed gates | Task 2–3 | +| Loop prompt rewrites | Task 3 | +| AGENT_MEMORY_CONTRACT pointer | Task 5 | +| LEG split home | Task 6–8 | +| CI ready policy | Task 3 (pr_watch_v2), Task 10 | +| MCP restart banner | Task 7 | +| No silent success | Tasks 2–3 HARD lines | +| Optional /api/glassbox later | Task 8 decision note | +| Phased A before B | Task order 1–5 then 6–8 | + +## Self-review (plan) + +- No TBD/TODO placeholders in steps. +- Schema field names consistent: `track_next`, `last_verify.status`, `verify_type`. +- LEG touches only additive CSS/HTML/JS + fixture. +- Process does not require new Rust for B1. + +--- + +## Execution handoff + +Plan complete and saved to `docs/superpowers/plans/2026-07-10-glassbox-rsi.md`. + +**Two execution options:** + +1. **Subagent-Driven (recommended)** — fresh subagent per task, review between tasks +2. **Inline Execution** — this session, `executing-plans`, batch with checkpoints + +Which approach? From d2982117bc15223a6324eb235bfe548cf8c79af3 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:50:35 -0700 Subject: [PATCH 04/16] feat(glassbox): dual_loop + fire verify schemas and validators --- docs/schemas/dual_loop_state_v1.json | 73 ++++++++++++++++++++++ docs/schemas/fire_verify_packet_v1.json | 64 +++++++++++++++++++ scripts/test_glassbox_schemas.py | 69 +++++++++++++++++++++ scripts/validate_dual_loop_schema.py | 82 +++++++++++++++++++++++++ 4 files changed, 288 insertions(+) create mode 100644 docs/schemas/dual_loop_state_v1.json create mode 100644 docs/schemas/fire_verify_packet_v1.json create mode 100755 scripts/test_glassbox_schemas.py create mode 100755 scripts/validate_dual_loop_schema.py diff --git a/docs/schemas/dual_loop_state_v1.json b/docs/schemas/dual_loop_state_v1.json new file mode 100644 index 0000000..541cd26 --- /dev/null +++ b/docs/schemas/dual_loop_state_v1.json @@ -0,0 +1,73 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://engram.dev/schemas/dual_loop_state_v1.json", + "title": "dual_loop_state_v1", + "description": "Control register for helper:rsi_dual_loop_state (Glass-Box RSI). Python validator in scripts/validate_dual_loop_schema.py is source of truth for tests.", + "type": "object", + "required": ["version", "track_next", "mcp_restart_required", "parents"], + "properties": { + "version": { + "const": 1, + "description": "Schema version; must be integer 1" + }, + "track_next": { + "type": "string", + "enum": ["S", "G", "M"], + "description": "Next Dual RSI track to fire" + }, + "track_last": { + "type": "string", + "enum": ["S", "G", "M"], + "description": "Last Dual RSI track that completed a fire" + }, + "open_pr": { + "type": ["string", "null"], + "description": "Open ship PR URL, or null if none" + }, + "mcp_restart_required": { + "type": "boolean", + "description": "True when binary_vs_proc verify found STALE binary vs process" + }, + "last_fire_goal": { + "type": ["string", "null"], + "description": "Most recent child goal:fire_* id" + }, + "last_verify": { + "type": "object", + "description": "Summary of last typed verify packet", + "required": ["type", "status"], + "properties": { + "type": { + "type": "string", + "description": "verify_type from fire packet (e.g. substrate_local)" + }, + "status": { + "type": "string", + "enum": ["pending", "pass", "fail"] + }, + "at": { + "type": "string", + "format": "date-time", + "description": "ISO-8601 timestamp of verify" + } + }, + "additionalProperties": true + }, + "parents": { + "type": "array", + "items": { "type": "string" }, + "description": "Durable parent goal ids (dual_rsi_program, ship_substrate, glassbox_leg)" + }, + "gemma": { + "type": "object", + "description": "Gemma track stage machine snapshot", + "properties": { + "stage": { "type": "string" }, + "adapter_path": { "type": "string" }, + "sft_rows": { "type": "integer", "minimum": 0 } + }, + "additionalProperties": true + } + }, + "additionalProperties": true +} diff --git a/docs/schemas/fire_verify_packet_v1.json b/docs/schemas/fire_verify_packet_v1.json new file mode 100644 index 0000000..f7d517c --- /dev/null +++ b/docs/schemas/fire_verify_packet_v1.json @@ -0,0 +1,64 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://engram.dev/schemas/fire_verify_packet_v1.json", + "title": "fire_verify_packet_v1", + "description": "Per-fire verify payload embedded in child goal:fire_* (Glass-Box RSI §2). Python validator in scripts/validate_dual_loop_schema.py is source of truth for tests.", + "type": "object", + "required": [ + "parent", + "loop", + "track", + "intent", + "verify_type", + "verify_status", + "verify_evidence", + "falsify" + ], + "properties": { + "parent": { + "type": "string", + "description": "Durable parent goal id (e.g. goal:dual_rsi_program)" + }, + "loop": { + "type": "string", + "description": "Loop id: dual_rsi | ship_gate | pr_watch | mcp_stale | aliveness | …" + }, + "track": { + "type": ["string", "null"], + "enum": ["S", "G", "M", null], + "description": "Dual RSI track when loop=dual_rsi; null for other loops" + }, + "intent": { + "type": "string", + "minLength": 1, + "description": "One-line fire intent" + }, + "verify_type": { + "type": "string", + "enum": [ + "substrate_local", + "gemma_stage", + "meta_policy", + "ship_local", + "ship_skip", + "ci_status", + "binary_vs_proc", + "metrics_atom" + ], + "description": "Typed gate id" + }, + "verify_status": { + "type": "string", + "enum": ["pending", "pass", "fail"] + }, + "verify_evidence": { + "type": "string", + "description": "Paths, test summary, CI URL, or metric concept" + }, + "falsify": { + "type": "string", + "description": "What would reverse this fire" + } + }, + "additionalProperties": true +} diff --git a/scripts/test_glassbox_schemas.py b/scripts/test_glassbox_schemas.py new file mode 100755 index 0000000..c83f63c --- /dev/null +++ b/scripts/test_glassbox_schemas.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python3 +"""Unit tests for Glass-Box RSI schemas.""" +from __future__ import annotations + +import json +import sys +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT / "scripts")) + +from validate_dual_loop_schema import validate_dual_loop, validate_verify_packet # noqa: E402 + + +class TestDualLoopSchema(unittest.TestCase): + def test_minimal_valid(self): + doc = { + "version": 1, + "track_next": "G", + "track_last": "S", + "open_pr": None, + "mcp_restart_required": False, + "last_fire_goal": "goal:fire_dual_rsi_test_1", + "last_verify": { + "type": "substrate_local", + "status": "pass", + "at": "2026-07-10T00:00:00Z", + }, + "parents": ["goal:dual_rsi_program"], + "gemma": {"stage": "eval_gate", "sft_rows": 51}, + } + errs = validate_dual_loop(doc) + self.assertEqual(errs, []) + + def test_missing_track_next_fails(self): + errs = validate_dual_loop({"version": 1}) + self.assertTrue(any("track_next" in e for e in errs)) + + def test_verify_packet_pass(self): + pkt = { + "parent": "goal:dual_rsi_program", + "loop": "dual_rsi", + "track": "S", + "intent": "grow corpus", + "verify_type": "substrate_local", + "verify_status": "pass", + "verify_evidence": "data/lora-export/leg_geometry_sft.jsonl rows=51", + "falsify": "disk export missing", + } + self.assertEqual(validate_verify_packet(pkt), []) + + def test_verify_status_invalid(self): + pkt = { + "parent": "goal:x", + "loop": "ship_gate", + "track": None, + "intent": "ship", + "verify_type": "ship_local", + "verify_status": "maybe", + "verify_evidence": "n/a", + "falsify": "n/a", + } + errs = validate_verify_packet(pkt) + self.assertTrue(any("verify_status" in e for e in errs)) + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/validate_dual_loop_schema.py b/scripts/validate_dual_loop_schema.py new file mode 100755 index 0000000..50767f8 --- /dev/null +++ b/scripts/validate_dual_loop_schema.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Validate dual_loop / fire verify packets (stdlib only).""" +from __future__ import annotations + +from typing import Any + +VERIFY_TYPES = { + "substrate_local", + "gemma_stage", + "meta_policy", + "ship_local", + "ship_skip", + "ci_status", + "binary_vs_proc", + "metrics_atom", +} +VERIFY_STATUS = {"pending", "pass", "fail"} +TRACKS = {"S", "G", "M", None} + + +def validate_dual_loop(doc: dict[str, Any]) -> list[str]: + errs: list[str] = [] + if not isinstance(doc, dict): + return ["root must be object"] + if doc.get("version") != 1: + errs.append("version must be 1") + if doc.get("track_next") not in ("S", "G", "M"): + errs.append("track_next must be S|G|M") + if "mcp_restart_required" in doc and not isinstance(doc["mcp_restart_required"], bool): + errs.append("mcp_restart_required must be bool") + if "parents" in doc and not isinstance(doc["parents"], list): + errs.append("parents must be array") + lv = doc.get("last_verify") + if lv is not None: + if not isinstance(lv, dict): + errs.append("last_verify must be object") + else: + if lv.get("status") not in VERIFY_STATUS: + errs.append("last_verify.status invalid") + if "type" not in lv: + errs.append("last_verify.type required") + return errs + + +def validate_verify_packet(doc: dict[str, Any]) -> list[str]: + errs: list[str] = [] + for k in ( + "parent", + "loop", + "intent", + "verify_type", + "verify_status", + "verify_evidence", + "falsify", + ): + if k not in doc: + errs.append(f"missing {k}") + if doc.get("verify_type") not in VERIFY_TYPES: + errs.append("verify_type invalid") + if doc.get("verify_status") not in VERIFY_STATUS: + errs.append("verify_status invalid") + if "track" in doc and doc["track"] not in TRACKS: + errs.append("track must be S|G|M|null") + return errs + + +if __name__ == "__main__": + import json + import sys + from pathlib import Path + + path = Path(sys.argv[1]) if len(sys.argv) > 1 else None + if not path: + print("usage: validate_dual_loop_schema.py [dual_loop|verify]") + sys.exit(2) + doc = json.loads(path.read_text()) + mode = sys.argv[2] if len(sys.argv) > 2 else "dual_loop" + errs = validate_dual_loop(doc) if mode == "dual_loop" else validate_verify_packet(doc) + if errs: + print("FAIL", errs) + sys.exit(1) + print("OK") From 5fde64261f8cc4fa571acfcb32295445163502e5 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:53:21 -0700 Subject: [PATCH 05/16] docs(skills): engram-glassbox-rsi fire lifecycle skill --- SKILLS.md | 1 + docs/skills/README.md | 1 + docs/skills/engram-glassbox-rsi.md | 233 +++++++++++++++++++++++++++++ 3 files changed, 235 insertions(+) create mode 100644 docs/skills/engram-glassbox-rsi.md diff --git a/SKILLS.md b/SKILLS.md index c92734c..c34df9a 100644 --- a/SKILLS.md +++ b/SKILLS.md @@ -26,6 +26,7 @@ Load the ritual skills in `docs/skills/` for full protocol detail (all aligned t - [docs/skills/engram-session-end.md](docs/skills/engram-session-end.md) — Structured handoff packet (`session_end` JSON, COMPRESS, anchors). - [docs/skills/engram-thought-tiles.md](docs/skills/engram-thought-tiles.md) — Structured offload (mandatory for meta, promote_hot for re-hydration). - [docs/skills/engram-leg-wiki-starter.md](docs/skills/engram-leg-wiki-starter.md) — Bootstrap a personal knowledge wiki (LEG Browser + tiles). +- [docs/skills/engram-glassbox-rsi.md](docs/skills/engram-glassbox-rsi.md) — Hybrid fire goals + typed verify for scheduled loops + LEG glass box. ## Declarative Process Sheaf diff --git a/docs/skills/README.md b/docs/skills/README.md index e23c49f..97467ce 100644 --- a/docs/skills/README.md +++ b/docs/skills/README.md @@ -25,6 +25,7 @@ These are the operational skills and rituals that power the Engram geometric mem - Thought Tiles (`engram-thought-tiles`): For structured offload of plans, policies, knowledge graphs. Mandatory for meta-work. - Personal wiki (`engram-leg-wiki-starter`): Bootstrap and maintain a compounding knowledge wiki with LEG Browser. See [docs/PERSONAL_KNOWLEDGE_WIKI.md](../PERSONAL_KNOWLEDGE_WIKI.md). +- Glass-Box RSI (`engram-glassbox-rsi`): Hybrid fire goals + typed verify for scheduled Dual RSI / Ship / PR / Stale / Aliveness loops; LEG glass box. See [engram-glassbox-rsi.md](engram-glassbox-rsi.md); loop bodies in [loop-prompts/](loop-prompts/). - Goal Stack (`engram-goal`): First-class intentional self-model. Primary goal auto-links to traces. - Spatial (Item 1.5): **lean:** `context_for_edit(path)`; **deep:** optional `watch_workspace` once per project. - Lawfulness: `mcp_engram_verify_manifold_integrity`, block lawfulness. diff --git a/docs/skills/engram-glassbox-rsi.md b/docs/skills/engram-glassbox-rsi.md new file mode 100644 index 0000000..93fa983 --- /dev/null +++ b/docs/skills/engram-glassbox-rsi.md @@ -0,0 +1,233 @@ +--- +name: engram-glassbox-rsi +--- + +# Engram Glass-Box RSI — Fire Lifecycle Skill + +**For scheduled loop operators** (Dual RSI, Ship, PR Watch, MCP Stale, Aliveness) on the Engram substrate. + +Every fire is a **child goal** under a durable parent, completed only after a **typed verify**. No silent success: stage flip, ship/PR claim, or “healthy” only with `verify_status=pass`. + +**Spec:** [docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md](../superpowers/specs/2026-07-10-glassbox-rsi-design.md) +**Schemas:** [docs/schemas/fire_verify_packet_v1.json](../schemas/fire_verify_packet_v1.json), [docs/schemas/dual_loop_state_v1.json](../schemas/dual_loop_state_v1.json) +**Control block:** `helper:rsi_dual_loop_state` +**LEG mirror:** `./scripts/leg --live` → `http://127.0.0.1:8765/?view=glassbox` (read-only) + +--- + +## When to use + +Use this skill for **any scheduled fire** of: + +| Fire | Typical loop id | +|------|-----------------| +| Dual RSI (S / G / M one-track) | `dual_rsi` | +| Ship gate (dirty-tree → test → PR) | `ship_gate` | +| PR watch (remote CI rollup) | `pr_watch` | +| MCP stale (binary vs process) | `mcp_stale` | +| Aliveness bench | `aliveness` | + +Also use when manually replaying a fire dry-run or recovering a failed verify (mint new child; do not rewrite history of a completed fire). + +**Do not use** for ordinary code-edit sessions that are not loop fires — use [engram-working-memory.md](engram-working-memory.md) + Code Edit Ritual instead. + +--- + +## Parent goals (durable) + +Parents **serve** `goal:engram_mvp_v1`. Mint once (if missing); do not re-mint per fire. + +| Parent | Owns | +|--------|------| +| `goal:dual_rsi_program` | Tracks S/G/M, stage machine, corpus/PEFT | +| `goal:ship_substrate` | Dirty-tree → test → PR | +| `goal:glassbox_leg` | LEG split home + any glassbox API | + +| Parent | Typical fires | +|--------|----------------| +| `goal:dual_rsi_program` | Dual RSI S/G/M, MCP stale (often), Aliveness | +| `goal:ship_substrate` | Ship gate, PR watch | +| `goal:glassbox_leg` | LEG glassbox UI / API work (not every loop tick) | + +--- + +## Lifecycle (every fire) + +``` +session_start(intent=" fire") + → ack_wake_queue + → ensure parent exists + related to goal:engram_mvp_v1 + → read_concept(helper:rsi_dual_loop_state) # track_next, open_pr, last_verify, … + → mint child goal (active, verify_status=pending) + → act (ONE track / one ship / one PR check / one stale probe / one aliveness write) + → run typed verify → fill verify packet + → IF pass: goal_update_status(child, completed) + update dual_loop (last_fire_goal, last_verify, …) + → IF fail: goal_update_status(child, blocked|abandoned) + scar if repeated + dual_loop blockers + → session_end(summary MUST include child goal id + verify_status) +``` + +**HARD constraints** + +- **No stage flip** in `helper:rsi_dual_loop_state` (or Gemma stage metrics) without `verify_status=pass`. +- **No ship/PR claim** (PR URL, “shipped”, ready-to-merge) without `verify_status=pass` on the matching gate (`ship_local` / `ship_skip` / `ci_status`). +- **No multi-track** Dual RSI in one fire — one of S, G, or M only. +- **No pack dumps** in chat (paths + short summaries only). +- LEG is **read-only**; it does not run loops or complete goals. +- No auto-merge. MCP auto-restart only if `ENGRAM_ALLOW_MCP_RESTART=1`. + +--- + +## Child goal mint recipe + +At fire start, mint a child under the correct parent: + +```text +mcp_engram_goal_create( + goal_id="fire___", + parent="goal:dual_rsi_program", # or goal:ship_substrate | goal:glassbox_leg + statement="One-line fire intent (track / ship / PR / stale / aliveness)", + priority="medium", + affirm="What this fire advances if verify passes", + deny="What is out of scope or rejected this fire (e.g. multi-track, auto-merge)", + reconcile="How this fire compounds parent + engram_mvp_v1 continuity" +) +``` + +**Naming** + +- Concept becomes `goal:fire___` when `goal_id` omits the `goal:` prefix (MCP mints `goal:`). +- Prefer stable, sortable ids: e.g. `fire_dual_rsi_sess1783716771_1720638000`. + +**Optional:** `mcp_engram_goal_set_primary` to the child for the duration of the fire so traces auto-link; restore parent/primary at session_end if needed. + +--- + +## Verify packet (required fields) + +Embed in goal update note and/or `mcp_engram_remember` as `metric:verify_` (related to the child goal). Schema: `fire_verify_packet_v1`. + +| Field | Meaning | +|-------|---------| +| `parent` | Durable parent goal id (e.g. `goal:dual_rsi_program`) | +| `loop` | `dual_rsi` \| `ship_gate` \| `pr_watch` \| `mcp_stale` \| `aliveness` \| … | +| `track` | `S` \| `G` \| `M` \| `null` (null when not Dual RSI) | +| `intent` | One-line fire intent | +| `verify_type` | Typed gate id (table below) | +| `verify_status` | `pending` \| `pass` \| `fail` | +| `verify_evidence` | Paths, test summary, CI URL, metric concept | +| `falsify` | What would reverse this fire | + +### Paste template (YAML) + +```yaml +parent: goal:dual_rsi_program +loop: dual_rsi +track: S # S | G | M | null +intent: "Dual RSI track S — one substrate win" +verify_type: substrate_local +verify_status: pending # → pass | fail after gate +verify_evidence: "" # fill on verify +falsify: "Artifact path missing or integrity sample fails" +``` + +### JSON equivalent + +```json +{ + "parent": "goal:dual_rsi_program", + "loop": "dual_rsi", + "track": "S", + "intent": "Dual RSI track S — one substrate win", + "verify_type": "substrate_local", + "verify_status": "pending", + "verify_evidence": "", + "falsify": "Artifact path missing or integrity sample fails" +} +``` + +Validate samples offline: `python3 scripts/validate_dual_loop_schema.py` / `python3 scripts/test_glassbox_schemas.py -v`. + +--- + +## Typed gates + +| Loop | `verify_type` | Pass means | +|------|---------------|------------| +| Dual RSI **S** | `substrate_local` | Disk artifact and/or targeted test + integrity sample; no pack dump in chat | +| Dual RSI **G** | `gemma_stage` | Stage advanced + metric atom status ok (`peft_metrics` / `eval_gate` / future `gguf_lora`) | +| Dual RSI **M** | `meta_policy` | dual_loop updated with rationale; optional scar | +| Ship | `ship_local` | Tests green + commit + PR URL | +| Ship (clean tree) | `ship_skip` | Explicit skip — complete child; **grey skip**, not green hero | +| PR watch | `ci_status` | Check rollup recorded; all **required** checks SUCCESS for ready-to-merge; else not ready | +| MCP stale | `binary_vs_proc` | FRESH / STALE / OFFLINE atom; restart only if allowed | +| Aliveness | `metrics_atom` | `metric:dual_rsi_aliveness_*` written and related | + +### Failure handling (process) + +| Failure | Process | +|---------|---------| +| Verify fail | Child → `blocked` (or `abandoned`); scar if repeated; **no** stage flip / ready claim | +| Flaky / partial CI | Not ready-to-merge until all required checks SUCCESS | +| dual_loop missing | Still mint child; scar thin handoff | +| MCP STALE | Set `mcp_restart_required=true`; no auto-kill unless allowed | +| Doom loop (same fail 2×) | Scar + stop fixing that fire | +| Ship skip (clean tree) | Child complete with `ship_skip` | + +--- + +## dual_loop update after verify + +On pass or fail, update `helper:rsi_dual_loop_state` (via `mcp_engram_update`) so LEG can mirror: + +- `last_fire_goal` — child `goal:fire_*` id +- `last_verify` — `{ type, status, at }` +- Dual RSI: `track_last` / `track_next`, gemma stage fields when G passes +- Ship/PR: `open_pr` when applicable +- Stale: `mcp_restart_required` + +Schema extensions: [docs/schemas/dual_loop_state_v1.json](../schemas/dual_loop_state_v1.json). + +--- + +## Loop prompts (canonical bodies) + +Paste-ready scheduler / operator prompts live under: + +**[docs/skills/loop-prompts/](loop-prompts/)** + +| Prompt (v2) | Parent | Default verify_type | +|-------------|--------|---------------------| +| `dual_rsi_v2.md` | `goal:dual_rsi_program` | `substrate_local` \| `gemma_stage` \| `meta_policy` by track | +| `ship_gate_v2.md` | `goal:ship_substrate` | `ship_local` or `ship_skip` | +| `pr_watch_v2.md` | `goal:ship_substrate` | `ci_status` | +| `mcp_stale_v2.md` | dual_rsi or ship parent | `binary_vs_proc` | +| `aliveness_bench_v2.md` | `goal:dual_rsi_program` | `metrics_atom` | + +If a prompt file is not yet present, still follow **this skill’s lifecycle + verify packet**; do not run a fire without mint + typed verify. Task 3 lands the prompt bodies. + +--- + +## session_end contract + +`mcp_engram_session_end` summary **must** include: + +1. Child goal id (`goal:fire_…`) +2. `verify_status` (`pass` \| `fail` \| still `pending` only if abandoned mid-fire with note) +3. `verify_type` + one-line evidence +4. Parent goal and loop id +5. Whether dual_loop was updated + +Use `prepare_compression=true` so the next wake rehydrates fire lineage. + +--- + +## Related + +- Wake / work / handoff: [engram-wake-up.md](engram-wake-up.md), [engram-working-memory.md](engram-working-memory.md), [engram-session-end.md](engram-session-end.md) +- 8-tool contract: [docs/AGENT_MEMORY_CONTRACT.md](../AGENT_MEMORY_CONTRACT.md) +- Design + plan: [2026-07-10-glassbox-rsi-design.md](../superpowers/specs/2026-07-10-glassbox-rsi-design.md), [2026-07-10-glassbox-rsi.md](../superpowers/plans/2026-07-10-glassbox-rsi.md) +- LEG Browser: [docs/LEG_BROWSER.md](../LEG_BROWSER.md) + +--- + +*Glass-box rule: if LEG (or dual_loop) cannot show what the last fire claimed and proved, the fire is incomplete.* From 3c2d4f0370277eba72e8380333fa929479b6dcc3 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:56:02 -0700 Subject: [PATCH 06/16] docs: glassbox loop prompts v2 with fire goals and typed verify --- docs/skills/loop-prompts/README.md | 59 ++++++++ .../skills/loop-prompts/aliveness_bench_v2.md | 106 ++++++++++++++ docs/skills/loop-prompts/dual_rsi_v2.md | 133 ++++++++++++++++++ docs/skills/loop-prompts/mcp_stale_v2.md | 109 ++++++++++++++ docs/skills/loop-prompts/pr_watch_v2.md | 108 ++++++++++++++ docs/skills/loop-prompts/ship_gate_v2.md | 111 +++++++++++++++ 6 files changed, 626 insertions(+) create mode 100644 docs/skills/loop-prompts/README.md create mode 100644 docs/skills/loop-prompts/aliveness_bench_v2.md create mode 100644 docs/skills/loop-prompts/dual_rsi_v2.md create mode 100644 docs/skills/loop-prompts/mcp_stale_v2.md create mode 100644 docs/skills/loop-prompts/pr_watch_v2.md create mode 100644 docs/skills/loop-prompts/ship_gate_v2.md diff --git a/docs/skills/loop-prompts/README.md b/docs/skills/loop-prompts/README.md new file mode 100644 index 0000000..cb8f871 --- /dev/null +++ b/docs/skills/loop-prompts/README.md @@ -0,0 +1,59 @@ +# Loop prompts (Glass-Box RSI v2) + +Canonical **scheduler bodies** for Engram Glass-Box RSI. Every fire mints a child `goal:fire_*`, runs a **typed verify**, then updates `helper:rsi_dual_loop_state.last_fire_goal` + `last_verify`. + +**Operator skill:** [engram-glassbox-rsi.md](../engram-glassbox-rsi.md) +**Schemas:** [fire_verify_packet_v1.json](../../schemas/fire_verify_packet_v1.json), [dual_loop_state_v1.json](../../schemas/dual_loop_state_v1.json) +**Spec / plan:** [glassbox-rsi design](../../superpowers/specs/2026-07-10-glassbox-rsi-design.md), [implementation plan](../../superpowers/plans/2026-07-10-glassbox-rsi.md) + +Do **not** leave verify out — LEG glassbox depends on fire goals + `last_verify`. + +--- + +## Prompts + +| File | Loop | Parent | `verify_type` | +|------|------|--------|----------------| +| [dual_rsi_v2.md](dual_rsi_v2.md) | `dual_rsi` | `goal:dual_rsi_program` | `substrate_local` \| `gemma_stage` \| `meta_policy` (by S/G/M track) | +| [ship_gate_v2.md](ship_gate_v2.md) | `ship_gate` | `goal:ship_substrate` | `ship_local` or `ship_skip` | +| [pr_watch_v2.md](pr_watch_v2.md) | `pr_watch` | `goal:ship_substrate` | `ci_status` (ready only if **all** required checks SUCCESS) | +| [mcp_stale_v2.md](mcp_stale_v2.md) | `mcp_stale` | dual_rsi or ship | `binary_vs_proc` | +| [aliveness_bench_v2.md](aliveness_bench_v2.md) | `aliveness` | `goal:dual_rsi_program` | `metrics_atom` | + +Each file has a **paste-ready** fenced block for the scheduler prompt field. + +--- + +## Reschedule + +After editing any prompt body: + +1. Copy the fenced prompt from the file. +2. Call **`scheduler_create`** with the new prompt (and desired interval). +3. Cancel or replace the previous scheduled job if your harness keeps old ids. + +Do not assume in-repo files auto-update live schedulers — **reschedule with `scheduler_create`**. + +Suggested intervals (operator choice): Dual RSI ~20m; PR watch while `open_pr` set ~15–30m; MCP stale after rebuilds; Aliveness ~30–60m; Ship gate on demand / after substrate wins. + +--- + +## Shared HARD rules (all loops) + +- `session_start` + `ack_wake_queue` every fire. +- Mint child `goal:fire_*` under durable parent before acting. +- Typed verify **before** `goal_update_status` → completed. +- Update `helper:rsi_dual_loop_state` with `last_fire_goal` + `last_verify`. +- `session_end` summary includes fire goal id + `verify_status`. +- **No** full packs in chat; **no** multi-track Dual RSI; **no** force-push; **no** auto-merge. +- **No** auto MCP kill/restart unless `ENGRAM_ALLOW_MCP_RESTART=1`. +- No stage flip / ship-shipped / ready-to-merge without `verify_status=pass` on the matching gate. +- LEG Browser is **read-only** — it does not run these loops. + +--- + +## Related + +- Wake / handoff: [engram-wake-up.md](../engram-wake-up.md), [engram-session-end.md](../engram-session-end.md) +- 8-tool contract: [AGENT_MEMORY_CONTRACT.md](../../AGENT_MEMORY_CONTRACT.md) +- LEG: [LEG_BROWSER.md](../../LEG_BROWSER.md) (`?view=glassbox`) diff --git a/docs/skills/loop-prompts/aliveness_bench_v2.md b/docs/skills/loop-prompts/aliveness_bench_v2.md new file mode 100644 index 0000000..c6f2578 --- /dev/null +++ b/docs/skills/loop-prompts/aliveness_bench_v2.md @@ -0,0 +1,106 @@ +# Aliveness Bench v2 (glassbox) + +**Loop id:** `aliveness` +**Parent:** `goal:dual_rsi_program` +**Interval (suggested):** periodic health (e.g. 30–60m) +**Skill:** [engram-glassbox-rsi.md](../engram-glassbox-rsi.md) +**Control:** `helper:rsi_dual_loop_state` + `metric:dual_rsi_aliveness_*` + +Reschedule after edits: paste the fenced prompt body below into `scheduler_create`. + +--- + +## Paste-ready scheduler prompt + +``` +ALIVENESS BENCH v2 — metrics_atom + fire goal + typed verify + +Working dir: Engram repo root. Use Engram MCP (search_tool then use_tool). ENGRAM_PROFILE=agent. +Purpose: write a single aliveness metrics atom for LEG/dual_loop health strip — not a multi-track RSI fire. + +LIFECYCLE (do not skip steps) + +1. session_start(intent="aliveness fire — metrics_atom") +2. ack_wake_queue(executed=true) +3. Ensure parent exists: + - read_concept("goal:dual_rsi_program") + - if missing: goal_create(goal_id="dual_rsi_program", + statement="Dual RSI substrate+Gemma stage machine with typed verify", + parent="goal:engram_mvp_v1", priority="high") +4. read_concept("helper:rsi_dual_loop_state") — track_next, gemma.stage, open_pr, mcp_restart_required +5. Mint child fire goal: + goal_create( + goal_id="fire_aliveness__", + parent="goal:dual_rsi_program", + statement="Aliveness bench — write metrics atom", + priority="medium", + affirm="metric:dual_rsi_aliveness_* written + related", + deny="pack dumps; multi-track S/G/M work; fake high fidelity", + reconcile="Feeds LEG glassbox health strip under dual_rsi_program" + ) + Verify packet pending: + parent: goal:dual_rsi_program + loop: aliveness + track: null + intent: "Aliveness bench — metrics atom" + verify_type: metrics_atom + verify_status: pending + verify_evidence: "" + falsify: "atom not written or not related; invented fidelity without tool evidence" + +6. Act (probe + write — no dual RSI track execution): + Collect what is cheaply available (use tools; do not invent): + - cold_start_fidelity / session readiness if available (session_start already may include) + - mean hub CRS / verify_manifold_integrity sample (optional, sample only) + - hermies endpoint health if dual_loop lists endpoint (e.g. :11435) — cos/dim if known + - gemma.stage, track_next, peft/adapter pointer from dual_loop + - leg_block_count / open_scars_count if readiness or summarize exposes them + - open_pr, mcp_restart_required from dual_loop + Write atom via remember (concept name pattern): + metric:dual_rsi_aliveness_ + or metric:dual_rsi_aliveness__ if multiple same day + Content: compact one-block summary (fidelity, mean_hub_crs, hermies, stage, track_next, + leg_block_count, open_scars, mcp_restart_required). Paths/pointers only — no packs. + relate atom → goal:dual_rsi_program and helper:rsi_dual_loop_state (and parent fire goal). + +7. Typed verify (verify_type=metrics_atom): + PASS = metric:dual_rsi_aliveness_* exists with non-empty body AND related to parent + (and preferably dual_loop helper). + FAIL = no write; empty atom; no relation; fidelity claimed without source. + verify_evidence: concept id + key fields one-liner (e.g. fidelity=0.93 stage=eval_gate). + +8. goal_update_status: + - pass → completed + - fail → blocked; scar if repeated empty aliveness + +9. update helper:rsi_dual_loop_state: + - last_fire_goal = goal:fire_aliveness_... + - last_verify = { type: "metrics_atom", status: pass|fail, at: ISO-8601 } + - Do NOT flip track_next or gemma.stage from aliveness alone + - Optionally pointer field / note to latest aliveness concept if dual_loop allows free keys + +10. session_end(summary=..., prepare_compression=true) + Summary MUST include fire goal id, verify_status, metrics atom concept id, + parent goal:dual_rsi_program, dual_loop updated yes/no. + +HARD (never violate) +- This fire writes metrics_atom only — no multi-track Dual RSI, no ship/PR merge. +- No pack dumps in chat. +- No force-push. No auto-merge. +- No auto MCP kill unless ENGRAM_ALLOW_MCP_RESTART=1. +- Do not flip gemma stage or track_next from aliveness without a Dual RSI verify pass. +- Honest low fidelity / needs_review is allowed; do not polish numbers. +``` + +--- + +## Atom naming + +| Pattern | Use | +|---------|-----| +| `metric:dual_rsi_aliveness_YYYY-MM-DD` | Daily rollup | +| `metric:dual_rsi_aliveness_YYYY-MM-DD_HHMM` | Multiple fires same day | + +## dual_loop fields this loop owns + +`last_fire_goal`, `last_verify` (type `metrics_atom`). Does **not** own `track_next` / `gemma.stage`. diff --git a/docs/skills/loop-prompts/dual_rsi_v2.md b/docs/skills/loop-prompts/dual_rsi_v2.md new file mode 100644 index 0000000..d6abf14 --- /dev/null +++ b/docs/skills/loop-prompts/dual_rsi_v2.md @@ -0,0 +1,133 @@ +# Dual RSI v2 (glassbox) + +**Loop id:** `dual_rsi` +**Parent:** `goal:dual_rsi_program` +**Interval (suggested):** 20m (operator schedules via `scheduler_create`) +**Skill:** [engram-glassbox-rsi.md](../engram-glassbox-rsi.md) +**Control:** `helper:rsi_dual_loop_state` + +Reschedule after edits: paste the fenced prompt body below into `scheduler_create`. + +--- + +## Paste-ready scheduler prompt + +``` +DUAL RSI v2 — ONE track + fire goal + typed verify + +Working dir: Engram repo root. Use Engram MCP (search_tool then use_tool). ENGRAM_PROFILE=agent. + +LIFECYCLE (do not skip steps) + +1. session_start(intent="dual_rsi fire — one track + typed verify") +2. ack_wake_queue(executed=true) before any context_for_edit +3. Ensure parent exists: + - read_concept("goal:dual_rsi_program") + - if missing: goal_create(goal_id="dual_rsi_program", + statement="Dual RSI substrate+Gemma stage machine with typed verify", + parent="goal:engram_mvp_v1", priority="high", + affirm="One S/G/M win per fire with verify", deny="multi-track; pack dumps; stage flip without pass") +4. read_concept("helper:rsi_dual_loop_state") → TRACK = track_next (must be S|G|M) + If dual_loop missing: scar thin handoff; still mint child with TRACK=S default only if no better signal. +5. Mint child fire goal: + goal_create( + goal_id="fire_dual_rsi__", + parent="goal:dual_rsi_program", + statement="Dual RSI track — one win", + priority="medium", + affirm="Advance dual_rsi_program on pass", + deny="multi-track; packs in chat; stage flip without verify pass", + reconcile="Compounds engram_mvp_v1 continuity + PEFT path" + ) + Embed verify packet (status=pending) in goal note / remember metric:verify_: + parent: goal:dual_rsi_program + loop: dual_rsi + track: + intent: "Dual RSI track — one win" + verify_type: + verify_status: pending + verify_evidence: "" + falsify: + Optional: goal_set_primary to the child for this fire. + +6. Execute ONE track only (TRACK from dual_loop). Never S+G or G+M in one fire. + + === TRACK S (substrate) — verify_type=substrate_local === + Win = one substrate/continuity artifact without chat pack dumps. + Preferred path: + - Prefer mcp_engram_leg_corpus (or equivalent) build that writes disk_export_path + under data/lora-export/ (or ENGRAM_LORA_EXPORT_DIR). Chat must show packs=[] unless + ENGRAM_LORA_EXPORT_INLINE=1. + - Or targeted cargo/test + integrity sample for a small substrate fix. + - Optional grow: scripts/grow_leg_sft.sh after disk pack batch exists. + Falsify: disk path missing; packs dumped in chat; integrity sample fails. + + === TRACK G (Gemma stage) — verify_type=gemma_stage === + Advance ONE stage on the stage machine when possible: + offline → hermies_up → packs → jsonl → peft_metrics → adapter_live → eval_gate + (post-eval optional: gguf_lora — blocked until llama.cpp maps Gemma4 LoRA tensors) + Typical tools/scripts (pick next unfinished stage only): + - hermies_up: endpoint http://127.0.0.1:11435 healthy; record cos/dim + - packs/jsonl: disk batch → scripts/export_leg_corpus_jsonl.py → leg_geometry_sft.jsonl + - peft_metrics / adapter_live: scripts/peft_leg_geometry_train.py → peft_metrics.json + adapter_path + - eval_gate: scripts/eval_leg_geometry_gate.py → eval_gate_metrics.json + Do NOT flip gemma.stage in dual_loop until verify pass. + Falsify: stage metric file missing/status not ok; claimed stage without artifact path. + + === TRACK M (meta policy) — verify_type=meta_policy === + Policy-only fire: read dual_loop + last verifies + open_pr/blockers; write rationale; + set track_next to S|G|M with justification; optional scar on friction/doom loop. + No large code ship; no PEFT train; no multi-track work disguised as meta. + Falsify: dual_loop not updated with rationale; silent track flip without note. + +7. Typed verify (required before complete): + S: disk path exists OR cargo/test summary + integrity sample; packs not in chat + G: stage metric file/status ok (peft_metrics / eval_gate / adapter path as claimed) + M: dual_loop rationale written (and track_next set intentionally) + Fill verify_status=pass|fail + verify_evidence (paths, row counts, metrics concept). + Optional: remember metric:verify_ related to child goal. + +8. goal_update_status on fire child: + - pass → completed + - fail → blocked (or abandoned); scar if same fail twice (doom loop → stop) + Never claim stage advanced / substrate shipped without pass. + +9. update helper:rsi_dual_loop_state via mcp_engram_update (always, pass or fail): + - track_last = TRACK (if work ran) + - track_next = next track (only advance stage fields / optimistic next on pass) + - last_fire_goal = goal:fire_dual_rsi_... + - last_verify = { type: , status: pass|fail, at: ISO-8601 } + - gemma.* only advanced on G verify pass + - parents includes goal:dual_rsi_program + On fail: do not flip gemma.stage; set blockers / scar if repeated. + +10. session_end(summary=..., prepare_compression=true) + Summary MUST include: + - fire goal id (goal:fire_dual_rsi_...) + - verify_status (pass|fail) + - verify_type + one-line evidence + - TRACK + parent goal:dual_rsi_program + - whether dual_loop was updated + +HARD (never violate) +- ONE track per fire (no multi-track). +- No full packs / pack dumps in chat — paths + short summaries only. +- No stage flip in dual_loop or Gemma metrics without verify_status=pass. +- No force-push. No auto-merge. No auto MCP kill/restart unless ENGRAM_ALLOW_MCP_RESTART=1. +- LEG is read-only; does not run this loop. +- Token economy: state atom + pointers; avoid re-mint core lexicon / manifesto re-read. +``` + +--- + +## Track → verify_type map + +| TRACK | `verify_type` | Pass means | +|-------|---------------|------------| +| S | `substrate_local` | Disk artifact and/or targeted test + integrity; no pack dump | +| G | `gemma_stage` | Stage advanced + metric atom / path ok | +| M | `meta_policy` | dual_loop updated with rationale; optional scar | + +## dual_loop fields this loop owns + +`track_last`, `track_next`, `last_fire_goal`, `last_verify`, `gemma.*` (on G pass), `rationale`, blockers. diff --git a/docs/skills/loop-prompts/mcp_stale_v2.md b/docs/skills/loop-prompts/mcp_stale_v2.md new file mode 100644 index 0000000..87c296f --- /dev/null +++ b/docs/skills/loop-prompts/mcp_stale_v2.md @@ -0,0 +1,109 @@ +# MCP Stale v2 (glassbox) + +**Loop id:** `mcp_stale` +**Parent:** `goal:dual_rsi_program` (preferred) or `goal:ship_substrate` if checking post-ship binary +**Interval (suggested):** after server rebuilds / ship merges, or periodic (e.g. 30–60m) +**Skill:** [engram-glassbox-rsi.md](../engram-glassbox-rsi.md) +**Control:** `helper:rsi_dual_loop_state` (`mcp_restart_required`, `last_verify`) + +Reschedule after edits: paste the fenced prompt body below into `scheduler_create`. + +--- + +## Paste-ready scheduler prompt + +``` +MCP STALE v2 — binary vs process + fire goal + typed verify (binary_vs_proc) + +Working dir: Engram repo root. Use Engram MCP (search_tool then use_tool). ENGRAM_PROFILE=agent. +Purpose: detect whether live MCP/engram process is older than target/debug/engram (or installed) binary. + +LIFECYCLE (do not skip steps) + +1. session_start(intent="mcp_stale fire — binary_vs_proc") +2. ack_wake_queue(executed=true) +3. Ensure parent exists (prefer dual RSI program; ship parent ok if fire is post-ship): + - read_concept("goal:dual_rsi_program") and/or "goal:ship_substrate" + - if dual_rsi_program missing: goal_create(goal_id="dual_rsi_program", + statement="Dual RSI substrate+Gemma stage machine with typed verify", + parent="goal:engram_mvp_v1", priority="high") + PARENT = goal:dual_rsi_program # or goal:ship_substrate when explicitly post-ship +4. read_concept("helper:rsi_dual_loop_state") — prior mcp_restart_required, open_pr +5. Mint child fire goal: + goal_create( + goal_id="fire_mcp_stale__", + parent="", + statement="MCP stale check — binary vs process", + priority="medium", + affirm="Honest FRESH|STALE|OFFLINE atom; restart only if allowed", + deny="auto-kill MCP without ENGRAM_ALLOW_MCP_RESTART=1; silent STALE", + reconcile="Keeps agent MCP on current binary for dual_rsi/ship honesty" + ) + Verify packet pending: + parent: + loop: mcp_stale + track: null + intent: "MCP stale — binary_vs_proc" + verify_type: binary_vs_proc + verify_status: pending + verify_evidence: "" + falsify: "claimed FRESH while process start < binary mtime; or auto-killed without allow" + +6. Act (probe only + optional allowed restart): + a. Resolve binary path (prefer): /home/a/Documents/Engram/target/debug/engram + (or `which engram` / build path from env). Record mtime epoch + ISO. + b. Resolve process: engram mcp / engram-server / host MCP pid (ps, /proc/, or + scripts/engram-mcp-health.sh if present). Record start epoch / elapsed. + c. Classify overall: + OFFLINE — no process found + STALE — process start_epoch < binary mtime epoch (binary newer than process) + FRESH — process running and start_epoch >= binary mtime epoch + d. Restart policy: + - Default: DO NOT kill or restart MCP. + - Only if ENGRAM_ALLOW_MCP_RESTART=1 AND operator intent allows: restart once, + re-probe, record outcome. + - Never force-kill unrelated processes; never force-push as part of this loop. + +7. Typed verify (verify_type=binary_vs_proc): + PASS = classification atom written with evidence (binary path+mtime, pid+start, overall). + FRESH, STALE, and OFFLINE can all be verify pass if honestly recorded. + FAIL = no probe; contradictory claim (e.g. FRESH with process older than binary); + unauthorized restart/kill attempted. + verify_evidence: overall=…; binary=… mtime=…; pid=… start=…; allow_restart=0|1 + +8. goal_update_status: + - pass → completed + - fail → blocked; scar if repeated false FRESH or unauthorized restart + +9. update helper:rsi_dual_loop_state: + - last_fire_goal = goal:fire_mcp_stale_... + - last_verify = { type: "binary_vs_proc", status: pass|fail, at: ISO-8601 } + - mcp_restart_required = true if STALE or OFFLINE (and still needs restart); + false if FRESH + - Optional short note atom: remember metric:mcp_stale_ related to parent + +10. session_end(summary=..., prepare_compression=true) + Summary MUST include fire goal id, verify_status, overall FRESH|STALE|OFFLINE, + mcp_restart_required, parent id, dual_loop updated. + +HARD (never violate) +- No auto MCP kill/restart unless ENGRAM_ALLOW_MCP_RESTART=1. +- No force-push. No auto-merge. +- No pack dumps in chat. +- Honest STALE is success of the *check*; do not hide restart debt. +- Do not multi-track Dual RSI or ship code in this fire. +``` + +--- + +## Classification + +| overall | Meaning | `mcp_restart_required` | +|---------|---------|------------------------| +| FRESH | process ≥ binary mtime | false | +| STALE | binary newer than process | true | +| OFFLINE | no process | true (needs start) | + +## dual_loop fields this loop owns + +`mcp_restart_required`, `last_fire_goal`, `last_verify` (type `binary_vs_proc`). diff --git a/docs/skills/loop-prompts/pr_watch_v2.md b/docs/skills/loop-prompts/pr_watch_v2.md new file mode 100644 index 0000000..1b6f24a --- /dev/null +++ b/docs/skills/loop-prompts/pr_watch_v2.md @@ -0,0 +1,108 @@ +# PR Watch v2 (glassbox) + +**Loop id:** `pr_watch` +**Parent:** `goal:ship_substrate` +**Interval (suggested):** frequent while `open_pr` set (e.g. 15–30m) +**Skill:** [engram-glassbox-rsi.md](../engram-glassbox-rsi.md) +**Control:** `helper:rsi_dual_loop_state` (`open_pr`, `last_verify`) + +Reschedule after edits: paste the fenced prompt body below into `scheduler_create`. + +--- + +## Paste-ready scheduler prompt + +``` +PR WATCH v2 — remote CI rollup + fire goal + typed verify (ci_status) + +Working dir: Engram repo root. Use Engram MCP (search_tool then use_tool). ENGRAM_PROFILE=agent. +Remote checks via gh (GitHub CLI). This loop does NOT run ship_local tests as the gate of record. + +LIFECYCLE (do not skip steps) + +1. session_start(intent="pr_watch fire — CI rollup + ready honesty") +2. ack_wake_queue(executed=true) +3. Ensure parent exists: + - read_concept("goal:ship_substrate") + - if missing: goal_create(goal_id="ship_substrate", + statement="Ship substrate code with local verify then PR", + parent="goal:engram_mvp_v1", priority="high") +4. read_concept("helper:rsi_dual_loop_state") → OPEN_PR = open_pr + If OPEN_PR null: still mint child; verify may pass with evidence "no open_pr — nothing to watch" + (not ready-to-merge). Prefer not inventing a PR. +5. Mint child fire goal: + goal_create( + goal_id="fire_pr_watch__", + parent="goal:ship_substrate", + statement="PR watch — CI rollup for open PR", + priority="medium", + affirm="Honest ready-to-merge only if all required checks SUCCESS", + deny="ready on partial/red CI; auto-merge; multi-fix shotgun", + reconcile="Protects ship_substrate merge honesty under engram_mvp_v1" + ) + Verify packet pending: + parent: goal:ship_substrate + loop: pr_watch + track: null + intent: "PR watch — CI rollup" + verify_type: ci_status + verify_status: pending + verify_evidence: "" + falsify: "ready-to-merge claimed while any required check not SUCCESS" + +6. Act (single PR check; optional ONE narrow CI fix): + a. Resolve PR: dual_loop.open_pr or gh pr view / gh pr list for current branch. + b. Collect check rollup (gh pr checks / gh pr view --json statusCheckRollup,state,mergeable). + c. Classify each required check: SUCCESS | FAILURE | PENDING | OTHER. + d. ready_to_merge = true ONLY if: + - PR open (or mergeable policy allows) + - AND every REQUIRED check is SUCCESS + Partial matrix (one job fail, one pass) ⇒ ready_to_merge=false (yellow honesty). + e. Fix budget: at most ONE narrow CI fix this fire if clearly local/flake-actionable. + Second same failure → scar + stop (doom loop). No drive-by refactors. + f. Never gh pr merge / never enable auto-merge. + +7. Typed verify (verify_type=ci_status): + PASS = check rollup recorded honestly (paths: PR URL, states, ready_to_merge true|false). + Pass does NOT require ready_to_merge=true — honest "not ready" is a pass. + FAIL = missing rollup; or claimed ready_to_merge while any required check ≠ SUCCESS; + or auto-merge attempted. + verify_evidence must include: PR URL, required check names+states, ready_to_merge boolean. + +8. goal_update_status: + - pass → completed + - fail → blocked; scar if repeated honesty violation or same CI fail twice with no progress + +9. update helper:rsi_dual_loop_state: + - last_fire_goal = goal:fire_pr_watch_... + - last_verify = { type: "ci_status", status: pass|fail, at: ISO-8601 } + - open_pr = current PR URL (or null if closed/merged — do not claim merge without evidence) + - Do not set any "ready" field that contradicts required-check rollup + +10. session_end(summary=..., prepare_compression=true) + Summary MUST include fire goal id, verify_status, ci_status evidence one-liner, + ready_to_merge true|false, PR URL, parent goal:ship_substrate, dual_loop updated. + +HARD (never violate) +- ready_to_merge only if ALL required checks are SUCCESS. +- No auto-merge. No force-push. +- One narrow CI fix max per fire; second same failure → scar + stop. +- Flaky/partial CI = not ready (yellow), never green ready. +- No pack dumps in chat. +- No auto MCP kill unless ENGRAM_ALLOW_MCP_RESTART=1. +- Ship local tests are ship_gate's job; do not re-label red remote CI as ship_local pass. +``` + +--- + +## Ready-to-merge rule + +| Required checks | `ready_to_merge` | +|-----------------|------------------| +| All SUCCESS | may be true | +| Any FAILURE / PENDING / missing required | **false** | +| No open PR | false (watch no-op; verify can still pass with evidence) | + +## dual_loop fields this loop owns + +`open_pr`, `last_fire_goal`, `last_verify` (type `ci_status`). diff --git a/docs/skills/loop-prompts/ship_gate_v2.md b/docs/skills/loop-prompts/ship_gate_v2.md new file mode 100644 index 0000000..2c358fe --- /dev/null +++ b/docs/skills/loop-prompts/ship_gate_v2.md @@ -0,0 +1,111 @@ +# Ship Gate v2 (glassbox) + +**Loop id:** `ship_gate` +**Parent:** `goal:ship_substrate` +**Interval (suggested):** operator-defined (often after dual RSI substrate wins) +**Skill:** [engram-glassbox-rsi.md](../engram-glassbox-rsi.md) +**Control:** `helper:rsi_dual_loop_state` (`open_pr`, `last_fire_goal`, `last_verify`) + +Reschedule after edits: paste the fenced prompt body below into `scheduler_create`. + +--- + +## Paste-ready scheduler prompt + +``` +SHIP GATE v2 — dirty-tree → local test → commit/PR + fire goal + typed verify + +Working dir: Engram repo root. Use Engram MCP (search_tool then use_tool). ENGRAM_PROFILE=agent. + +LIFECYCLE (do not skip steps) + +1. session_start(intent="ship_gate fire — local verify then PR or ship_skip") +2. ack_wake_queue(executed=true) before any context_for_edit +3. Ensure parent exists: + - read_concept("goal:ship_substrate") + - if missing: goal_create(goal_id="ship_substrate", + statement="Ship substrate code with local verify then PR", + parent="goal:engram_mvp_v1", priority="high", + affirm="Green local tests + PR or honest ship_skip", + deny="claim ship without tests; auto-merge; force-push") +4. read_concept("helper:rsi_dual_loop_state") — note open_pr, last_verify, mcp_restart_required +5. Mint child fire goal: + goal_create( + goal_id="fire_ship_gate__", + parent="goal:ship_substrate", + statement="Ship gate — local tests then PR or skip", + priority="medium", + affirm="Ship only with ship_local pass or honest ship_skip", + deny="force-push; auto-merge; ship claim without green local tests", + reconcile="Advances ship_substrate under engram_mvp_v1" + ) + Verify packet pending: + parent: goal:ship_substrate + loop: ship_gate + track: null + intent: "Ship gate — local tests then PR or skip" + verify_type: ship_local # or ship_skip if tree clean + verify_status: pending + verify_evidence: "" + falsify: "tests red; no PR URL on ship claim; dirty uncommitted ship claim" + +6. Act (single ship attempt): + a. git status / branch — prefer current feature branch (e.g. feat/*). Never force-push. + b. If working tree clean AND nothing meaningful to ship (no unpushed ship-worthy commits + that lack a PR, and no open ship work): + → verify_type=ship_skip, verify_status=pass after recording reason + → complete child as grey skip (not green hero) + → go to step 8–10 + c. If dirty or commits ready to ship: + - Run LOCAL tests only (this gate is not remote CI). Prefer targeted crate tests + relevant to the change; record summary (e.g. cargo test -p engram-server …). + - On red tests: stop. verify_status=fail. Do not open/claim PR as shipped. + - On green: commit if needed (conventional message; no secrets). Push with normal + push only (no --force / --force-with-lease unless human explicitly ordered — + default HARD: no force-push). + - Open or update PR via gh if not open; capture PR URL. + - verify_type=ship_local + +7. Typed verify: + ship_local PASS = local tests green + commit identity + PR URL recorded + ship_skip PASS = explicit clean-tree / nothing-to-ship rationale (grey skip) + FAIL = tests red, missing PR URL on ship claim, or forced through red tests + Fill verify_evidence: test summary, commit SHA, PR URL or skip reason. + +8. goal_update_status: + - pass (ship_local or ship_skip) → completed + - fail → blocked; scar if repeated same failure + Never claim "shipped" without ship_local pass. Never mark ready-to-merge here (that is pr_watch). + +9. update helper:rsi_dual_loop_state: + - last_fire_goal = goal:fire_ship_gate_... + - last_verify = { type: ship_local|ship_skip, status: pass|fail, at: ISO-8601 } + - open_pr = PR URL on ship_local pass (leave prior open_pr if ship_skip and PR still open) + - if server binary changed this ship: note mcp_restart_required may need stale check next + +10. session_end(summary=..., prepare_compression=true) + Summary MUST include fire goal id, verify_status, verify_type, PR URL or skip reason, + parent goal:ship_substrate, dual_loop updated yes/no. + +HARD (never violate) +- Ship verify = LOCAL tests only (remote CI is pr_watch). +- No force-push. +- No auto-merge. +- No pack dumps in chat. +- No auto MCP kill/restart unless ENGRAM_ALLOW_MCP_RESTART=1. +- Clean tree → ship_skip (complete child; grey skip, not green hero). +- Do not multi-track Dual RSI work inside ship_gate. +``` + +--- + +## Verify types + +| Situation | `verify_type` | Pass means | +|-----------|---------------|------------| +| Code to ship | `ship_local` | Tests green + commit + PR URL | +| Clean / nothing to ship | `ship_skip` | Explicit skip recorded | + +## dual_loop fields this loop owns + +`open_pr`, `last_fire_goal`, `last_verify` (type `ship_local` \| `ship_skip`). From 9110e4642924186dd07cfa4238bfd38d96e424c1 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:57:50 -0700 Subject: [PATCH 07/16] docs: runbook to mint glassbox parent goals via MCP --- scripts/mint_glassbox_parent_goals.md | 160 ++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 scripts/mint_glassbox_parent_goals.md diff --git a/scripts/mint_glassbox_parent_goals.md b/scripts/mint_glassbox_parent_goals.md new file mode 100644 index 0000000..e531013 --- /dev/null +++ b/scripts/mint_glassbox_parent_goals.md @@ -0,0 +1,160 @@ +# One-time: mint Glass-Box RSI parent goals + +**Operator runbook** (not a shell script). Engram MCP has no stable non-interactive batch in-repo without a client — agents execute this **once** via `search_tool` then `use_tool`. + +**When:** Phase A of [Glass-Box RSI](../docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md) before scheduled fires rely on durable parents. +**Skill:** [engram-glassbox-rsi.md](../docs/skills/engram-glassbox-rsi.md) +**Control block:** `helper:rsi_dual_loop_state` ([schema](../docs/schemas/dual_loop_state_v1.json)) + +Parents **serve** `goal:engram_mvp_v1`. Mint once if missing; do **not** re-mint per fire. + +| Parent | Owns | Priority | +|--------|------|----------| +| `goal:dual_rsi_program` | Dual RSI S/G/M, stage machine, corpus/PEFT | high | +| `goal:ship_substrate` | Dirty-tree → test → PR | high | +| `goal:glassbox_leg` | LEG Browser split-home glass box | medium | + +--- + +## Prerequisites + +1. `mcp_engram_session_start(intent="mint glassbox parent goals")` +2. `mcp_engram_ack_wake_queue(executed=true)` before any edit path +3. Confirm program root: `mcp_engram_read_concept(concept="goal:engram_mvp_v1")` (or recall anchors) + +**Idempotency:** `read_concept` each `goal:*` first. If present with a sensible statement, skip create; still run dual_loop parent list + `promote_hot` + verify. + +--- + +## Steps (MCP) + +Via Engram MCP (`search_tool` for live schema, then `use_tool` with qualified names): + +### 1. Create parent goals (parent = `goal:engram_mvp_v1`) + +``` +mcp_engram_goal_create + goal_id=dual_rsi_program + statement="Dual RSI substrate+Gemma stage machine with typed verify" + parent=goal:engram_mvp_v1 + priority=high + affirm="One S/G/M win per fire with typed verify" + deny="multi-track; pack dumps; stage flip without verify pass" + reconcile="Compounds engram_mvp_v1 continuity + PEFT path" +``` + +``` +mcp_engram_goal_create + goal_id=ship_substrate + statement="Ship substrate code with local verify then PR" + parent=goal:engram_mvp_v1 + priority=high + affirm="Ship only after ship_local / ship_skip verify pass" + deny="claim shipped or open PR without verify pass; force-push; auto-merge" + reconcile="Honest CI + PR path under engram_mvp_v1" +``` + +``` +mcp_engram_goal_create + goal_id=glassbox_leg + statement="LEG Browser split-home glass box for process visibility" + parent=goal:engram_mvp_v1 + priority=medium + affirm="Read-only process visibility for dual_loop + fire goals" + deny="LEG runs loops or auto-merges; silent success without last_verify" + reconcile="Operators see fire lifecycle without chat archaeology" +``` + +Concept names resolve as `goal:dual_rsi_program`, `goal:ship_substrate`, `goal:glassbox_leg`. + +### 2. Ensure serves / primary linkage + +If create did not attach parent edges, relate explicitly: + +``` +mcp_engram_relate from=goal:dual_rsi_program to=goal:engram_mvp_v1 label=serves +mcp_engram_relate from=goal:ship_substrate to=goal:engram_mvp_v1 label=serves +mcp_engram_relate from=goal:glassbox_leg to=goal:engram_mvp_v1 label=serves +``` + +(Use `search_tool` for exact `relate` parameter names on your MCP build.) + +Do **not** change `primary_goal` unless the operator is deliberately switching focus — parents sit under `engram_mvp_v1`. + +### 3. Update `helper:rsi_dual_loop_state` parents + schema fields + +``` +mcp_engram_read_concept(concept="helper:rsi_dual_loop_state") +``` + +Then `mcp_engram_update` (or remember-if-missing) so the control block includes at least: + +```json +{ + "version": 1, + "track_next": "S", + "mcp_restart_required": false, + "parents": [ + "goal:dual_rsi_program", + "goal:ship_substrate", + "goal:glassbox_leg" + ] +} +``` + +Preserve existing `track_next` / `open_pr` / `gemma` / `last_fire_goal` / `last_verify` when present — only ensure `version`, `parents`, and required fields. Validate shape against [dual_loop_state_v1.json](../docs/schemas/dual_loop_state_v1.json) (`python3 scripts/validate_dual_loop_schema.py` when the block is exported as JSON). + +### 4. Promote hot + +``` +mcp_engram_promote_hot(concept="goal:dual_rsi_program") +mcp_engram_promote_hot(concept="goal:ship_substrate") +mcp_engram_promote_hot(concept="goal:glassbox_leg") +mcp_engram_promote_hot(concept="helper:rsi_dual_loop_state") +``` + +Or one batch: + +``` +mcp_engram_promote_hot_batch(concepts=[ + "goal:dual_rsi_program", + "goal:ship_substrate", + "goal:glassbox_leg", + "helper:rsi_dual_loop_state" +]) +``` + +### 5. Verify + +``` +mcp_engram_goal_status / goal_get (each parent) — status active +mcp_engram_goal_get_children(parent="goal:engram_mvp_v1") — or goal_list; parents visible +mcp_engram_read_concept(concept="helper:rsi_dual_loop_state") — parents array complete +mcp_engram_recall(query="goal:dual_rsi_program", scope="anchors") — hit after promote +``` + +Optional: `mcp_engram_quick_trace` decision="minted glassbox parents" why="Phase A process contract for fire lifecycle". + +### 6. Session handoff + +``` +mcp_engram_session_end( + summary="Minted goal:dual_rsi_program, goal:ship_substrate, goal:glassbox_leg under engram_mvp_v1; dual_loop.parents set; promote_hot", + prepare_compression=true +) +``` + +--- + +## Acceptance + +- [ ] Three durable parents exist and serve `goal:engram_mvp_v1` +- [ ] `helper:rsi_dual_loop_state.parents` lists all three +- [ ] Goals + helper on hot path +- [ ] Child fires can use `parent=goal:dual_rsi_program|ship_substrate|glassbox_leg` without re-mint + +## Related + +- Loop prompts: [docs/skills/loop-prompts/](../docs/skills/loop-prompts/) +- Reschedule: [docs/skills/loop-prompts/RESCHEDULE.md](../docs/skills/loop-prompts/RESCHEDULE.md) +- Plan: [docs/superpowers/plans/2026-07-10-glassbox-rsi.md](../docs/superpowers/plans/2026-07-10-glassbox-rsi.md) Task 4 From 300c70f7850d8c75d992a50e2766d0ea4e3101b6 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:57:50 -0700 Subject: [PATCH 08/16] docs: AGENT_MEMORY_CONTRACT glassbox fire-goal pointer --- docs/AGENT_MEMORY_CONTRACT.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/AGENT_MEMORY_CONTRACT.md b/docs/AGENT_MEMORY_CONTRACT.md index 80ef012..c6ea079 100644 --- a/docs/AGENT_MEMORY_CONTRACT.md +++ b/docs/AGENT_MEMORY_CONTRACT.md @@ -89,6 +89,14 @@ Wake lean-avoid (no `watch_workspace` at wake) is separate and still applies. Th --- +## Glass-Box RSI (scheduled fires) + +Scheduled Dual RSI / Ship / PR / Stale / Aliveness fires **mint a child `goal:fire_*`**, run a **typed verify**, then update `helper:rsi_dual_loop_state.last_verify`. Do not flip stages or claim ship/PR ready without `verify_status=pass`. + +See: [docs/skills/engram-glassbox-rsi.md](skills/engram-glassbox-rsi.md), [docs/superpowers/specs/2026-07-10-glassbox-rsi-design.md](superpowers/specs/2026-07-10-glassbox-rsi-design.md). + +--- + ## Manage resume (TUI / MCP restart) After **TUI restart**, **MCP transport death**, or **`cargo build`** on `engram-server`, the live MCP may run a stale binary until restart. Resume without re-briefing: From 120af468211e66dd4015d1e2c632d71bc80af242 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:57:50 -0700 Subject: [PATCH 09/16] feat(leg-browser): glassbox sample fixture for offline smoke --- .../leg-browser/fixtures/glassbox-sample.json | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tools/leg-browser/fixtures/glassbox-sample.json diff --git a/tools/leg-browser/fixtures/glassbox-sample.json b/tools/leg-browser/fixtures/glassbox-sample.json new file mode 100644 index 0000000..6b3f6fd --- /dev/null +++ b/tools/leg-browser/fixtures/glassbox-sample.json @@ -0,0 +1,58 @@ +{ + "dual_loop": { + "version": 1, + "track_next": "S", + "track_last": "G", + "open_pr": "https://github.com/staticroostermedia-arch/engram/pull/58", + "mcp_restart_required": false, + "last_fire_goal": "goal:fire_dual_rsi_demo_1", + "last_verify": { + "type": "gemma_stage", + "status": "pass", + "at": "2026-07-10T20:00:00Z" + }, + "parents": [ + "goal:dual_rsi_program", + "goal:ship_substrate", + "goal:glassbox_leg" + ], + "gemma": { + "stage": "eval_gate", + "sft_rows": 51, + "eval_passed": true + } + }, + "aliveness": { + "fidelity": 0.94, + "mean_hub_crs": 0.89, + "hermies_cos": 0.71 + }, + "parents": [ + { + "id": "goal:dual_rsi_program", + "status": "active", + "last_fire": "goal:fire_dual_rsi_demo_1", + "last_verify_status": "pass" + }, + { + "id": "goal:ship_substrate", + "status": "active", + "last_fire": "goal:fire_ship_demo_1", + "last_verify_status": "pass" + }, + { + "id": "goal:glassbox_leg", + "status": "active", + "last_fire": null, + "last_verify_status": "pending" + } + ], + "last_fire": { + "id": "goal:fire_dual_rsi_demo_1", + "intent": "eval_gate advance", + "verify_type": "gemma_stage", + "verify_status": "pass", + "verify_evidence": "eval_gate_metrics.json passed=true", + "falsify": "eval fail on re-run" + } +} From eae0a83782ea886caa744dbc59705c987e5dc81b Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 13:57:50 -0700 Subject: [PATCH 10/16] docs: how to reschedule loops onto glassbox v2 prompts --- docs/skills/loop-prompts/RESCHEDULE.md | 79 ++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 docs/skills/loop-prompts/RESCHEDULE.md diff --git a/docs/skills/loop-prompts/RESCHEDULE.md b/docs/skills/loop-prompts/RESCHEDULE.md new file mode 100644 index 0000000..c5c39a7 --- /dev/null +++ b/docs/skills/loop-prompts/RESCHEDULE.md @@ -0,0 +1,79 @@ +# Reschedule loops onto Glass-Box RSI v2 prompts + +**When:** After editing any `*_v2.md` body, or when rotating scheduler jobs onto fire-goal + typed-verify prompts. +**Operator skill:** [engram-glassbox-rsi.md](../engram-glassbox-rsi.md) +**Prompt bodies:** this directory ([README](README.md)) + +In-repo markdown does **not** update live schedulers. You must **delete the old job** and **create a new one** with the paste-ready fenced prompt from the matching `*_v2.md`. + +--- + +## HARD procedure + +1. **Copy** the fenced scheduler prompt from the target `*_v2.md` (entire body inside the fence). +2. **`scheduler_delete(id=…)`** the old job if it still appears in `scheduler_list`. +3. **`scheduler_create(interval=…, prompt=…, recurring=true)`** with the new body. +4. Record the **new job id** returned by create (ids rotate; never treat the table below as permanent). +5. Optional: `session_start` / quick_trace note which loops were rotated. + +Do **not** leave both old and new Dual RSI (or Ship) jobs running the same loop — duplicate fires mint duplicate child goals. + +**PR watch:** schedule only while `helper:rsi_dual_loop_state.open_pr` is set; delete or pause when PR merges/closes. + +--- + +## Known job IDs (snapshot — must refresh) + +> **IDs expire ~7 days** after creation (harness max lifetime). Refresh via `scheduler_list` before delete. This table is a **point-in-time** operator aid from the glassbox cutover conversation; update the table when you reschedule. + +| Loop | Interval | Snapshot job id | v2 prompt body | Parent | +|------|----------|-----------------|----------------|--------| +| Dual RSI | 20m | `019f4d8d86ec` | [dual_rsi_v2.md](dual_rsi_v2.md) | `goal:dual_rsi_program` | +| Hermies | 2h | `019f4d8f921b` | *(legacy / non-glassbox body — keep or rewrite separately)* | — | +| Meta | 8h | `019f4d8fa9dd` | *(legacy / non-glassbox body — keep or rewrite separately)* | — | +| Aliveness | 1d | `019f4d8fc387` | [aliveness_bench_v2.md](aliveness_bench_v2.md) | `goal:dual_rsi_program` | +| Research | 3d | `019f4d8fdbc1` | *(legacy / non-glassbox body — keep or rewrite separately)* | — | +| Consciousness | 30m | `019f4daa1d06` | *(legacy / non-glassbox body — keep or rewrite separately)* | — | +| Ship | 1d | `019f4db6bcc9` | [ship_gate_v2.md](ship_gate_v2.md) | `goal:ship_substrate` | +| PR Watch | 2h | `019f4dbb269c` | [pr_watch_v2.md](pr_watch_v2.md) | `goal:ship_substrate` | +| MCP Stale | 1d | `019f4dbb497a` | [mcp_stale_v2.md](mcp_stale_v2.md) | dual_rsi or ship | + +**Glassbox v2 cutover priority (prompt bodies in this folder):** Dual RSI, Ship, PR Watch, MCP Stale, Aliveness. +Hermies / Meta / Research / Consciousness stay on their existing prompts until those loops get glassbox rewrites. + +--- + +## Example: rotate Dual RSI to v2 + +``` +# 1. List — confirm id still live +scheduler_list + +# 2. Delete snapshot (or current) Dual RSI job +scheduler_delete(id="019f4d8d86ec") # only if still listed; else use id from list + +# 3. Create with body from dual_rsi_v2.md fenced block +scheduler_create( + interval="20m", + recurring=true, + prompt="" +) + +# 4. Note new id from create response; update this table +``` + +Suggested intervals (operator choice): Dual RSI ~20m; PR watch while `open_pr` set ~15–30m or 2h; MCP stale after rebuilds / 1d; Aliveness ~30–60m or 1d; Ship gate on demand / 1d after substrate wins. + +--- + +## After reschedule + +- First fire must: `session_start` → ack → mint `goal:fire_*` → typed verify → update `helper:rsi_dual_loop_state`. +- LEG glassbox (`?view=glassbox`) only reflects state after dual_loop + fire goals exist ([mint parents runbook](../../../scripts/mint_glassbox_parent_goals.md)). +- No auto-merge; no stage flip without `verify_status=pass`. + +## Related + +- [loop-prompts README](README.md) +- [glassbox design](../../superpowers/specs/2026-07-10-glassbox-rsi-design.md) +- Plan Task 9: [2026-07-10-glassbox-rsi.md](../../superpowers/plans/2026-07-10-glassbox-rsi.md) From 2c5b0ad5952d6dd10bf365cfd6eb1bd9037112b6 Mon Sep 17 00:00:00 2001 From: staticroostermedia-arch Date: Fri, 10 Jul 2026 14:00:13 -0700 Subject: [PATCH 11/16] feat(leg-browser): glassbox split-home view (?view=glassbox) --- tools/leg-browser/index.html | 271 +++++++++++++++++++++++++++++++++++ 1 file changed, 271 insertions(+) diff --git a/tools/leg-browser/index.html b/tools/leg-browser/index.html index 7b7bb19..eacfabb 100644 --- a/tools/leg-browser/index.html +++ b/tools/leg-browser/index.html @@ -533,6 +533,18 @@ line-height: 1.35; opacity: 0.92; } + [hidden] { display: none !important; } + .gb-root { display: flex; flex-direction: column; gap: 12px; padding: 12px; min-height: 100vh; background: var(--bg); color: var(--text); } + .gb-strip { display: flex; flex-wrap: wrap; gap: 8px; } + .gb-chip { border: 1px solid #333; border-radius: 6px; padding: 4px 8px; font-size: 12px; } + .gb-chip.pass { border-color: #2a6; } + .gb-chip.fail { border-color: #a33; } + .gb-chip.warn { border-color: #a80; } + .gb-main { display: grid; grid-template-columns: 1fr 280px; gap: 12px; min-height: 60vh; } + .gb-parents { display: flex; flex-direction: column; gap: 8px; } + .gb-card { border: 1px solid #333; border-radius: 8px; padding: 10px; cursor: pointer; } + .gb-activity { border: 1px solid #333; border-radius: 8px; padding: 8px; overflow: auto; max-height: 70vh; } + @media (max-width: 900px) { .gb-main { grid-template-columns: 1fr; } } .status { margin: 12px 16px 0; padding: 10px 12px; @@ -1850,6 +1862,23 @@

Tile

+ +