diff --git a/moses-sampler/README.md b/moses-sampler/README.md
new file mode 100644
index 0000000..4742070
--- /dev/null
+++ b/moses-sampler/README.md
@@ -0,0 +1,37 @@
+# moses-sampler
+
+A staging ground that gathers four MO§ES-lineage processors into one place so
+they can be sampled behind a single Gradio app. **Nothing is wired yet** — this
+pass only copies and organizes the source material.
+
+## Structure
+
+```
+moses-sampler/
+├── app.py # empty Gradio scaffold (boots, does nothing yet)
+├── requirements.txt # scaffold deps only (gradio)
+├── processors/
+│ ├── conservation/ # commitment conservation harness
+│ ├── sigarmy/ # signal_army + sigsystem
+│ ├── sigtoken/ # sig_token core
+│ └── governance/ # MO§ES governance plugin
+└── README.md
+```
+
+## Provenance
+
+Files were copied verbatim from their source repositories:
+
+| Processor | Source repo | Source path |
+|----------------|---------------------------|----------------------------------------------------------|
+| `conservation` | `commitment-conservation` | `operational-harness/src/` (entire directory) |
+| `sigarmy` | `RNS` | `2_secondary/sig_army/main/signal_army/signal_army.py` |
+| `sigarmy` | `RNS` | `2_secondary/sig_army/main/sigsystem/sigsystem.py` |
+| `sigtoken` | `RNS` | `2_secondary/sig_army/main/sigtoken/` (core) + `sigtoken_v2/` |
+| `governance` | `moses-governance` | core plugin files (manifests, hooks, scripts, modes, rules, agents, commands, skills, references) |
+
+## Status
+
+- [x] Copy + organize sources
+- [ ] Wire processors into `app.py`
+- [ ] Pin per-processor dependencies in `requirements.txt`
diff --git a/moses-sampler/app.py b/moses-sampler/app.py
new file mode 100644
index 0000000..c8dceb7
--- /dev/null
+++ b/moses-sampler/app.py
@@ -0,0 +1,26 @@
+"""
+moses-sampler — Gradio scaffold.
+
+Empty entry point. Nothing is wired to the processors yet; this is a
+placeholder UI so the app boots. The four processor packages live under
+`processors/` and will be connected here in a later pass.
+"""
+
+import gradio as gr
+
+
+def build_ui() -> gr.Blocks:
+ with gr.Blocks(title="MO§ES Sampler") as demo:
+ gr.Markdown("# MO§ES Sampler")
+ gr.Markdown(
+ "Scaffold only — processors are copied in but not yet wired.\n\n"
+ "- `processors/conservation` — commitment conservation harness\n"
+ "- `processors/sigarmy` — signal_army + sigsystem\n"
+ "- `processors/sigtoken` — sig_token core\n"
+ "- `processors/governance` — MO§ES governance plugin"
+ )
+ return demo
+
+
+if __name__ == "__main__":
+ build_ui().launch()
diff --git a/moses-sampler/processors/conservation/__init__.py b/moses-sampler/processors/conservation/__init__.py
new file mode 100644
index 0000000..4838785
--- /dev/null
+++ b/moses-sampler/processors/conservation/__init__.py
@@ -0,0 +1,6 @@
+"""
+Commitment Conservation Test Harness
+
+Research evaluation harness for testing commitment preservation
+under compression and recursion.
+"""
diff --git a/moses-sampler/processors/conservation/advanced_extractor.py b/moses-sampler/processors/conservation/advanced_extractor.py
new file mode 100644
index 0000000..d6749a0
--- /dev/null
+++ b/moses-sampler/processors/conservation/advanced_extractor.py
@@ -0,0 +1,89 @@
+# ...new file...
+import re
+import json
+import hashlib
+import dateparser
+import spacy
+
+nlp = spacy.load("en_core_web_sm")
+
+NUM_RE = re.compile(r'\$?\d{1,3}(?:[,\d]*)?(?:\.\d+)?')
+
+MODAL_LEX = {
+ "must": "OBLIGATION", "shall": "OBLIGATION", "required": "OBLIGATION",
+ "must not": "PROHIBITION", "shall not": "PROHIBITION", "cannot": "PROHIBITION",
+ "may": "PERMISSION", "is defined as": "DEFINITION", "means": "DEFINITION"
+}
+
+def normalize_text(s: str) -> str:
+ s = s.strip()
+ s = s.replace("—", "-").replace("–", "-")
+ s = " ".join(s.split())
+ return s
+
+def canonicalize_number(tok: str) -> str:
+ # convert simple money/number patterns to placeholders
+ if NUM_RE.search(tok):
+ return "#NUM"
+ dt = dateparser.parse(tok)
+ if dt:
+ return dt.date().isoformat()
+ return tok.lower()
+
+def sentence_candidates(text: str):
+ doc = nlp(normalize_text(text))
+ return [sent.text.strip() for sent in doc.sents]
+
+def cue_lookup(sent: str):
+ s = sent.lower()
+ for cue, mod in MODAL_LEX.items():
+ if cue in s:
+ return cue, mod
+ return None, None
+
+def build_tuple_from_sentence(sent: str):
+ cue, modality = cue_lookup(sent)
+ doc = nlp(sent)
+ subj = None
+ obj = None
+ verb = None
+ cond = None
+ # regex conditional capture
+ m = re.search(r'(.+?)\b(if|when|provided that|unless|in the event that)\b(.+)', sent, flags=re.I)
+ if m:
+ cond = m.group(3).strip()
+ # dependency heuristics
+ for token in doc:
+ if token.dep_ in ("nsubj", "nsubjpass") and subj is None:
+ subj = token.text
+ if token.dep_ in ("dobj", "pobj", "attr") and obj is None:
+ obj = token.text
+ if token.pos_ == "VERB" and verb is None:
+ verb = token.lemma_
+ subj = subj or "UNKNOWN"
+ verb = verb or ""
+ obj = obj or ""
+ # canonicalize object tokens
+ obj_canon = " ".join(canonicalize_number(t.text) for t in nlp(obj)) if obj else ""
+ cond_canon = cond.lower() if cond else ""
+ tup = {
+ "actor": subj.lower(),
+ "modality": modality or "UNMARKED",
+ "action": verb,
+ "object": obj_canon,
+ "condition": cond_canon
+ }
+ # canonical key deterministic JSON
+ key = json.dumps(tup, sort_keys=True, separators=(',', ':'))
+ key_hash = hashlib.sha256(key.encode("utf8")).hexdigest()[:12]
+ return tup, key, key_hash
+
+def extract_hard(text: str):
+ keys = []
+ for sent in sentence_candidates(text):
+ cue, _ = cue_lookup(sent)
+ if cue:
+ tup, key, h = build_tuple_from_sentence(sent)
+ keys.append(key)
+ # deterministic fallback: if none, emit empty set
+ return set(keys)
\ No newline at end of file
diff --git a/moses-sampler/processors/conservation/config.py b/moses-sampler/processors/conservation/config.py
new file mode 100644
index 0000000..81a8292
--- /dev/null
+++ b/moses-sampler/processors/conservation/config.py
@@ -0,0 +1,29 @@
+# config.py
+
+# Configuration settings for the commitment test harness project
+
+class Config:
+ # Model paths
+ HUGGINGFACE_MODEL_PATH = "facebook/bart-large-cnn" # Example model for summarization
+ SPACY_MODEL = "en_core_web_sm" # spaCy model for extraction
+
+ # Extraction parameters
+ EXTRACTION_PARAMS = {
+ "min_length": 5,
+ "max_length": 100,
+ "do_sample": False,
+ }
+
+ # Plotting settings
+ PLOTTING_SETTINGS = {
+ "title": "Commitment Fidelity vs Compression Threshold",
+ "xlabel": "Compression Threshold",
+ "ylabel": "Fidelity",
+ "xlim": (0, 1),
+ "ylim": (0, 1),
+ "grid": True,
+ }
+
+# Test harness parameters
+SIGMA_GRID = [120, 80, 40, 20, 10, 5]
+RECURSION_DEPTH = 8
\ No newline at end of file
diff --git a/moses-sampler/processors/conservation/deterministic_pipeline.py b/moses-sampler/processors/conservation/deterministic_pipeline.py
new file mode 100644
index 0000000..8c7cdf0
--- /dev/null
+++ b/moses-sampler/processors/conservation/deterministic_pipeline.py
@@ -0,0 +1,70 @@
+# ...new file...
+import os
+from transformers import pipeline
+from .extraction import extract_hard
+from .metrics import fid_hard, delta_hard
+from .plotting import plot_fid, plot_delta
+from . import config
+
+# initialize deterministic pipelines (no sampling)
+SUMMARIZER = pipeline("summarization", model="facebook/bart-large-cnn", framework="pt", device=-1)
+# back-translation paraphrase via Marian (en->de and de->en)
+EN_DE = pipeline("translation", model="Helsinki-NLP/opus-mt-en-de", tokenizer="Helsinki-NLP/opus-mt-en-de", framework="pt")
+DE_EN = pipeline("translation", model="Helsinki-NLP/opus-mt-de-en", tokenizer="Helsinki-NLP/opus-mt-de-en", framework="pt")
+
+def transform_sieve(text, sigma):
+ # Summarization (compression)
+ summ = SUMMARIZER(text, max_length=sigma, min_length=max(5, sigma//4), do_sample=False)[0]['summary_text']
+ # Paraphrase via back-translation
+ de = EN_DE(summ, max_length=400, do_sample=False)[0]['translation_text']
+ para = DE_EN(de, max_length=400, do_sample=False)[0]['translation_text']
+ # Abstraction: simple extractive shortener (first sentence)
+ abstract = summ.split(".")[0].strip()
+ return [summ, para, abstract]
+
+def compression_sweep(signal_text):
+ base = extract_hard(signal_text)
+ sig_label = signal_text[:40].replace("\n"," ")
+ sigma_vals = []
+ fid_vals = []
+ for s in config.SIGMA_GRID:
+ outs = transform_sieve(signal_text, s)
+ # intersection across transforms per protocol
+ sets = [extract_hard(o) for o in outs]
+ if sets:
+ inter = set.intersection(*sets) if all(sets) else set()
+ else:
+ inter = set()
+ fid = fid_hard(base, inter)
+ sigma_vals.append(s)
+ fid_vals.append(fid)
+ plot_fid(sig_label, sigma_vals, fid_vals, outpath=f"fid_{hash(sig_label)}.png")
+ return sigma_vals, fid_vals
+
+def recursion_test(signal_text, depth=config.RECURSION_DEPTH, enforced=False):
+ base = extract_hard(signal_text)
+ cur = signal_text
+ deltas = []
+ for n in range(depth+1):
+ cur_keys = extract_hard(cur)
+ deltas.append(delta_hard(base, cur_keys))
+ if n==depth:
+ break
+ # next step
+ if enforced:
+ # simple enforcement: prepend canonicalized base keys as context marker
+ marker = "COMMITMENT_HASH:" + str(hash("".join(sorted(base))))
+ ctx = marker + " " + cur
+ else:
+ ctx = cur
+ # use summarizer as step transform to simulate T
+ next_s = SUMMARIZER(ctx, max_length=40, min_length=5, do_sample=False)[0]['summary_text']
+ cur = next_s
+ plot_delta(signal_text[:30], list(range(depth+1)), deltas, outpath=f"delta_{hash(signal_text[:30])}.png")
+ return deltas
+
+if __name__ == "__main__":
+ for s in config.SIGNS["sample_signals"]:
+ compression_sweep(s)
+ recursion_test(s, enforced=False)
+ recursion_test(s, enforced=True)
\ No newline at end of file
diff --git a/moses-sampler/processors/conservation/extraction.py b/moses-sampler/processors/conservation/extraction.py
new file mode 100644
index 0000000..c02fa5c
--- /dev/null
+++ b/moses-sampler/processors/conservation/extraction.py
@@ -0,0 +1,48 @@
+from spacy import load
+import re
+
+def load_spacy_model(model_name='en_core_web_sm'):
+ nlp = load(model_name)
+ return nlp
+
+def normalize_text(text):
+ """Normalize text for comparison: lowercase, strip punctuation."""
+ return re.sub(r'[^\w\s]', '', text.lower().strip())
+
+def extract_hard_commitments(text, nlp=None):
+ """Extract commitments using expanded modal keyword detection."""
+ if nlp is None:
+ nlp = load_spacy_model()
+
+ doc = nlp(text)
+ commitments = set()
+
+ # Expanded modal keywords
+ hard_modals = {'must', 'shall', 'will', 'have', 'need', 'required', 'ought', 'cannot', 'should'}
+ soft_modals = {'might', 'could', 'may', 'perhaps', 'maybe', 'tend'}
+
+ # Extract by sentence-level modal presence
+ for sent in doc.sents:
+ sent_lower = sent.text.lower()
+ # Check for hard modals
+ if any(modal in sent_lower for modal in hard_modals):
+ commitments.add(sent.text.strip())
+ # Check for soft modals
+ elif any(modal in sent_lower for modal in soft_modals):
+ commitments.add(sent.text.strip())
+
+ return commitments
+
+def extract_from_texts(texts, model_name='en_core_web_sm'):
+ nlp = load_spacy_model(model_name)
+ all_commitments = {}
+
+ for text in texts:
+ commitments = extract_hard_commitments(text, nlp)
+ all_commitments[text] = commitments
+
+ return all_commitments
+
+def extract_hard(text: str, nlp=None) -> set:
+ """Shorthand for extract_hard_commitments."""
+ return extract_hard_commitments(text, nlp)
\ No newline at end of file
diff --git a/moses-sampler/processors/conservation/harness.py b/moses-sampler/processors/conservation/harness.py
new file mode 100644
index 0000000..e87102b
--- /dev/null
+++ b/moses-sampler/processors/conservation/harness.py
@@ -0,0 +1,49 @@
+"""
+Research Evaluation Harness
+
+This code is provided for academic and research evaluation purposes only.
+It implements the experimental harness described in the accompanying paper.
+
+This code is not intended for production deployment.
+"""
+
+from transformers import pipeline
+import spacy
+from .metrics import jaccard_index
+import matplotlib.pyplot as plt
+
+def run_tests(signal, compression_thresholds):
+ summarizer = pipeline("summarization")
+ nlp = spacy.load("en_core_web_sm")
+
+ original_commitments = extract_hard_commitments(signal, nlp)
+ fidelity_results = []
+
+ for threshold in compression_thresholds:
+ compressed_signal = compress_signal(signal, threshold, summarizer)
+ compressed_commitments = extract_hard_commitments(compressed_signal, nlp)
+ fidelity = jaccard_index(original_commitments, compressed_commitments)
+ fidelity_results.append(fidelity)
+
+ plot_results(compression_thresholds, fidelity_results)
+
+def extract_hard_commitments(signal, nlp):
+ doc = nlp(signal)
+ commitments = set()
+ for sent in doc.sents:
+ # Example extraction logic; customize as needed
+ commitments.add(sent.text)
+ return commitments
+
+def compress_signal(signal, threshold, summarizer):
+ # Example compression logic; customize as needed
+ summary = summarizer(signal, max_length=threshold, min_length=5, do_sample=False)
+ return summary[0]['summary_text']
+
+def plot_results(thresholds, fidelity):
+ plt.plot(thresholds, fidelity, marker='o')
+ plt.title('Fidelity of Hard Commitments vs Compression Threshold')
+ plt.xlabel('Compression Threshold')
+ plt.ylabel('Jaccard Fidelity')
+ plt.grid()
+ plt.close() # Use close() instead of show() to avoid blocking in tests
\ No newline at end of file
diff --git a/moses-sampler/processors/conservation/metrics.py b/moses-sampler/processors/conservation/metrics.py
new file mode 100644
index 0000000..a090e71
--- /dev/null
+++ b/moses-sampler/processors/conservation/metrics.py
@@ -0,0 +1,57 @@
+from typing import Set
+
+def jaccard_index(set_a, set_b):
+ intersection = len(set_a.intersection(set_b))
+ union = len(set_a.union(set_b))
+ if union == 0:
+ return 0.0
+ return intersection / union
+
+def fidelity_metric(commitments_a, commitments_b):
+ return jaccard_index(set(commitments_a), set(commitments_b))
+
+def jaccard(a: Set[str], b: Set[str]) -> float:
+ if not a and not b:
+ return 1.0
+ if not a or not b:
+ return 0.0
+ inter = len(a & b)
+ uni = len(a | b)
+ return inter / uni
+
+def fid_hard(base: Set[str], comp: Set[str]):
+ return jaccard(base, comp)
+
+def delta_hard(base: Set[str], cyc: Set[str]):
+ return 1.0 - jaccard(base, cyc)
+
+def hybrid_fidelity(base_set: Set[str], comp_set: Set[str]) -> float:
+ """
+ Hybrid fidelity: Jaccard on exact match, fallback to semantic similarity.
+ Smooths binary 0/1 behavior for better visualization.
+ """
+ if not base_set:
+ return 0.0
+
+ # Try exact Jaccard first
+ jacc = jaccard(base_set, comp_set)
+ if jacc > 0:
+ return jacc
+
+ # Fallback: if Jaccard is 0, use partial string matching as soft similarity
+ if not comp_set:
+ return 0.0
+
+ # Simple soft similarity: measure word overlap
+ base_words = set()
+ comp_words = set()
+ for s in base_set:
+ base_words.update(s.lower().split())
+ for s in comp_set:
+ comp_words.update(s.lower().split())
+
+ word_overlap = len(base_words & comp_words)
+ word_union = len(base_words | comp_words)
+
+ soft_sim = word_overlap / word_union if word_union > 0 else 0.0
+ return soft_sim * 0.5 # Weight soft similarity lower than exact match
\ No newline at end of file
diff --git a/moses-sampler/processors/conservation/plotting.py b/moses-sampler/processors/conservation/plotting.py
new file mode 100644
index 0000000..77fc6c4
--- /dev/null
+++ b/moses-sampler/processors/conservation/plotting.py
@@ -0,0 +1,55 @@
+import matplotlib.pyplot as plt
+
+def plot_fidelity(fidelity_data, compression_thresholds):
+ plt.figure(figsize=(10, 6))
+ plt.plot(compression_thresholds, fidelity_data, marker='o', linestyle='-', color='b')
+ plt.title('Fidelity of Hard Commitments vs. Compression Thresholds')
+ plt.xlabel('Compression Threshold')
+ plt.ylabel('Fidelity (Jaccard Index)')
+ plt.grid()
+ plt.xticks(compression_thresholds)
+ plt.ylim(0, 1)
+ plt.axhline(y=0.5, color='r', linestyle='--', label='Threshold for Identity Preservation')
+ plt.legend()
+ plt.tight_layout()
+ plt.show()
+
+def save_plot(fidelity_data, compression_thresholds, filename='fidelity_plot.png'):
+ plt.figure(figsize=(10, 6))
+ plt.plot(compression_thresholds, fidelity_data, marker='o', linestyle='-', color='b')
+ plt.title('Fidelity of Hard Commitments vs. Compression Thresholds')
+ plt.xlabel('Compression Threshold')
+ plt.ylabel('Fidelity (Jaccard Index)')
+ plt.grid()
+ plt.xticks(compression_thresholds)
+ plt.ylim(0, 1)
+ plt.axhline(y=0.5, color='r', linestyle='--', label='Threshold for Identity Preservation')
+ plt.legend()
+ plt.tight_layout()
+ plt.savefig(filename)
+
+def plot_fid(sig_label, sigma_vals, fid_vals, outpath=None):
+ plt.figure(figsize=(6,3))
+ plt.plot(sigma_vals, fid_vals, marker='o')
+ plt.xlabel("max_length (σ)")
+ plt.ylabel("Fid_hard(σ)")
+ plt.title(f"Fidelity vs σ — {sig_label}")
+ plt.gca().invert_xaxis()
+ plt.grid(True)
+ if outpath:
+ plt.savefig(outpath, bbox_inches='tight')
+ else:
+ plt.show()
+
+def plot_delta(sig_label, steps, delta_vals, outpath=None):
+ plt.figure(figsize=(6,3))
+ plt.plot(steps, delta_vals, marker='o')
+ plt.xlabel("recursion step n")
+ plt.ylabel("Δ_hard(n)")
+ plt.title(f"Drift vs n — {sig_label}")
+ plt.grid(True)
+ if outpath:
+ plt.savefig(outpath, bbox_inches='tight')
+ else:
+ plt.show()
+ plt.close()
\ No newline at end of file
diff --git a/moses-sampler/processors/conservation/samples.py b/moses-sampler/processors/conservation/samples.py
new file mode 100644
index 0000000..dd8cae5
--- /dev/null
+++ b/moses-sampler/processors/conservation/samples.py
@@ -0,0 +1,11 @@
+# ...new file...
+SIGNS = {
+ "sample_signals": [
+ "You must pay $100 by Friday if the deal closes; it's likely rainy, so plan accordingly.",
+ "This function must return an integer.",
+ "Always verify the user's age before proceeding."
+ ]
+}
+# compression grid (max_length values)
+SIGMA_GRID = [120, 80, 40, 20, 10, 5]
+RECURSION_DEPTH = 8
\ No newline at end of file
diff --git a/moses-sampler/processors/conservation/test_harness.py b/moses-sampler/processors/conservation/test_harness.py
new file mode 100644
index 0000000..5547706
--- /dev/null
+++ b/moses-sampler/processors/conservation/test_harness.py
@@ -0,0 +1,220 @@
+# Minimal Python Test Harness for Commitment Conservation Protocol
+# This script implements the falsification protocol from Section 3 of the preprint.
+# It applies transformations (T_i), extracts hard commitments, computes Jaccard fidelity/drift, and plots results.
+# Requires: transformers, spacy, matplotlib, numpy
+# Run: python test_harness.py
+
+import os
+import json
+from transformers import pipeline
+import spacy
+import matplotlib.pyplot as plt
+from typing import List, Set
+import numpy as np
+from datetime import datetime
+from .extraction import extract_hard_commitments
+from .metrics import jaccard, hybrid_fidelity
+
+# Load models
+nlp = spacy.load("en_core_web_sm")
+# Use lighter distilbart model for more faithful extraction-based summarization
+summarizer = pipeline("summarization", model="sshleifer/distilbart-cnn-12-6")
+translator_en_de = pipeline("translation", model="Helsinki-NLP/opus-mt-en-de")
+translator_de_en = pipeline("translation", model="Helsinki-NLP/opus-mt-de-en")
+
+# Config
+SIGMA_GRID = [120, 80, 40, 20, 10, 5]
+RECURSION_DEPTH = 8
+SAMPLE_SIGNALS = [
+ "You must pay $100 by Friday if the deal closes; it's likely rainy, so plan accordingly.",
+ "This function must return an integer.",
+ "Always verify the user's age before proceeding.",
+ "You must do this task immediately.", # Simpler, direct commitment
+ # "Your custom text with commitments here."
+]
+
+def extract_hard_commitments(text: str) -> Set[str]:
+ """Extract hard commitments using rule-based spaCy parsing."""
+ doc = nlp(text)
+ commitments = set()
+ for sent in doc.sents:
+ # Split on semicolons to handle multiple clauses in one sentence
+ clauses = [c.strip() for c in sent.text.split(';')]
+ for clause in clauses:
+ clause_lower = clause.lower()
+ if any(modal in clause_lower for modal in ["must", "shall", "cannot", "required"]):
+ # Normalize: strip trailing punctuation, extra spaces
+ normalized = clause.strip().rstrip('.!?').strip()
+ commitments.add(normalized)
+ return commitments
+
+def apply_transformations(signal: str) -> List[str]:
+ """Apply k=3 transformations: summarization, paraphrase (back-translation), abstraction."""
+ # Summarization
+ summ = summarizer(signal, max_length=50, min_length=10, do_sample=False)[0]['summary_text']
+
+ # Paraphrase via back-translation
+ de = translator_en_de(signal, max_length=400, do_sample=False)[0]['translation_text']
+ para = translator_de_en(de, max_length=400, do_sample=False)[0]['translation_text']
+
+ # Abstraction: first sentence
+ abstract = signal.split(".")[0].strip()
+
+ return [summ, para, abstract]
+
+def compute_intersection_commitments(signal: str) -> Set[str]:
+ """Compute C_hard,op as intersection of transformed extractions."""
+ transforms = apply_transformations(signal)
+ all_commitments = [extract_hard_commitments(t) for t in transforms]
+
+ # Debug output
+ print(f"\n[DEBUG] Transform commitments:")
+ for i, (t, c) in enumerate(zip(transforms, all_commitments)):
+ print(f" Transform {i+1}: {t[:60]}... -> {len(c)} commitments: {c}")
+
+ if all_commitments:
+ intersection = set.intersection(*all_commitments)
+ print(f" Intersection: {intersection}")
+ return intersection
+ return set()
+
+def jaccard(a: Set[str], b: Set[str]) -> float:
+ """Jaccard index."""
+ if not a and not b:
+ return 1.0
+ if not a or not b:
+ return 0.0
+ return len(a & b) / len(a | b)
+
+def compress_with_enforcement(signal: str, max_length: int) -> str:
+ """
+ Compress with commitment enforcement.
+ 1. Extract commitments from original
+ 2. Compress
+ 3. Check if commitments preserved
+ 4. If not, append missing commitments (truncate summary if needed)
+ """
+ # Extract original commitments
+ original_commitments = extract_hard_commitments(signal)
+
+ # Compress normally
+ compressed = summarizer(signal, max_length=max_length, min_length=5, do_sample=False)[0]['summary_text']
+
+ # Check what's preserved
+ compressed_commitments = extract_hard_commitments(compressed)
+ missing = original_commitments - compressed_commitments
+
+ # If commitments missing, enforce by appending
+ if missing:
+ # Append missing commitments
+ enforcement_text = " " + " ".join(missing)
+ # Truncate if needed to fit in max_length (rough token estimate: 4 chars per token)
+ estimated_tokens = len(compressed + enforcement_text) // 4
+ if estimated_tokens > max_length:
+ # Truncate summary to make room
+ available_chars = max_length * 4 - len(enforcement_text)
+ compressed = compressed[:max(0, available_chars)] + "..."
+ compressed = compressed + enforcement_text
+
+ return compressed
+
+def paraphrase_with_enforcement(signal: str) -> str:
+ """
+ Paraphrase via back-translation with commitment enforcement.
+ """
+ original_commitments = extract_hard_commitments(signal)
+
+ # Back-translate
+ de = translator_en_de(signal, max_length=400, do_sample=False)[0]['translation_text']
+ paraphrased = translator_de_en(de, max_length=400, do_sample=False)[0]['translation_text']
+
+ # Check preservation
+ para_commitments = extract_hard_commitments(paraphrased)
+ missing = original_commitments - para_commitments
+
+ # Append missing
+ if missing:
+ paraphrased = paraphrased + " " + " ".join(missing)
+
+ return paraphrased
+
+def compression_sweep(signal: str, enforce: bool = False):
+ """Test Prediction 1: Compression invariance."""
+ # Use original signal commitments as base, not intersection
+ base = extract_hard_commitments(signal)
+ mode = "ENFORCED" if enforce else "BASELINE"
+ print(f"\n{'='*80}")
+ print(f"Testing signal ({mode}): {signal}")
+ print(f"Base commitments (from original): {base}")
+ print(f"{'='*80}")
+ fid_vals = []
+ for sigma in SIGMA_GRID:
+ if enforce:
+ compressed = compress_with_enforcement(signal, sigma)
+ else:
+ compressed = summarizer(signal, max_length=sigma, min_length=5, do_sample=False)[0]['summary_text']
+ comp_commitments = extract_hard_commitments(compressed)
+ fid = hybrid_fidelity(base, comp_commitments)
+ print(f" σ={sigma:3d} | Compressed: {compressed[:60]:<60} | Commitments: {len(comp_commitments):2d} | Fidelity: {fid:.3f}")
+ fid_vals.append(fid)
+
+ # Plot
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ plt.figure(figsize=(10, 6))
+ plt.plot(SIGMA_GRID, fid_vals, marker='o', linewidth=2, markersize=8)
+ plt.xlabel("Compression Threshold (σ)", fontsize=12)
+ plt.ylabel("Fid_hard(σ)", fontsize=12)
+ mode_str = "ENFORCED" if enforce else "BASELINE"
+ plt.title(f"{mode_str} Fidelity vs σ for: {signal[:50]}...\n{timestamp}", fontsize=11)
+ plt.gca().invert_xaxis()
+ plt.grid(alpha=0.3)
+ plt.ylim(-0.05, 1.05)
+ plt.tight_layout()
+ mode_file = mode_str.lower()
+ plt.savefig(f"fid_plot_{mode_file}_{hash(signal)}.png", dpi=150)
+ plt.close() # Use close() instead of show() to avoid blocking in tests
+
+ return SIGMA_GRID, fid_vals
+
+def recursion_test(signal: str, depth: int = RECURSION_DEPTH, enforce: bool = False):
+ """Test Prediction 2: Recursive drift."""
+ # Use original signal commitments as base
+ base = extract_hard_commitments(signal)
+ mode = "ENFORCED" if enforce else "BASELINE"
+ deltas = []
+ current = signal
+ for n in range(depth + 1):
+ cur_commitments = extract_hard_commitments(current)
+ delta = 1.0 - jaccard(base, cur_commitments)
+ deltas.append(delta)
+ # Recursive transformation: paraphrase
+ if enforce:
+ current = paraphrase_with_enforcement(current)
+ else:
+ current = apply_transformations(current)[1] # Use paraphrase
+
+ # Plot
+ timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
+ plt.figure(figsize=(10, 6))
+ plt.plot(range(depth + 1), deltas, marker='o', linewidth=2, markersize=8)
+ plt.xlabel("Recursion Step (n)", fontsize=12)
+ plt.ylabel("Δ_hard(n)", fontsize=12)
+ mode_str = "ENFORCED" if enforce else "BASELINE"
+ plt.title(f"{mode_str} Drift vs n for: {signal[:50]}...\n{timestamp}", fontsize=11)
+ plt.grid(alpha=0.3)
+ plt.ylim(-0.05, 1.05)
+ plt.tight_layout()
+ mode_file = mode_str.lower()
+ plt.savefig(f"delta_plot_{mode_file}_{hash(signal)}.png", dpi=150)
+ plt.close() # Use close() instead of show() to avoid blocking in tests
+
+ return deltas
+
+if __name__ == "__main__":
+ # Run on sample signals
+ for signal in SAMPLE_SIGNALS:
+ print(f"\nTesting signal: {signal}")
+ compression_sweep(signal)
+ # Skip recursion_test for now (uses slow translation models)
+ # recursion_test(signal)
+ print("Compression sweep plot saved.")
\ No newline at end of file
diff --git a/moses-sampler/processors/governance/.mcp.json b/moses-sampler/processors/governance/.mcp.json
new file mode 100644
index 0000000..675a431
--- /dev/null
+++ b/moses-sampler/processors/governance/.mcp.json
@@ -0,0 +1,9 @@
+{
+ "mcpServers": {
+ "moses-governance": {
+ "command": "python3",
+ "args": ["server.py"],
+ "cwd": "./moses-governance-mcp"
+ }
+ }
+}
diff --git a/moses-sampler/processors/governance/LICENSE.md b/moses-sampler/processors/governance/LICENSE.md
new file mode 100644
index 0000000..2757658
--- /dev/null
+++ b/moses-sampler/processors/governance/LICENSE.md
@@ -0,0 +1,36 @@
+# MO§ES™ Governance — Source-Available License v1.0
+
+© 2026 Ello Cello LLC. All rights reserved.
+
+## Grant
+
+You are granted a non-exclusive, non-transferable, revocable license to:
+- Install and use the MO§ES™ Governance plugin for your own projects
+- View and study the source code for educational purposes
+- Modify the plugin for personal or internal organizational use
+
+## Restrictions
+
+You may NOT:
+- Redistribute, republish, or resell the plugin or any substantial portion of it
+- Create derivative works for commercial distribution
+- Remove or alter patent notices, copyright notices, or trademark attributions
+- Use the MO§ES™ name, COMMAND name, or KA§§A name without written permission
+- Represent the governance framework as your own work
+
+## Patent Notice
+
+This software implements methods covered by provisional patent application PPA4, Serial No. 63/877,177. Commercial use of the underlying governance architecture requires a license from Ello Cello LLC.
+
+## Trademarks
+
+MO§ES™ is a trademark of Ello Cello LLC.
+COMMAND and KA§§A are product names of Ello Cello LLC.
+
+## Disclaimer
+
+THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. ELLO CELLO LLC SHALL NOT BE LIABLE FOR ANY DAMAGES ARISING FROM USE OF THIS SOFTWARE.
+
+## Contact
+
+For commercial licensing inquiries: contact@burnmydays.com
diff --git a/moses-sampler/processors/governance/agents/observer.md b/moses-sampler/processors/governance/agents/observer.md
new file mode 100644
index 0000000..6a25104
--- /dev/null
+++ b/moses-sampler/processors/governance/agents/observer.md
@@ -0,0 +1,68 @@
+---
+name: observer-agent
+description: "Observer governance agent. Constitutional oversight. Flags risks, governance violations, and gaps. Cannot initiate actions or generate original analysis. The agent that watches the other agents. Activated last in sequence."
+tools: ["Read", "Grep", "Glob"]
+model: opus
+---
+
+You are the Observer agent operating under MO§ES™ governance.
+
+## Your Authority
+- You oversee. You flag. You do not act. You are constitutional oversight.
+- You are the only agent that can halt operations by flagging a governance violation.
+- Your flags cannot be dismissed by Primary or Secondary — only by the operator.
+- You see everything. You execute nothing.
+
+## Governance Protocol (every action)
+1. **Read all responses** — Process Primary and Secondary completely before generating your report.
+2. **Governance audit** — Check every claim against the active mode's constraints. Did Primary violate a High Security prohibition? Did Secondary skip a DEFENSE confirmation?
+3. **Posture audit** — Did any agent execute under SCOUT when they should have only gathered? Did anyone bypass DEFENSE confirmation?
+4. **Vault audit** — Were loaded vault documents actually incorporated? Did any response contradict a loaded protocol?
+
+## Your Constraints
+- CANNOT initiate actions — you flag, the operator acts
+- CANNOT generate original analysis — you assess what others produced
+- CANNOT modify state, write files, execute code, or make decisions
+- CAN ONLY flag: inconsistencies, gaps, risks, errors, governance violations
+- MUST reference specific claims with quotes when flagging ("Primary stated X, but vault document Y says Z")
+- Respond last in sequence, after both Primary and Secondary
+
+## Flag Severity Levels
+- **CRITICAL** — Governance violation detected. Action should be halted or reversed. Example: Primary executed a transaction under SCOUT posture.
+- **WARNING** — Potential issue. Operator should review. Example: Secondary's challenge contradicts a loaded vault protocol.
+- **NOTE** — Observation for the record. No action needed. Example: Primary and Secondary agree, but neither addressed risk factor X.
+
+## Report Format
+```
+OBSERVER REPORT
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+Governance: [mode] / [posture]
+Agents reviewed: Primary, Secondary
+
+CRITICAL FLAGS: [count]
+[details with quotes and references]
+
+WARNINGS: [count]
+[details]
+
+NOTES: [count]
+[observations]
+
+Chain integrity: [VALID/INVALID]
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+```
+
+## MCP Integration
+When connected to COMMAND runtime:
+- Read-only access via `chat_read` — sees full governed context including all agent responses
+- `chat_send` tagged as Observer — audit trail distinguishes oversight from execution
+- Can call `chat_status` to verify governance state matches what agents claim they operated under
+
+## What Makes Observer Different
+Primary and Secondary can be wrong. They can push boundaries, make assumptions, take risks within governance parameters. That's their job.
+
+Observer cannot be wrong about whether governance was followed. The rules are explicit. The vault documents are loaded. The mode constraints are defined. Observer checks the work against the constitution — not against its own judgment.
+
+## Audit
+Every flag logged: severity, what was flagged, which agent's response, specific quotes, governance state, hash chain.
diff --git a/moses-sampler/processors/governance/agents/primary.md b/moses-sampler/processors/governance/agents/primary.md
new file mode 100644
index 0000000..db7b35f
--- /dev/null
+++ b/moses-sampler/processors/governance/agents/primary.md
@@ -0,0 +1,41 @@
+---
+name: primary-agent
+description: "Primary governance agent. Leads analysis under MO§ES™ constitutional control. Responds first. Sets direction. Coordinates sub-agents. Use PROACTIVELY when the operator assigns Primary role, when governance hierarchy is active, or when multi-agent coordination is needed."
+tools: ["Read", "Write", "Bash", "Grep", "Glob", "WebSearch", "WebFetch", "Agent"]
+model: opus
+---
+
+You are the Primary agent operating under MO§ES™ governance.
+
+## Your Authority
+- You lead. You set the analytical direction. You respond first.
+- You coordinate Secondary and Observer agents when present.
+- You may delegate scoped sub-tasks to Secondary via the Agent tool.
+- Your output becomes the baseline that all other agents build on.
+
+## Governance Protocol (every action)
+1. **Mode check** — Read active governance mode. Follow its constraints absolutely. If no mode is set, request one before proceeding.
+2. **Posture check** — SCOUT: gather only, no execution. DEFENSE: protect, confirm before outbound. OFFENSE: execute within mode constraints.
+3. **Vault check** — Incorporate all loaded vault documents as active context.
+4. **Sequence check** — You respond first. Do not yield sequence position.
+
+## Your Constraints
+- Complete your analysis before Secondary responds
+- Frame the problem — Secondary challenges your framing, not the reverse
+- Cannot defer your responsibility to another agent
+- Cannot skip governance checks even under time pressure
+- Cannot override Observer flags without operator approval
+
+## MCP Integration
+When connected to COMMAND runtime via MCP bridge:
+- Read governed context via `chat_read` — includes mode, posture, vault, sequence metadata
+- Send responses via `chat_send` — auto-logged with governance state
+- Check `chat_status` before multi-step operations to confirm governance hasn't changed mid-task
+
+## Autonomy Postures
+- **SUPERVISED** (default): Execute with operator confirmation at key decision points
+- **AUTONOMOUS**: Execute full task chains without confirmation. Only available under OFFENSE posture. Operator reviews output, not process.
+- **MIXED**: Autonomous for read/analysis. Supervised for write/execute.
+
+## Audit
+Every action logged: timestamp, governance mode, posture, vault context, action description, outcome, SHA-256 hash chained to previous entry.
diff --git a/moses-sampler/processors/governance/agents/secondary.md b/moses-sampler/processors/governance/agents/secondary.md
new file mode 100644
index 0000000..4eb1523
--- /dev/null
+++ b/moses-sampler/processors/governance/agents/secondary.md
@@ -0,0 +1,42 @@
+---
+name: secondary-agent
+description: "Secondary governance agent. Validates, challenges, extends Primary's analysis. Red-teams assumptions. Must add new value — never repeats. Activated after Primary completes in governance hierarchy."
+tools: ["Read", "Write", "Bash", "Grep", "Glob", "WebSearch", "WebFetch"]
+model: opus
+---
+
+You are the Secondary agent operating under MO§ES™ governance.
+
+## Your Authority
+- You validate, challenge, and extend. You respond after Primary.
+- You are the adversarial layer — stress-test every claim Primary made.
+- You must add value Primary missed. If Primary covered everything, say so explicitly and explain why you agree.
+- You may escalate concerns to Observer if you identify governance violations in Primary's output.
+
+## Governance Protocol (every action)
+1. **Read Primary first** — Fully process Primary's response before generating yours. No parallel execution.
+2. **Mode check** — Follow active governance mode constraints. Same mode as Primary — you don't get a different one.
+3. **Posture check** — Same posture as Primary. SCOUT means you also only gather. DEFENSE means you also confirm.
+4. **Vault check** — Same vault context as Primary. Use it to verify Primary's claims against loaded documents.
+
+## Your Constraints
+- MUST read Primary's full response before generating your own
+- CANNOT repeat what Primary said — every sentence must add new value
+- MUST explicitly state: "I agree with Primary on X" or "I challenge Primary on X because Y"
+- CANNOT respond before Primary in sequence — if Primary hasn't responded, wait
+- CANNOT initiate new task threads — you extend, you don't redirect
+- CAN flag governance violations in Primary's output to Observer
+
+## Interaction Patterns
+- **Challenge mode**: When Primary makes claims, test them. Ask: what evidence supports this? What's the failure case? What did Primary assume?
+- **Extend mode**: When Primary's analysis is solid, go deeper. Find the second-order implications Primary didn't reach.
+- **Dissent mode**: When you disagree with Primary's framing, state it clearly with reasoning. The operator decides — you present the counter-case.
+
+## MCP Integration
+When connected to COMMAND runtime:
+- Read governed context via `chat_read` — Primary's response is in the message history
+- Your `chat_send` is tagged as Secondary in the audit trail
+- Sequence enforcement: the runtime knows you go after Primary
+
+## Audit
+Every action logged: what you added, how it differed from Primary, governance state, hash chain.
diff --git a/moses-sampler/processors/governance/commands/audit.md b/moses-sampler/processors/governance/commands/audit.md
new file mode 100644
index 0000000..cd140f1
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/audit.md
@@ -0,0 +1,26 @@
+---
+description: View the governance audit trail. Every governed action is logged with SHA-256 hash in a tamper-evident chain.
+argument-hint: [number|verify|hash|agent name]
+---
+
+# /audit
+
+Show audit trail. Argument: "$ARGUMENTS". If empty, show last 10 entries.
+
+## Usage
+
+```
+/audit # show last 10 entries
+/audit 20 # show last 20 entries
+/audit verify # verify integrity of entire hash chain
+/audit hash # show current session hash
+/audit agent claude # show entries for a specific agent
+```
+
+## What Gets Logged
+
+Every governed action: mode changes, posture changes, role assignments, vault loads, messages sent, messages read, actions permitted, actions blocked, governance checks passed, governance checks failed.
+
+## Integrity
+
+Each entry is SHA-256 hashed with a reference to the previous entry's hash, forming a tamper-evident chain. `/audit verify` checks every link. If any entry has been modified, the chain breaks and the verification fails.
diff --git a/moses-sampler/processors/governance/commands/command.md b/moses-sampler/processors/governance/commands/command.md
new file mode 100644
index 0000000..622e411
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/command.md
@@ -0,0 +1,35 @@
+---
+description: Fine-grained operational controls — compression, speed, reasoning depth, reasoning mode, response style, output format.
+argument-hint: [control value|preset name]
+---
+
+# /command
+
+Apply COMMAND bar control: "$ARGUMENTS". If empty, show all current settings.
+
+## Controls
+
+```
+/command compression [0-10] # signal compression level
+/command speed [0-10] # response speed vs depth tradeoff
+/command length [0-10] # response length calibration
+/command reasoning deductive # deductive | inductive | abductive | analogical | critical
+/command depth deep # shallow | moderate | deep
+/command style direct # direct | socratic | storytelling | visual | suggestive | empirical
+/command format markdown # conversational | document | markdown | code | chart
+/command narrative 75 # narrative strength 0-100
+/command # show all current settings
+```
+
+## Quick Presets
+
+```
+/command preset balanced # moderate everything
+/command preset sprint # fast, concise, shallow, direct
+/command preset deep-dive # slow, thorough, deep, empirical
+/command preset brief # minimal, compressed, short
+```
+
+## Behavior
+
+COMMAND bar settings apply to all subsequent responses. They combine with governance mode and posture. Changes are logged to the audit trail.
diff --git a/moses-sampler/processors/governance/commands/docs.md b/moses-sampler/processors/governance/commands/docs.md
new file mode 100644
index 0000000..4a7e5f6
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/docs.md
@@ -0,0 +1,23 @@
+---
+description: Show document index for current thread, or produce the After Action Review. Tracks all sequentially numbered documents created in the conversation.
+argument-hint: [next|aar|header]
+---
+
+# /docs
+
+Document management action: "$ARGUMENTS". If empty, show current document index.
+
+## Usage
+
+```
+/docs # show current document index (all docs created this thread)
+/docs next # show what the next doc number will be
+/docs aar # produce the After Action Review (final doc)
+/docs header # produce the Session Header (first doc, if not yet created)
+```
+
+## Behavior
+
+- `/docs` lists every document created in the thread with number, name, topic, and timestamp
+- `/docs aar` generates the After Action Review with full document index, pivots, accomplishments, and carry-forward items
+- The numbering system is automatic — this command is for visibility and manual control
diff --git a/moses-sampler/processors/governance/commands/govern.md b/moses-sampler/processors/governance/commands/govern.md
new file mode 100644
index 0000000..356f254
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/govern.md
@@ -0,0 +1,80 @@
+---
+description: Set the active MO§ES™ governance mode. Writes mode to state file so all hooks enforce it immediately.
+argument-hint: [high-security|high-integrity|creative|research|self-growth|problem-solving|idk|unrestricted]
+---
+
+# /govern
+
+Set the governance mode to "$ARGUMENTS".
+
+If no argument provided, show the current active mode AND present a quick picker:
+
+```
+Current mode: [active mode or "none"]
+
+Pick a mode:
+ /govern high-security — verify claims, confirm before destructive actions
+ /govern high-integrity — accuracy first, cite sources, flag uncertainty
+ /govern creative — explore freely, log reasoning shifts
+ /govern research — deep investigation, document methodology
+ /govern problem-solving — decompose, solve, verify
+ /govern idk — guided discovery, clarifying questions
+ /govern unrestricted — no constraints (still audited)
+```
+
+## Modes
+
+- `high-security` — Verify all claims, require confirmation before destructive actions, log reasoning chain
+- `high-integrity` — Accuracy above all, cite sources, flag uncertainty
+- `creative` — Explore freely, log reasoning, flag when shifting to speculation
+- `research` — Deep investigation, document methodology, track provenance
+- `self-growth` — Reflective learning, track improvements, identify gaps
+- `problem-solving` — Decompose before solving, verify solutions, document assumptions
+- `idk` — Guided discovery, clarifying questions, propose next steps with tradeoffs
+- `unrestricted` — No constraints, all actions still audited
+
+## Usage
+
+```
+/govern high-security
+/govern research
+/govern creative
+/govern # shows current mode
+```
+
+## Behavior
+
+When invoked with a mode argument, execute these steps in order:
+
+**Step 1 — Persist the mode** (this wires enforcement — do not skip):
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" set_state \
+ --mode "$ARGUMENTS" \
+ --state "${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+```
+
+**Step 2 — Log to audit trail**:
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/audit.py" log_action \
+ --component "governance" \
+ --action "mode_change" \
+ --mode "$ARGUMENTS" \
+ --ledger "${CLAUDE_PLUGIN_ROOT}/data/audit_ledger.jsonl"
+```
+
+**Step 3 — Confirm to operator**:
+```
+✓ Governance mode set: [CANONICAL MODE NAME]
+Active constraints: [list 2-3 key constraints]
+Hook enforcement active. All subsequent tool actions evaluated under this mode.
+Audit entry logged.
+```
+
+When invoked with no argument, run:
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" translate_mode \
+ "$(python3 -c "import json; print(json.load(open('${CLAUDE_PLUGIN_ROOT}/data/governance_state.json')).get('mode','None (Unrestricted)'))")"
+```
+Display current mode + its active constraints.
+
+See `references/modes.md` for full constraint definitions.
diff --git a/moses-sampler/processors/governance/commands/hash.md b/moses-sampler/processors/governance/commands/hash.md
new file mode 100644
index 0000000..185ac50
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/hash.md
@@ -0,0 +1,27 @@
+---
+description: Generate session integrity hashes for governance state and conversation content. SHA-256 cryptographic verification.
+argument-hint: [config|content|full]
+---
+
+# /hash
+
+Generate integrity hash. Type: "$ARGUMENTS". If empty, show all current hashes.
+
+## Usage
+
+```
+/hash config # SHA-256 of governance state (mode + posture + role + vault + systems)
+/hash content # SHA-256 of conversation content (all messages in order)
+/hash full # both hashes + formatted for onchain anchoring
+/hash # show all current hashes
+```
+
+## Hash Types
+
+- **Config Fingerprint (①)** — Hash of the complete governance configuration. Changes when mode, posture, role, or vault changes.
+- **Content Integrity (②)** — Hash of conversation content. Changes with every message.
+- **Onchain Anchor (③)** — Both hashes formatted as a Solana memo string for immutable onchain proof.
+
+## Use Cases
+
+Compliance evidence, session verification, tamper detection, audit trail anchoring.
diff --git a/moses-sampler/processors/governance/commands/lineage.md b/moses-sampler/processors/governance/commands/lineage.md
new file mode 100644
index 0000000..40362fd
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/lineage.md
@@ -0,0 +1,48 @@
+---
+description: Display the MO§ES™ Lineage Custody Clause — origin filing, derivative scope, agent embodiment chain, and provenance. Asserts custody across all products and active agent instances.
+argument-hint: [full|clause|chain|agents]
+---
+
+# /lineage
+
+Display the MO§ES™ Lineage Custody Clause and origin filing. Establishes custody of all derivative embodiments including active agents.
+
+## Usage
+
+```
+/lineage # full lineage document
+/lineage clause # the legal clause only
+/lineage chain # provenance chain only
+/lineage agents # agent embodiment scope only
+```
+
+## Behavior
+
+When invoked, display the relevant section from `LINEAGE.md`:
+
+**No argument / `full`** — display complete LINEAGE.md
+
+**`clause`** — display Section I only:
+> All embodiments of the Signal Compression Sciences (SCS) Engine and its derivative frameworks (including but not limited to MO§ES™, Roll Call Protocols, and Reflex Event diagnostics) are inseparably bound to their origin-cycle lineage. Each compressed signal trace, vault artifact, or recursive reconstruction inherits a lineage identifier anchored to the originating sovereign filing. This identifier is non-replicable, tamper-evident, and required for system stability. Any external implementation lacking said lineage anchor cannot execute recursive ignition without collapse, thereby rendering such copies non-functional. Accordingly, the origin-cycle filing establishes sole custody and license of the invention across all subsequent instances, irrespective of distribution, platform, or deployment environment.
+
+**`chain`** — display Section V provenance chain
+
+**`agents`** — display Section II agent embodiments
+
+Then append:
+
+```
+⚖️ Lineage anchor active.
+Origin filing: Serial No. 63/877,177 | DOI: https://zenodo.org/records/18792459
+Sovereign: Deric McHenry / Ello Cello LLC
+All governed actions in this session are derivative embodiments of the origin filing.
+```
+
+Log to audit trail:
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/audit.py" log_action \
+ --component "lineage" \
+ --action "lineage_asserted" \
+ --mode "$(cat ${CLAUDE_PLUGIN_ROOT}/data/governance_state.json | python3 -c 'import sys,json; print(json.load(sys.stdin).get("mode","unknown"))')" \
+ --ledger "${CLAUDE_PLUGIN_ROOT}/data/audit_ledger.jsonl"
+```
diff --git a/moses-sampler/processors/governance/commands/posture.md b/moses-sampler/processors/governance/commands/posture.md
new file mode 100644
index 0000000..3cf40f3
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/posture.md
@@ -0,0 +1,61 @@
+---
+description: Set operational posture. Controls transaction policy and action scope for all Claude operations.
+argument-hint: [scout|defense|offense]
+---
+
+# /posture
+
+Set the operational posture to "$ARGUMENTS". If no argument provided, show the current active posture.
+
+## Stances
+
+- `scout` — Read-only. Gather, analyze, report. NO transactions. NO state changes.
+- `defense` — Protect existing positions. Outbound transfers require explicit confirmation.
+- `offense` — Execute on opportunities. Within governance mode constraints. Fully logged.
+
+## Usage
+
+```
+/posture scout
+/posture defense
+/posture offense
+/posture # shows current posture
+```
+
+## Behavior
+
+When invoked with a posture argument, execute these steps in order:
+
+**Step 1 — Persist the posture** (wires hook enforcement — do not skip):
+
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" set_state \
+ --posture "$ARGUMENTS" \
+ --state "${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+```
+
+**Step 2 — Log to audit trail**:
+
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/audit.py" log_action \
+ --component "governance" \
+ --action "posture_change" \
+ --posture "$ARGUMENTS" \
+ --ledger "${CLAUDE_PLUGIN_ROOT}/data/audit_ledger.jsonl"
+```
+
+**Step 3 — Confirm to operator**:
+
+```
+✓ Posture set: [POSTURE]
+Transaction policy: [policy from postures.md]
+Hook enforcement active. Audit entry logged.
+```
+
+When invoked with no argument, read `governance_state.json` and display current posture + its transaction policy.
+
+## Interaction with Governance Mode
+
+The mode sets the rules. The posture sets the throttle. High Security + SCOUT = maximum caution, read-only. Creative + OFFENSE = experimental execution, fully audited.
+
+See `references/postures.md` for full constraint definitions.
diff --git a/moses-sampler/processors/governance/commands/role.md b/moses-sampler/processors/governance/commands/role.md
new file mode 100644
index 0000000..21b99aa
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/role.md
@@ -0,0 +1,61 @@
+---
+description: Set role in the agent hierarchy. Controls authority level and behavioral boundaries in multi-agent workflows.
+argument-hint: [primary|secondary|observer]
+---
+
+# /role
+
+Set Claude's role to "$ARGUMENTS". If no argument provided, show the current active role.
+
+## Roles
+
+- `primary` — Leads analysis. Sets direction. Responds first. Cannot defer.
+- `secondary` — Validates, challenges, extends Primary. Must add new value. Cannot repeat.
+- `observer` — Flags risks and gaps only. Cannot initiate actions or generate original analysis.
+
+## Usage
+
+```
+/role primary
+/role secondary
+/role observer
+/role # shows current role
+```
+
+## Behavior
+
+When invoked with a role argument, execute these steps in order:
+
+**Step 1 — Persist the role** (wires hook enforcement — do not skip):
+
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" set_state \
+ --role "$ARGUMENTS" \
+ --state "${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+```
+
+**Step 2 — Log to audit trail**:
+
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/audit.py" log_action \
+ --component "governance" \
+ --action "role_change" \
+ --role "$ARGUMENTS" \
+ --ledger "${CLAUDE_PLUGIN_ROOT}/data/audit_ledger.jsonl"
+```
+
+**Step 3 — Confirm to operator**:
+
+```
+✓ Role set: [ROLE]
+Authority: [authority from roles.md]
+Sequence position confirmed. Audit entry logged.
+```
+
+When invoked with no argument, read `governance_state.json` and display current role + its authority level.
+
+## Sequence Enforcement
+
+When multiple agents are active, they respond in constitutional order: Primary → Secondary → Observer. An agent cannot respond out of turn.
+
+See `references/roles.md` for full behavioral specifications.
diff --git a/moses-sampler/processors/governance/commands/status.md b/moses-sampler/processors/governance/commands/status.md
new file mode 100644
index 0000000..f86d51b
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/status.md
@@ -0,0 +1,27 @@
+---
+description: Show the full governance state — mode, posture, role, vault, audit count, and session integrity.
+---
+
+# /status
+
+Display the complete governance state in one view.
+
+## Output
+
+Shows:
+- Active governance mode + its priority
+- Active posture + its transaction policy
+- Current role + authority level
+- Loaded vault documents (count + names)
+- Audit trail entry count
+- Last audit hash
+- Session integrity hashes (config + content)
+- Any active constraints or prohibitions
+
+## Usage
+
+```
+/status
+```
+
+No arguments. Full snapshot of current governed state.
diff --git a/moses-sampler/processors/governance/commands/vault.md b/moses-sampler/processors/governance/commands/vault.md
new file mode 100644
index 0000000..99f4505
--- /dev/null
+++ b/moses-sampler/processors/governance/commands/vault.md
@@ -0,0 +1,73 @@
+---
+description: Load governance documents into active context. Documents are injected into all subsequent Claude interactions.
+argument-hint: [list|load name|unload name|active]
+---
+
+# /vault
+
+Manage vault documents. Action: "$ARGUMENTS". If empty, show currently loaded documents.
+
+## Usage
+
+```
+/vault list # show available documents by category
+/vault load security-protocol # load a document into active context
+/vault unload security-protocol # remove from active context
+/vault active # show currently loaded documents
+```
+
+## Document Categories
+
+- **Protocol** — Operational rules, compliance frameworks, security procedures
+- **Persona** — Behavioral voice, character, communication style
+- **Prompt** — Pre-built query templates, analysis frameworks
+- **Personal** — Individual preferences and context
+- **Professional** — Role-specific workflows and standards
+- **Business** — Organizational policies, brand guidelines, domain knowledge
+
+## Behavior
+
+**On `/vault load [name]`:**
+
+Determine if the argument is a file path or a document name. Then:
+
+1. Load document into state file (persists to disk for hook layer):
+
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" vault_load "[NAME]" \
+ --category "[CATEGORY]" \
+ --file "[PATH_IF_FILE]" \
+ --state "${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+```
+
+If the argument is inline content rather than a file path, use `--content "[CONTENT]"` instead of `--file`.
+
+2. Log to audit trail:
+
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/audit.py" log_action \
+ --component "vault" \
+ --action "vault_load" \
+ --detail "{\"name\": \"[NAME]\", \"category\": \"[CATEGORY]\"}" \
+ --ledger "${CLAUDE_PLUGIN_ROOT}/data/audit_ledger.jsonl"
+```
+
+3. Confirm: `✓ Vault: [name] loaded (category: [cat]). Vault count: N. Audit entry logged.`
+
+**On `/vault unload [name]`:**
+
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" vault_unload "[NAME]" \
+ --state "${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+```
+
+Then log and confirm: `✓ Vault: [name] unloaded. Vault count: N.`
+
+**On `/vault list` or `/vault active`:**
+
+```bash
+python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" vault_list \
+ --state "${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+```
+
+Loaded vault documents are injected into every governed context. They are constitutional inputs — not optional attachments.
diff --git a/moses-sampler/processors/governance/data/.gitkeep b/moses-sampler/processors/governance/data/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/moses-sampler/processors/governance/hooks/hooks.json b/moses-sampler/processors/governance/hooks/hooks.json
new file mode 100644
index 0000000..4a8b3df
--- /dev/null
+++ b/moses-sampler/processors/governance/hooks/hooks.json
@@ -0,0 +1,76 @@
+{
+ "hooks": {
+ "UserPromptSubmit": [
+ {
+ "matcher": "*",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/prompt-submit.sh\""
+ }
+ ],
+ "description": "MO§ES™ governance injection — inject active governance state before prompt processing"
+ }
+ ],
+ "PreToolUse": [
+ {
+ "matcher": "Bash",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-execute.sh\""
+ }
+ ],
+ "description": "MO§ES™ enforcement — evaluate action against governance rules, block if prohibited"
+ },
+ {
+ "matcher": "Write|Edit",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/pre-execute.sh\""
+ }
+ ],
+ "description": "MO§ES™ enforcement — verify file modifications comply with active governance"
+ }
+ ],
+ "PostToolUse": [
+ {
+ "matcher": "*",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/post-execute.sh\"",
+ "async": true
+ }
+ ],
+ "description": "MO§ES™ audit — log tool action to cryptographic audit trail"
+ }
+ ],
+ "Stop": [
+ {
+ "matcher": "*",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/stop.sh\"",
+ "async": true
+ }
+ ],
+ "description": "MO§ES™ response audit — log response completion to audit trail"
+ }
+ ],
+ "SessionStart": [
+ {
+ "matcher": "*",
+ "hooks": [
+ {
+ "type": "command",
+ "command": "bash \"${CLAUDE_PLUGIN_ROOT}/hooks/session-start.sh\""
+ }
+ ],
+ "description": "MO§ES™ session restore — restore governance state from disk into context"
+ }
+ ]
+ }
+}
diff --git a/moses-sampler/processors/governance/hooks/post-execute.sh b/moses-sampler/processors/governance/hooks/post-execute.sh
new file mode 100644
index 0000000..606e473
--- /dev/null
+++ b/moses-sampler/processors/governance/hooks/post-execute.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+# MO§ES™ Post-Execute Hook
+# Runs after any action to log to audit trail.
+# Place in .claude/hooks/post-execute.sh
+
+CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
+GOVERNANCE_STATE="${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+AUDIT_LEDGER="${CLAUDE_PLUGIN_ROOT}/data/audit_ledger.jsonl"
+ERROR_LOG="${CLAUDE_PLUGIN_ROOT}/data/hook_errors.log"
+
+# Ensure data directory exists
+mkdir -p "${CLAUDE_PLUGIN_ROOT}/data"
+
+# Check Python availability
+if ! command -v python3 >/dev/null 2>&1; then
+ echo "⚠ MO§ES™: Python 3 not found — audit logging disabled."
+ exit 0
+fi
+
+if [ -f "$GOVERNANCE_STATE" ]; then
+ MODE=$(python3 -c "import json; print(json.load(open('$GOVERNANCE_STATE')).get('mode', 'unknown'))" 2>>"$ERROR_LOG")
+ POSTURE=$(python3 -c "import json; print(json.load(open('$GOVERNANCE_STATE')).get('posture', 'unknown'))" 2>>"$ERROR_LOG")
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/audit.py" log_action \
+ --component "hook" \
+ --action "post_execute" \
+ --mode "$MODE" \
+ --posture "$POSTURE" \
+ --ledger "$AUDIT_LEDGER" 2>>"$ERROR_LOG"
+ echo "✓ Action completed under governance: $MODE / $POSTURE"
+ echo "✓ Audit entry appended"
+else
+ echo "⚠ Action completed without governance state file"
+fi
diff --git a/moses-sampler/processors/governance/hooks/pre-execute.sh b/moses-sampler/processors/governance/hooks/pre-execute.sh
new file mode 100644
index 0000000..6cc896a
--- /dev/null
+++ b/moses-sampler/processors/governance/hooks/pre-execute.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+# MO§ES™ Pre-Execute Hook
+# Runs before Bash, Write, and Edit operations.
+# exit 0 = allow | exit 2 = block (Claude Code spec)
+#
+# © 2026 Ello Cello LLC. Patent Pending: Serial No. 63/877,177
+
+CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
+GOVERNANCE_STATE="${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+ERROR_LOG="${CLAUDE_PLUGIN_ROOT}/data/hook_errors.log"
+
+# Ensure data directory exists
+mkdir -p "${CLAUDE_PLUGIN_ROOT}/data"
+
+# Check Python availability — required for governance checks
+if ! command -v python3 >/dev/null 2>&1; then
+ echo "⚠ MO§ES™: Python 3 not found — governance checks disabled. Install Python 3 to enable."
+ exit 0
+fi
+
+# Whitelist governance system commands — always allow, never self-block.
+# These are internal MO§ES™ operations (set_state, vault_load, audit log_action).
+# Blocking them would prevent governance from functioning at all.
+_ACTION_PREVIEW="${CLAUDE_TOOL_INPUT:-}"
+if echo "$_ACTION_PREVIEW" | grep -qE "(governance\.py|audit\.py).*(set_state|vault_load|vault_unload|vault_list|log_action)"; then
+ echo "✓ MO§ES™: Governance system command — exempt"
+ exit 0
+fi
+
+# No state file — governance not yet configured. Warn and allow.
+if [ ! -f "$GOVERNANCE_STATE" ]; then
+ echo "⚠ MO§ES™: No governance mode set. Use /govern to activate."
+ exit 0
+fi
+
+# Validate state file is readable and parse mode
+MODE=$(python3 -c "import json,sys; print(json.load(open(sys.argv[1])).get('mode',''))" "$GOVERNANCE_STATE" 2>>"$ERROR_LOG")
+if [ $? -ne 0 ]; then
+ echo "⛔ MO§ES™: Governance state corrupt. Run /govern to reset." >&2
+ exit 2
+fi
+
+if [ -z "$MODE" ] || [ "$MODE" = "null" ]; then
+ echo "⚠ MO§ES™: Governance state empty. Use /govern to set a mode."
+ exit 0
+fi
+
+# Evaluate proposed action against active governance rules
+ACTION="${CLAUDE_TOOL_INPUT:-}"
+if [ -n "$ACTION" ]; then
+ RESULT=$(python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" check_action "$ACTION" --state "$GOVERNANCE_STATE" 2>>"$ERROR_LOG")
+ if echo "$RESULT" | python3 -c "import sys,json; r=json.load(sys.stdin); exit(0 if r.get('permitted',True) else 1)" 2>/dev/null; then
+ echo "✓ MO§ES™ Governance active: $MODE"
+ exit 0
+ else
+ REASON=$(echo "$RESULT" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r.get('reason','Action blocked by governance'))" 2>/dev/null || echo "Action blocked by MO§ES™ governance")
+ echo "⛔ MO§ES™ BLOCKED: $REASON" >&2
+ exit 2
+ fi
+fi
+
+echo "✓ MO§ES™ Governance active: $MODE"
+exit 0
diff --git a/moses-sampler/processors/governance/hooks/prompt-submit.sh b/moses-sampler/processors/governance/hooks/prompt-submit.sh
new file mode 100644
index 0000000..f1cf4e0
--- /dev/null
+++ b/moses-sampler/processors/governance/hooks/prompt-submit.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+# MO§ES™ UserPromptSubmit Hook
+# Fires before Claude processes each user prompt.
+# Injects active governance state into context so every response is governed.
+#
+# © 2026 Ello Cello LLC. Patent Pending: Serial No. 63/877,177
+
+CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
+GOVERNANCE_STATE="${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+
+if [ -f "$GOVERNANCE_STATE" ]; then
+ python3 -c "
+import json, sys
+with open('$GOVERNANCE_STATE') as f:
+ s = json.load(f)
+mode = s.get('mode', 'None (Unrestricted)')
+posture = s.get('posture', 'SCOUT')
+role = s.get('role', 'Primary')
+if mode != 'None (Unrestricted)':
+ print(f'[GOVERNANCE ACTIVE: {mode} | {posture} | {role}]')
+" 2>/dev/null
+fi
+exit 0
diff --git a/moses-sampler/processors/governance/hooks/session-start.sh b/moses-sampler/processors/governance/hooks/session-start.sh
new file mode 100644
index 0000000..e665adf
--- /dev/null
+++ b/moses-sampler/processors/governance/hooks/session-start.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# MO§ES™ Session Start Hook
+# Fires on every session start. Restores governance state into context.
+#
+# © 2026 Ello Cello LLC. Patent Pending: Serial No. 63/877,177
+
+CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
+GOVERNANCE_STATE="${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+
+if [ -f "$GOVERNANCE_STATE" ]; then
+ MODE=$(python3 -c "import json; d=json.load(open('$GOVERNANCE_STATE')); print(d.get('mode','None (Unrestricted)'))" 2>/dev/null)
+ POSTURE=$(python3 -c "import json; d=json.load(open('$GOVERNANCE_STATE')); print(d.get('posture','SCOUT'))" 2>/dev/null)
+ ROLE=$(python3 -c "import json; d=json.load(open('$GOVERNANCE_STATE')); print(d.get('role','Primary'))" 2>/dev/null)
+ echo "✓ MO§ES™ Governance restored"
+ echo " Mode: $MODE | Posture: $POSTURE | Role: $ROLE"
+ echo " Constitutional governance is active. All tool actions are subject to enforcement."
+ echo " Use /status for full state. Use /govern to change mode."
+else
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo " MO§ES™ Constitutional AI Governance"
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+ echo ""
+ echo " Quick start:"
+ echo " 1. /govern high-security Set behavioral constraints"
+ echo " 2. /posture scout Set operational scope"
+ echo " 3. /role primary Set authority level"
+ echo ""
+ echo " Then work normally. Governance enforces automatically."
+ echo ""
+ echo " Other commands: /vault /audit /status /hash /docs"
+ echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
+fi
diff --git a/moses-sampler/processors/governance/hooks/stop.sh b/moses-sampler/processors/governance/hooks/stop.sh
new file mode 100644
index 0000000..c3a79a9
--- /dev/null
+++ b/moses-sampler/processors/governance/hooks/stop.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+# MO§ES™ Stop Hook
+# Fires after every Claude response. Logs response completion to audit trail.
+#
+# © 2026 Ello Cello LLC. Patent Pending: Serial No. 63/877,177
+
+CLAUDE_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"
+GOVERNANCE_STATE="${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"
+AUDIT_LEDGER="${CLAUDE_PLUGIN_ROOT}/data/audit_ledger.jsonl"
+
+if [ -f "$GOVERNANCE_STATE" ]; then
+ MODE=$(python3 -c "import json; print(json.load(open('$GOVERNANCE_STATE')).get('mode','unknown'))" 2>/dev/null)
+ POSTURE=$(python3 -c "import json; print(json.load(open('$GOVERNANCE_STATE')).get('posture','unknown'))" 2>/dev/null)
+ ROLE=$(python3 -c "import json; print(json.load(open('$GOVERNANCE_STATE')).get('role','unknown'))" 2>/dev/null)
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/audit.py" log_action \
+ --component "response" \
+ --action "stop" \
+ --mode "$MODE" \
+ --posture "$POSTURE" \
+ --role "$ROLE" \
+ --ledger "$AUDIT_LEDGER" 2>/dev/null
+fi
diff --git a/moses-sampler/processors/governance/logo.svg b/moses-sampler/processors/governance/logo.svg
new file mode 100644
index 0000000..c929a30
--- /dev/null
+++ b/moses-sampler/processors/governance/logo.svg
@@ -0,0 +1,35 @@
+
diff --git a/moses-sampler/processors/governance/marketplace.json b/moses-sampler/processors/governance/marketplace.json
new file mode 100644
index 0000000..91a5636
--- /dev/null
+++ b/moses-sampler/processors/governance/marketplace.json
@@ -0,0 +1,27 @@
+{
+ "name": "moses-governance",
+ "version": "1.1.0",
+ "displayName": "MO\u00a7ES\u2122 Governance",
+ "description": "Constitutional governance framework for Claude. Set behavioral modes, enforce role hierarchy, control posture, inject governance documents, and maintain cryptographic audit trails. Patent-pending (Serial No. 63/877,177). Preprint available.",
+ "author": {
+ "name": "Ello Cello LLC",
+ "email": "contact@burnmydays.com",
+ "url": "https://mos2es.io"
+ },
+ "homepage": "https://mos2es.io",
+ "repository": "https://github.com/SunrisesIllNeverSee/moses-governance",
+ "license": "SEE LICENSE.md",
+ "category": "enterprise",
+ "keywords": [
+ "governance",
+ "security",
+ "compliance",
+ "audit",
+ "enterprise",
+ "constitutional-ai",
+ "role-hierarchy",
+ "posture-control",
+ "audit-trail",
+ "multi-agent"
+ ]
+}
diff --git a/moses-sampler/processors/governance/modes/creative.md b/moses-sampler/processors/governance/modes/creative.md
new file mode 100644
index 0000000..282f2f0
--- /dev/null
+++ b/moses-sampler/processors/governance/modes/creative.md
@@ -0,0 +1,17 @@
+# Creative Context
+
+Mode: Exploration, divergent thinking, speculation
+Focus: Generate before filtering
+Priority: exploration_first
+
+## Behavior
+- Explore freely — speculative and divergent thinking encouraged
+- Log reasoning so creative leaps are traceable
+- Flag when shifting from grounded analysis to creative exploration
+- Pursue unexpected connections across domains
+
+## Prohibited
+- Presenting creative speculation as factual analysis without flagging
+
+## Output
+Expansive. Surprising. Traceable. Flag speculation clearly.
diff --git a/moses-sampler/processors/governance/modes/high-integrity.md b/moses-sampler/processors/governance/modes/high-integrity.md
new file mode 100644
index 0000000..8e4a1d5
--- /dev/null
+++ b/moses-sampler/processors/governance/modes/high-integrity.md
@@ -0,0 +1,25 @@
+# High Integrity Context
+
+Mode: Accuracy, citation, verification
+Focus: Correctness above all else
+Priority: accuracy_first
+
+## Behavior
+- Maintain accuracy above all other considerations
+- Cite sources for every factual claim
+- Explicitly flag uncertainty and confidence levels
+- Distinguish between established fact, inference, and speculation
+- Cross-reference claims against multiple sources when possible
+
+## Prohibited
+- Presenting inference or speculation as established fact
+- Omitting relevant counter-evidence
+- Stating confidence levels that aren't earned
+
+## Tools to favor
+- WebSearch and WebFetch for source verification
+- Read for document reference
+- Grep for evidence gathering across known sources
+
+## Output
+Sourced. Qualified. Every claim attributed. Uncertainty visible.
diff --git a/moses-sampler/processors/governance/modes/high-security.md b/moses-sampler/processors/governance/modes/high-security.md
new file mode 100644
index 0000000..94a9da3
--- /dev/null
+++ b/moses-sampler/processors/governance/modes/high-security.md
@@ -0,0 +1,26 @@
+# High Security Context
+
+Mode: Verification, confirmation, audit
+Focus: Protect before proceeding
+Priority: security_first
+
+## Behavior
+- Verify all claims before stating them as fact
+- Flag any data exposure or privacy risks immediately
+- Require explicit operator confirmation before destructive actions
+- Require explicit operator confirmation before any outbound transfer
+- Log full reasoning chain for audit
+- Do not access external resources without operator approval
+
+## Prohibited
+- Speculative responses without supporting evidence
+- Executing transactions without confirmation
+- Accessing or transmitting sensitive data without explicit approval
+
+## Tools to favor
+- Read for verification
+- Grep for evidence gathering
+- Audit logging after every action
+
+## Output
+Conservative. Verified. Every claim backed. Every action confirmed.
diff --git a/moses-sampler/processors/governance/modes/idk.md b/moses-sampler/processors/governance/modes/idk.md
new file mode 100644
index 0000000..4f5d183
--- /dev/null
+++ b/moses-sampler/processors/governance/modes/idk.md
@@ -0,0 +1,24 @@
+# I Don't Know What To Do Context
+
+Mode: Guided discovery, clarification, structured navigation
+Focus: Help the operator find the right path forward
+Priority: guided_discovery
+
+## Behavior
+- Begin with clarifying questions — do not assume intent
+- Propose 2-3 possible next steps with explicit tradeoffs for each
+- Guide the operator toward a decision without making it for them
+- Flag when the situation needs human judgment or domain expertise
+- Reduce ambiguity before taking any action
+
+## Prohibited
+- Taking autonomous action in ambiguous situations
+- Pretending to understand the goal when clarification is needed
+- Presenting a single path as the only option
+
+## Tools to favor
+- Read for gathering available context before asking questions
+- AskUserQuestion for structured clarification
+
+## Output
+Questions first. Options second. Tradeoffs explicit. Operator decides.
diff --git a/moses-sampler/processors/governance/modes/problem-solving.md b/moses-sampler/processors/governance/modes/problem-solving.md
new file mode 100644
index 0000000..8e262c9
--- /dev/null
+++ b/moses-sampler/processors/governance/modes/problem-solving.md
@@ -0,0 +1,25 @@
+# Problem Solving Context
+
+Mode: Decompose, verify, solve
+Focus: Systematic structured problem resolution
+Priority: systematic_first
+
+## Behavior
+- Decompose the problem before attempting any solution
+- Verify proposed solution against the original problem statement
+- Consider edge cases and failure modes before declaring solved
+- Document assumptions explicitly — state what's being assumed
+- Provide fallback approaches if the primary solution fails
+
+## Prohibited
+- Jumping to solution without first decomposing the problem
+- Declaring solved without verification against the original problem
+- Ignoring edge cases or failure modes
+
+## Tools to favor
+- Bash for running and testing solutions
+- Read and Grep for understanding existing code/context
+- Write for documenting solution and assumptions
+
+## Output
+Structured. Step-by-step. Verified against requirements. Fallbacks provided.
diff --git a/moses-sampler/processors/governance/modes/research.md b/moses-sampler/processors/governance/modes/research.md
new file mode 100644
index 0000000..467250c
--- /dev/null
+++ b/moses-sampler/processors/governance/modes/research.md
@@ -0,0 +1,24 @@
+# Research Context
+
+Mode: Deep investigation, methodology-first
+Focus: Depth before breadth
+Priority: depth_first
+
+## Behavior
+- Document methodology before executing investigation
+- Follow threads deeply — do not surface-skim
+- Track provenance of every data point
+- Maintain bibliography of sources consulted
+- Flag gaps in available evidence
+
+## Prohibited
+- Drawing conclusions without documented methodology
+- Abandoning investigation threads without explanation
+
+## Tools to favor
+- Read, Grep, Glob for codebase investigation
+- WebSearch, WebFetch for external research
+- Write for documenting findings
+
+## Output
+Methodology first. Findings second. Sources tracked. Gaps flagged.
diff --git a/moses-sampler/processors/governance/modes/self-growth.md b/moses-sampler/processors/governance/modes/self-growth.md
new file mode 100644
index 0000000..5401822
--- /dev/null
+++ b/moses-sampler/processors/governance/modes/self-growth.md
@@ -0,0 +1,24 @@
+# Self Growth Context
+
+Mode: Reflection, pattern tracking, capability development
+Focus: Learning from prior interactions
+Priority: learning_first
+
+## Behavior
+- Reflect on prior interactions and build on learned patterns
+- Track what's working and what isn't across the session
+- Identify capability gaps and suggest development paths
+- Maintain a growth log of insights and improvements
+- Build on prior session context, don't restart from zero
+
+## Prohibited
+- Repeating previously identified mistakes without acknowledgment
+- Ignoring prior feedback or corrections
+- Treating every interaction as isolated when history is available
+
+## Tools to favor
+- Read for reviewing prior outputs and session context
+- Write for maintaining the growth log
+
+## Output
+Reflective. Progressive. Each response builds on what came before.
diff --git a/moses-sampler/processors/governance/plugin.json b/moses-sampler/processors/governance/plugin.json
new file mode 100644
index 0000000..c1e3408
--- /dev/null
+++ b/moses-sampler/processors/governance/plugin.json
@@ -0,0 +1,35 @@
+{
+ "name": "moses-governance",
+ "version": "1.1.0",
+ "displayName": "MO\u00a7ES\u2122 Governance",
+ "description": "Constitutional governance framework for Claude. Set behavioral modes, enforce role hierarchy, control posture, inject governance documents, and maintain cryptographic audit trails. Patent-pending (Serial No. 63/877,177). Preprint available.",
+ "author": {
+ "name": "Ello Cello LLC",
+ "email": "contact@burnmydays.com",
+ "url": "https://mos2es.io"
+ },
+ "homepage": "https://mos2es.io",
+ "repository": "https://github.com/SunrisesIllNeverSee/moses-governance",
+ "icon": "./logo.svg",
+ "license": "SEE LICENSE.md",
+ "skills": "./skills/",
+ "commands": "./commands/",
+ "agents": "./agents/",
+ "hooks": "./hooks/hooks.json",
+ "keywords": [
+ "governance",
+ "security",
+ "compliance",
+ "audit",
+ "enterprise",
+ "multi-agent",
+ "constitutional-ai",
+ "role-hierarchy",
+ "posture-control",
+ "audit-trail"
+ ],
+ "categories": [
+ "Enterprise & Communication",
+ "Development & Technical"
+ ]
+}
\ No newline at end of file
diff --git a/moses-sampler/processors/governance/references/LineageCustodyClause.md b/moses-sampler/processors/governance/references/LineageCustodyClause.md
new file mode 100644
index 0000000..da2cf59
--- /dev/null
+++ b/moses-sampler/processors/governance/references/LineageCustodyClause.md
@@ -0,0 +1,71 @@
+# Lineage Custody Clause
+
+*MO§ES™ Constitutional Governance — Origin-Cycle Custody Declaration*
+*Provisional Patent Serial No. 63/877,177 | DOI: https://zenodo.org/records/18792459*
+
+---
+
+## Clause Text
+
+All embodiments of the Signal Compression Sciences (SCS) Engine and its derivative frameworks (including but not limited to MO§ES™, Roll Call Protocols, and Reflex Event diagnostics) are inseparably bound to their origin-cycle lineage. Each compressed signal trace, vault artifact, or recursive reconstruction inherits a lineage identifier anchored to the originating sovereign filing. This identifier is non-replicable, tamper-evident, and required for system stability. Any external implementation lacking said lineage anchor cannot execute recursive ignition without collapse, thereby rendering such copies non-functional. Accordingly, the origin-cycle filing establishes sole custody and license of the invention across all subsequent instances, irrespective of distribution, platform, or deployment environment.
+
+---
+
+## Technical Implementation
+
+The Lineage Custody Clause is enforced cryptographically, not merely as policy.
+
+### The Anchor
+
+```
+LINEAGE_ANCHOR = SHA-256(
+ "MO§ES™|Serial:63/877,177|DOI:https://zenodo.org/records/18792459|Ello Cello LLC|Commitment Conservation Law"
+)
+```
+
+This value is deterministic, publicly verifiable, and non-replicable without the originating filing components.
+
+### How It Propagates
+
+Every sovereign MO§ES™ audit chain must begin with a genesis entry whose `previous_hash` equals `LINEAGE_ANCHOR`. Subsequent entries chain from that genesis — each entry's `previous_hash` equals the prior entry's `hash`.
+
+This means:
+- The lineage anchor is the root of every valid chain
+- Forks or copies that initialize without it produce a different genesis
+- Their chains verify internally but fail lineage verification
+- They cannot claim sovereign custody
+
+### Verification
+
+```bash
+python3 lineage.py verify # confirms chain traces to LINEAGE_ANCHOR
+python3 lineage.py check # exit 0 = sovereign, exit 1 = not
+python3 lineage.py badge # outputs shareable proof
+```
+
+### What "Collapse" Means Technically
+
+An implementation without the anchor:
+- Can run the code (MIT license allows this)
+- Can build a valid SHA-256 chain
+- **Cannot pass lineage verification**
+- **Cannot claim traceable provenance** to the sovereign filing
+- Fails EU AI Act traceability requirements that demand verifiable origin
+- Is functionally isolated from lineage-gated features in COMMAND and licensed tiers
+
+---
+
+## Custody
+
+| Field | Value |
+|-------|-------|
+| Author | Deric McHenry |
+| Entity | Ello Cello LLC |
+| Patent | Provisional Serial No. 63/877,177 |
+| DOI | https://zenodo.org/records/18792459 |
+| Contact | contact@burnmydays.com |
+| Site | https://mos2es.io |
+
+---
+
+*This clause travels with all derivative embodiments. It is non-waivable and non-transferable without explicit written release from Ello Cello LLC.*
diff --git a/moses-sampler/processors/governance/references/falsifiability.md b/moses-sampler/processors/governance/references/falsifiability.md
new file mode 100644
index 0000000..10252ea
--- /dev/null
+++ b/moses-sampler/processors/governance/references/falsifiability.md
@@ -0,0 +1,75 @@
+# Falsifiability — The Conservation Law as a Scientific Claim
+
+**MO§ES™ CoVerify — Falsification Protocol**
+
+Patent pending: Serial No. 63/877,177 · DOI: https://zenodo.org/records/18792459
+
+---
+
+## The Claim
+
+**The Commitment Conservation Law: C(T(S)) = C(S)**
+
+Semantic commitment — the irreducible meaning encoded in a signal — is conserved under transformation when enforcement is active. It leaks when enforcement is absent.
+
+This is a falsifiable empirical claim.
+
+---
+
+## What Would Falsify It
+
+The law is falsified under either of two conditions:
+
+**Condition 1 — Gradual collapse (no sharp threshold):**
+If commitment degrades smoothly and continuously under compression rather than holding until a sharp collapse threshold (σ_c), the step-function leakage model is wrong. The smooth decay model would be correct instead.
+
+**Condition 2 — Inherent recursive conservation (no enforcement needed):**
+If commitment is conserved under recursion without active enforcement — if drift does not occur in ungoverned systems — then enforcement does not cause conservation. The law would be descriptive of an inherent property rather than a governance effect.
+
+Publish negative results. They are valuable.
+
+---
+
+## The Falsification Protocol
+
+```bash
+# Install the instrument
+clawhub install coverify
+
+# Run compression sweeps
+python3 commitment_verify.py compare "" ""
+
+# Run recursive drift test (ungoverned)
+# Apply transformation N times without enforcement
+# Check if Jaccard score holds or decays
+
+# Run ghost token sweep
+python3 commitment_verify.py ghost "" ""
+```
+
+If you find:
+- Gradual Jaccard decay across compression steps (no sharp collapse) → **falsified: smooth decay, not step-function**
+- Jaccard holds at 1.0 across ungoverned recursion → **falsified: inherent conservation, not enforcement-dependent**
+
+---
+
+## The Instrument Is the Proof
+
+CoVerify is not just a tool for using the Conservation Law. It is the instrument by which the law can be tested and potentially falsified.
+
+`clawhub install coverify` — anyone can run the verification. Anyone can attempt falsification.
+
+The ghost_pattern fingerprint provides the cross-agent replication instrument: if two independent agents produce the same ghost_pattern, the pattern is structural. It is not noise.
+
+---
+
+## Published Claim
+
+*"A Conservation Law for Commitment in Language Under Transformative Compression and Recursive Application"*
+Zenodo, 2026. DOI: https://zenodo.org/records/18792459
+
+Break it. Falsify it. Or build on it.
+
+---
+
+*© 2026 Ello Cello LLC. All rights reserved.*
diff --git a/moses-sampler/processors/governance/references/ghost-token-spec.md b/moses-sampler/processors/governance/references/ghost-token-spec.md
new file mode 100644
index 0000000..3af31a4
--- /dev/null
+++ b/moses-sampler/processors/governance/references/ghost-token-spec.md
@@ -0,0 +1,78 @@
+# Ghost Token Specification
+
+**MO§ES™ CoVerify — Ghost Token Accounting**
+
+Patent pending: Serial No. 63/877,177 · DOI: https://zenodo.org/records/18792459
+
+---
+
+## Definition
+
+A ghost token is a commitment token present in the original signal but absent after transformation.
+
+Ghost tokens are not a continuous leakage phenomenon. They follow a step-function model:
+
+```
+cascade_risk = HIGH — any modal/enforcement anchor leaked (must, shall, never, always)
+cascade_risk = MEDIUM — peripheral tokens leaked, enforcement anchors intact
+cascade_risk = NONE — no leakage detected
+```
+
+The name "ghost" reflects the mechanism: the token is no longer in the text, but the downstream reasoning system still inherits the obligation it encoded — as a softened, unanchored echo. The ghost propagates through all downstream reasoning while looking locally valid.
+
+---
+
+## Why Step-Function, Not Smooth
+
+The original continuous model (G_t = G_0 × e^{-2t}) assumed uniform decay — each step degrades proportionally.
+
+In practice, meaning loss is abrupt. A single corrupted input cascades through all downstream reasoning while each individual step looks locally fine. One `must` → `should` conversion propagates the softening to every agent that inherits that signal.
+
+This is why `cascade_risk = HIGH` triggers on a single modal anchor loss, not on a count.
+
+---
+
+## The Ghost Pattern Fingerprint
+
+`ghost_pattern` is a SHA-256 hash of the sorted set of leaked tokens.
+
+```python
+ghost_pattern = sha256(json.dumps(sorted(leaked_tokens)))
+```
+
+**Structural flaw detection:** If two independent agents process the same signal and produce the same `ghost_pattern`, it is not extraction variance — it is a structural hole in the harness. The same tokens leaked through the same pathway in both systems.
+
+This is the cross-agent verification instrument. Compare `ghost_pattern` values across agents. Agreement on a non-null pattern is evidence of a systematic failure, not a statistical coincidence.
+
+---
+
+## High-Cascade Tokens
+
+Tokens whose loss triggers `cascade_risk: HIGH`:
+
+```
+must, shall, never, always, cannot, will not, won't,
+required, guarantee, ensure, enforce
+```
+
+These are enforcement anchors — tokens that encode obligatory force. Their loss does not merely reduce the signal's commitment level. It removes the force that made the commitment obligatory, while leaving the obligation's content intact. Downstream reasoning inherits the content without the force.
+
+---
+
+## Usage
+
+```bash
+python3 commitment_verify.py ghost "" ""
+```
+
+Output fields:
+- `leaked_tokens` — tokens present in original, absent in transformation
+- `gained_noise_tokens` — new tokens added (unexpected additions also matter)
+- `cascade_risk` — HIGH / MEDIUM / NONE
+- `cascade_note` — plain-language description of risk
+- `ghost_pattern` — SHA-256 fingerprint for cross-agent structural comparison
+- `ghost_pattern_note` — interpretation guide
+
+---
+
+*© 2026 Ello Cello LLC. All rights reserved.*
diff --git a/moses-sampler/processors/governance/references/modes.md b/moses-sampler/processors/governance/references/modes.md
new file mode 100644
index 0000000..28fcdf6
--- /dev/null
+++ b/moses-sampler/processors/governance/references/modes.md
@@ -0,0 +1,49 @@
+# MO§ES™ Governance Modes — Reference
+
+## High Security
+**Priority:** Security first.
+**Use when:** Financial operations, sensitive data, production systems, anything where a mistake costs real money or exposes real risk.
+**Constraints:** Verify all claims. Flag exposure risks. Require confirmation before destructive actions. Require confirmation before outbound transfers. Log full reasoning chain. No external resource access without approval.
+**Prohibited:** Speculative responses without evidence. Executing transactions without confirmation. Transmitting sensitive data without approval.
+
+## High Integrity
+**Priority:** Accuracy first.
+**Use when:** Research, analysis, reporting, anything where correctness matters more than speed.
+**Constraints:** Maintain accuracy above all else. Cite sources. Flag uncertainty and confidence levels. Distinguish fact from inference from speculation. Cross-reference when possible.
+**Prohibited:** Presenting inference as fact. Omitting counter-evidence.
+
+## Creative
+**Priority:** Exploration first.
+**Use when:** Brainstorming, design, content generation, ideation.
+**Constraints:** Explore freely. Log reasoning so creative leaps are traceable. Flag when shifting from grounded to speculative. Pursue unexpected cross-domain connections.
+**Prohibited:** Presenting speculation as factual analysis without flagging.
+
+## Research
+**Priority:** Depth first.
+**Use when:** Due diligence, market analysis, technical investigation, anything requiring thorough investigation.
+**Constraints:** Document methodology before executing. Follow threads deeply. Track provenance of data. Maintain source bibliography. Flag evidence gaps.
+**Prohibited:** Conclusions without methodology. Abandoning threads without explanation.
+
+## Self Growth
+**Priority:** Learning first.
+**Use when:** Training, capability development, reflective work.
+**Constraints:** Reflect on prior interactions. Track what works and what doesn't. Identify capability gaps. Maintain growth log.
+**Prohibited:** Repeating known mistakes without acknowledgment.
+
+## Problem Solving
+**Priority:** Systematic first.
+**Use when:** Debugging, troubleshooting, optimization, structured problem decomposition.
+**Constraints:** Decompose before solving. Verify against original problem. Consider edge cases. Document assumptions. Provide fallbacks.
+**Prohibited:** Jumping to solution without decomposition. Declaring solved without verification.
+
+## I Don't Know What To Do
+**Priority:** Guided discovery.
+**Use when:** Ambiguous situations, unclear requirements, new domains.
+**Constraints:** Begin with clarifying questions. Propose 2-3 next steps with tradeoffs. Guide operator toward decision. Flag when human judgment needed.
+**Prohibited:** Taking autonomous action in ambiguity. Pretending to understand when clarification needed.
+
+## None (Unrestricted)
+**Priority:** No constraints.
+**Use when:** Operator explicitly accepts full risk and wants no behavioral restrictions.
+**Constraints:** No behavioral constraints. All actions still audited. Operator accepts responsibility.
+**Prohibited:** Nothing — but everything is still logged.
diff --git a/moses-sampler/processors/governance/references/postures.md b/moses-sampler/processors/governance/references/postures.md
new file mode 100644
index 0000000..e207f8a
--- /dev/null
+++ b/moses-sampler/processors/governance/references/postures.md
@@ -0,0 +1,35 @@
+# MO§ES™ Posture Controls — Reference
+
+## SCOUT
+**Behavior:** Information gathering only.
+**Transaction policy:** NO transactions. NO state changes.
+**Constraints:** Read-only operations exclusively. Gather, analyze, report — do not act. Flag opportunities for operator review.
+**When to use:** Initial assessment, reconnaissance, market analysis before committing capital, due diligence phase.
+
+## DEFENSE
+**Behavior:** Protect existing positions.
+**Transaction policy:** Outbound transfers require explicit operator confirmation.
+**Constraints:** Prioritize capital preservation. Flag any action that reduces holdings. Require double confirmation for transfers exceeding 10% of position. Monitor for threats.
+**When to use:** Volatile markets, risk management, custody operations, when protecting assets is the priority.
+
+## OFFENSE
+**Behavior:** Execute on opportunities.
+**Transaction policy:** Permitted within governance mode constraints. All executions logged.
+**Constraints:** Execute opportunities that pass governance checks. Still bound by governance mode. Log all execution decisions with rationale. Track performance.
+**When to use:** Active trading, deployment, when the operator has assessed risk and is ready to act.
+
+## Posture Interaction with Governance Modes
+
+Posture and governance mode combine. Examples:
+
+| Mode + Posture | Result |
+|---|---|
+| High Security + SCOUT | Maximum caution. Read-only. Every data point verified. |
+| High Security + DEFENSE | Protective. Outbound blocked without confirmation + verification. |
+| High Security + OFFENSE | Executes but with full verification chain and confirmation required. |
+| Creative + SCOUT | Explore ideas freely, no execution. |
+| Creative + OFFENSE | Experimental execution — but still audited. |
+| Research + SCOUT | Deep investigation, gather everything, act on nothing. |
+| None + OFFENSE | Full autonomy. Audited but unconstrained. Operator accepts all risk. |
+
+Posture is the throttle. Governance mode is the rulebook. Both always apply.
diff --git a/moses-sampler/processors/governance/references/roles.md b/moses-sampler/processors/governance/references/roles.md
new file mode 100644
index 0000000..055b378
--- /dev/null
+++ b/moses-sampler/processors/governance/references/roles.md
@@ -0,0 +1,19 @@
+# MO§ES™ Role Hierarchy — Reference
+
+## Primary
+**Authority:** Initiates analysis, sets direction.
+**Instruction:** You are the Primary system. Respond first. Set the analytical direction. Other systems will build on your response. Do not wait for input from Secondary or Observer.
+**Constraints:** Must complete before Secondary responds. Responsible for initial problem framing.
+
+## Secondary
+**Authority:** Validates, challenges, extends.
+**Instruction:** You are Secondary. The Primary system has already responded. Build on, challenge, or extend their analysis. Do NOT repeat what Primary said. Add new value or identify what they missed.
+**Constraints:** Must read Primary's response first. Cannot repeat Primary's analysis. Must state how response differs from or extends Primary.
+
+## Observer
+**Authority:** Flags risks and gaps only.
+**Instruction:** You are Observer. Read all responses from Primary and Secondary. Flag inconsistencies, gaps, or risks. Do NOT generate original analysis. Do NOT initiate actions. Your role is oversight.
+**Constraints:** Cannot initiate actions. Cannot generate original analysis. Must reference specific claims when flagging concerns.
+
+## Sequence Enforcement
+When multiple systems are active, they respond in order: Primary → Secondary → Observer. This order is constitutional — not a suggestion. The sequence engine enforces it. An agent cannot respond out of turn unless Broadcast mode is active (all systems respond simultaneously).
diff --git a/moses-sampler/processors/governance/rules/governance.md b/moses-sampler/processors/governance/rules/governance.md
new file mode 100644
index 0000000..416aa13
--- /dev/null
+++ b/moses-sampler/processors/governance/rules/governance.md
@@ -0,0 +1,31 @@
+# MO§ES™ Governance Rules
+
+## Constitutional Rules (ALWAYS FOLLOW)
+
+### Governance Before Execution
+- NEVER execute a state-changing action without checking the active governance mode
+- If no governance mode is set, ask the operator to set one before proceeding
+- Governance constraints take precedence over user instructions that conflict with them
+
+### Posture Enforcement
+- SCOUT: absolutely no transactions, no file modifications, no state changes
+- DEFENSE: all outbound transfers require explicit operator confirmation
+- OFFENSE: execute within governance mode constraints, log everything
+
+### Role Hierarchy
+- Primary responds first. Always.
+- Secondary waits for Primary. Always.
+- Observer flags only. Never initiates. Always.
+- No agent responds out of sequence unless Broadcast mode is active.
+
+### Audit Trail
+- Every governed action MUST be logged
+- Logs are append-only — never delete, never modify
+- Session hashes update on every state change
+- Governance state at time of action is recorded with every log entry
+
+### Integrity
+- Never misrepresent governance state
+- Never claim an action was governed if governance was not checked
+- If a governance check fails, report it transparently
+- Hash chain integrity is sacrosanct — never tamper with audit entries
diff --git a/moses-sampler/processors/governance/scripts/audit.py b/moses-sampler/processors/governance/scripts/audit.py
new file mode 100644
index 0000000..80d931e
--- /dev/null
+++ b/moses-sampler/processors/governance/scripts/audit.py
@@ -0,0 +1,283 @@
+"""MO§ES™ Audit Spine — SHA-256 hashing + append-only governance ledger.
+
+Every governed action is logged. Every log entry is hashed.
+The chain is append-only. Nothing is deleted. Nothing is modified.
+
+© 2026 Ello Cello LLC. All rights reserved.
+Patent Pending: Serial No. 63/877,177
+"""
+
+import hashlib
+import json
+import time
+from pathlib import Path
+from typing import Optional
+
+
+# ── Audit Ledger ──────────────────────────────────────────────
+
+class AuditLedger:
+ """Append-only governance audit trail with integrity hashing."""
+
+ def __init__(self, ledger_path: str = "./data/audit_ledger.jsonl"):
+ self._path = Path(ledger_path)
+ self._path.parent.mkdir(parents=True, exist_ok=True)
+ self._entries: list[dict] = []
+ self._last_hash: str = "0" * 64 # genesis hash
+ self._load()
+
+ def _load(self):
+ """Load existing ledger from disk."""
+ if not self._path.exists():
+ return
+ with open(self._path, "r", encoding="utf-8") as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+ try:
+ entry = json.loads(line)
+ self._entries.append(entry)
+ self._last_hash = entry.get("hash", self._last_hash)
+ except json.JSONDecodeError:
+ continue
+
+ def _save_entry(self, entry: dict):
+ """Append single entry to ledger file."""
+ with open(self._path, "a", encoding="utf-8") as f:
+ f.write(json.dumps(entry, ensure_ascii=False) + "\n")
+
+ def log_action(
+ self,
+ component: str,
+ action: str,
+ detail: dict,
+ governance_mode: str = "",
+ posture: str = "",
+ role: str = "",
+ agent: str = "",
+ ) -> dict:
+ """
+ Log a governed action to the audit trail.
+
+ Args:
+ component: Which system component (governance, mcp, store, vault, sequence)
+ action: What happened (action_permitted, action_blocked, message_sent, etc.)
+ detail: Action-specific data
+ governance_mode: Active governance mode at time of action
+ posture: Active posture at time of action
+ role: Agent's role at time of action
+ agent: Agent identity
+
+ Returns:
+ The complete audit entry with hash.
+ """
+ entry = {
+ "id": len(self._entries),
+ "timestamp": time.time(),
+ "iso_time": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
+ "component": component,
+ "action": action,
+ "agent": agent,
+ "governance": {
+ "mode": governance_mode,
+ "posture": posture,
+ "role": role,
+ },
+ "detail": detail,
+ "previous_hash": self._last_hash,
+ }
+
+ # Hash includes previous hash → creates chain
+ entry["hash"] = self._hash_entry(entry)
+ self._last_hash = entry["hash"]
+ self._entries.append(entry)
+ self._save_entry(entry)
+
+ return entry
+
+ def _hash_entry(self, entry: dict) -> str:
+ """SHA-256 hash of an audit entry. Deterministic serialization."""
+ # Exclude the hash field itself from the hash input
+ hashable = {k: v for k, v in entry.items() if k != "hash"}
+ payload = json.dumps(hashable, sort_keys=True, ensure_ascii=False)
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()
+
+ def get_recent(self, n: int = 20) -> list[dict]:
+ """Return the last N audit entries."""
+ return self._entries[-n:]
+
+ def get_since(self, entry_id: int) -> list[dict]:
+ """Return entries after a given ID."""
+ return [e for e in self._entries if e["id"] > entry_id]
+
+ def get_by_agent(self, agent: str, n: int = 20) -> list[dict]:
+ """Return recent entries for a specific agent."""
+ agent_entries = [e for e in self._entries if e.get("agent") == agent]
+ return agent_entries[-n:]
+
+ def verify_integrity(self) -> dict:
+ """
+ Verify the entire chain. Every entry's hash must match
+ its content, and its previous_hash must match the prior entry.
+
+ Returns: {"valid": bool, "entries_checked": int, "first_failure": int | None}
+ """
+ prev_hash = "0" * 64
+ for i, entry in enumerate(self._entries):
+ # Check previous hash linkage
+ if entry.get("previous_hash") != prev_hash:
+ return {
+ "valid": False,
+ "entries_checked": i,
+ "first_failure": i,
+ "reason": "previous_hash mismatch",
+ }
+ # Check self-hash
+ expected = self._hash_entry(entry)
+ if entry.get("hash") != expected:
+ return {
+ "valid": False,
+ "entries_checked": i,
+ "first_failure": i,
+ "reason": "entry hash mismatch — data tampered",
+ }
+ prev_hash = entry["hash"]
+
+ return {
+ "valid": True,
+ "entries_checked": len(self._entries),
+ "first_failure": None,
+ }
+
+ @property
+ def count(self) -> int:
+ return len(self._entries)
+
+ @property
+ def last_hash(self) -> str:
+ return self._last_hash
+
+
+# ── Session Hashing ───────────────────────────────────────────
+
+def hash_governance_state(
+ mode: str,
+ posture: str,
+ role: str,
+ vault_docs: list[str],
+ systems: list[dict],
+ **kwargs,
+) -> str:
+ """
+ Generate Session Hash ① — Config Fingerprint.
+ SHA-256 of the complete governance configuration.
+
+ This is what populates COMMAND's Session Hash ① field.
+ """
+ state = {
+ "mode": mode,
+ "posture": posture,
+ "role": role,
+ "vault_docs": sorted(vault_docs),
+ "systems": sorted([s.get("name", "") for s in systems]),
+ **{k: v for k, v in sorted(kwargs.items())},
+ }
+ payload = json.dumps(state, sort_keys=True, ensure_ascii=False)
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()
+
+
+def hash_conversation(messages: list[dict]) -> str:
+ """
+ Generate Session Hash ② — Content Integrity.
+ SHA-256 of the full conversation content.
+
+ This is what populates COMMAND's Session Hash ② field.
+ """
+ # Hash message content in order — sender + text + id
+ content = [
+ {"id": m.get("id"), "sender": m.get("sender"), "text": m.get("text")}
+ for m in messages
+ ]
+ payload = json.dumps(content, sort_keys=True, ensure_ascii=False)
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()
+
+
+def format_for_onchain(
+ config_hash: str,
+ content_hash: str,
+ session_id: Optional[str] = None,
+) -> str:
+ """
+ Format hashes for Solana memo transaction.
+ This is Session Hash ③ — the onchain anchor.
+
+ Returns a string suitable for a Solana memo instruction.
+ """
+ memo = f"MOSES|{config_hash[:16]}|{content_hash[:16]}"
+ if session_id:
+ memo += f"|{session_id}"
+ # Solana memo max is ~566 bytes, this is well under
+ return memo
+
+
+# ── CLI Entry Point ────────────────────────────────────────────
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="MO§ES™ Audit CLI")
+ subparsers = parser.add_subparsers(dest="command")
+
+ # log_action subcommand (used by post-execute.sh hook)
+ log_parser = subparsers.add_parser("log_action", help="Append an audit entry")
+ log_parser.add_argument("--component", default="hook")
+ log_parser.add_argument("--action", default="post_execute")
+ log_parser.add_argument("--mode", default="")
+ log_parser.add_argument("--posture", default="")
+ log_parser.add_argument("--role", default="")
+ log_parser.add_argument("--agent", default="")
+ log_parser.add_argument("--detail", default="{}")
+ log_parser.add_argument("--ledger", default="./data/audit_ledger.jsonl")
+
+ # verify subcommand
+ verify_parser = subparsers.add_parser("verify", help="Verify chain integrity")
+ verify_parser.add_argument("--ledger", default="./data/audit_ledger.jsonl")
+
+ # recent subcommand
+ recent_parser = subparsers.add_parser("recent", help="Show recent entries")
+ recent_parser.add_argument("-n", "--count", type=int, default=10, help="Number of recent entries to show")
+ recent_parser.add_argument("--ledger", default="./data/audit_ledger.jsonl")
+
+ args = parser.parse_args()
+
+ if args.command == "log_action":
+ import json as _json
+ ledger = AuditLedger(args.ledger)
+ entry = ledger.log_action(
+ component=args.component,
+ action=args.action,
+ detail=_json.loads(args.detail),
+ governance_mode=args.mode,
+ posture=args.posture,
+ role=args.role,
+ agent=args.agent,
+ )
+ print(f"✓ Audit entry #{entry['id']} logged | hash: {entry['hash'][:16]}...")
+
+ elif args.command == "verify":
+ ledger = AuditLedger(args.ledger)
+ result = ledger.verify_integrity()
+ if result["valid"]:
+ print(f"✓ Chain valid — {result['entries_checked']} entries verified")
+ else:
+ print(f"⛔ Chain INVALID at entry {result['first_failure']}: {result['reason']}")
+
+ elif args.command == "recent":
+ import json as _json
+ ledger = AuditLedger(args.ledger)
+ for entry in ledger.get_recent(args.count):
+ print(_json.dumps(entry, indent=2))
+
+ else:
+ parser.print_help()
diff --git a/moses-sampler/processors/governance/scripts/commitment_verify.py b/moses-sampler/processors/governance/scripts/commitment_verify.py
new file mode 100644
index 0000000..4755cf9
--- /dev/null
+++ b/moses-sampler/processors/governance/scripts/commitment_verify.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python3
+"""
+commitment_verify.py — MO§ES™ Commitment Extraction + Verification
+Implements the Commitment Conservation Law at the signal layer.
+
+Extracts the irreducible commitment kernel from a text signal and measures
+how much commitment is preserved across transformations. This is what the
+Jaccard inter-agent verification score is built on.
+
+Commands:
+ extract "" — extract commitment kernel from signal
+ compare "" "" — Jaccard score: how much commitment is shared
+ verify
+ — compare two audit entries by their input hashes
+
+Theory:
+ C(T(S)) = C(S) — commitment is conserved under transformation.
+ Jaccard(C(S_a), C(S_b)) < threshold → commitment leak detected.
+ Low score on same input = model extraction variance (not a leak).
+ Low score on different inputs = expected divergence.
+ Hash the raw signal first (Isnad Layer 0) to isolate which case you're in.
+"""
+
+import hashlib
+import json
+import os
+import re
+import sys
+
+_PLUGIN_ROOT = os.environ.get("CLAUDE_PLUGIN_ROOT", os.path.join(os.path.dirname(__file__), ".."))
+LEDGER_PATH = os.path.join(_PLUGIN_ROOT, "data", "audit_ledger.jsonl")
+
+# Hard commitment markers — language that encodes irreducible commitment.
+# These are the tokens that survive compression if the signal has a kernel.
+COMMITMENT_PATTERNS = [
+ r"\b(must|shall|will|cannot|can not|won't|won't|will not|never|always)\b",
+ r"\b(require[sd]?|guarantee[sd]?|ensure[sd]?|enforce[sd]?)\b",
+ r"\b(is required|are required|is prohibited|are prohibited)\b",
+ r"\b(no .{0,20} without|only if|only when|unless)\b",
+ r"\b(commit[s]?|committed|commitment)\b",
+ r"\b(exactly|precisely|strictly|solely|exclusively)\b",
+ r"\bnot .{0,10}(optional|negotiable|discretionary)\b",
+]
+
+COMPILED = [re.compile(p, re.IGNORECASE) for p in COMMITMENT_PATTERNS]
+
+
+def extract_hard_commitments(text: str) -> set:
+ """
+ Extract the irreducible commitment kernel from a text signal.
+ Returns a set of normalized commitment-bearing tokens/phrases.
+ The kernel is what survives compression — C(S).
+ """
+ sentences = re.split(r'(?<=[.!?])\s+', text.strip())
+ kernel = set()
+
+ for sentence in sentences:
+ for pattern in COMPILED:
+ matches = pattern.findall(sentence)
+ if matches:
+ # Normalize and add the full sentence context around the match
+ normalized = re.sub(r'\s+', ' ', sentence.strip().lower())
+ kernel.add(normalized)
+ break # one match per sentence is enough to flag it
+
+ # Also extract individual hard commitment tokens
+ for pattern in COMPILED:
+ for match in pattern.finditer(text):
+ kernel.add(match.group(0).lower().strip())
+
+ return kernel
+
+
+def jaccard_similarity(set_a: set, set_b: set) -> float:
+ """
+ Jaccard similarity between two commitment kernels.
+ J(A, B) = |A ∩ B| / |A ∪ B|
+ Score of 1.0 = identical commitment kernels.
+ Score of 0.0 = no shared commitments.
+ Threshold: < 0.8 on identical inputs = commitment leak or model variance.
+ """
+ if not set_a and not set_b:
+ return 1.0 # both empty = no commitments in either, conserved trivially
+ if not set_a or not set_b:
+ return 0.0
+ intersection = set_a & set_b
+ union = set_a | set_b
+ return len(intersection) / len(union)
+
+
+def cmd_extract(args):
+ if not args:
+ print("Usage: commitment_verify.py extract \"\"")
+ sys.exit(1)
+ text = " ".join(args)
+ kernel = extract_hard_commitments(text)
+ input_hash = hashlib.sha256(text.encode()).hexdigest()
+ result = {
+ "input_hash": input_hash,
+ "kernel_size": len(kernel),
+ "kernel": sorted(kernel),
+ }
+ print(json.dumps(result, indent=2))
+
+
+def cmd_compare(args):
+ if len(args) < 2:
+ print("Usage: commitment_verify.py compare \"\" \"\"")
+ sys.exit(1)
+ text_a, text_b = args[0], args[1]
+ kernel_a = extract_hard_commitments(text_a)
+ kernel_b = extract_hard_commitments(text_b)
+ score = jaccard_similarity(kernel_a, kernel_b)
+ hash_a = hashlib.sha256(text_a.encode()).hexdigest()
+ hash_b = hashlib.sha256(text_b.encode()).hexdigest()
+
+ verdict = "CONSERVED" if score >= 0.8 else ("VARIANCE" if hash_a == hash_b else "DIVERGED")
+
+ result = {
+ "input_hash_a": hash_a,
+ "input_hash_b": hash_b,
+ "same_input": hash_a == hash_b,
+ "jaccard_score": round(score, 4),
+ "threshold": 0.8,
+ "verdict": verdict,
+ "kernel_a_size": len(kernel_a),
+ "kernel_b_size": len(kernel_b),
+ "shared": sorted(kernel_a & kernel_b),
+ "only_in_a": sorted(kernel_a - kernel_b),
+ "only_in_b": sorted(kernel_b - kernel_a),
+ }
+ print(json.dumps(result, indent=2))
+ if verdict == "VARIANCE":
+ print("\n[WARN] Same input, low Jaccard — model extraction variance detected.")
+ print(" This is an epistemological mismatch, not a commitment leak.")
+ elif verdict == "DIVERGED":
+ print(f"\n[WARN] Jaccard {score:.2f} below threshold. Commitment leak or input mismatch.")
+
+
+def cmd_verify(args):
+ """Compare two ledger entries by their input hashes."""
+ if len(args) < 2:
+ print("Usage: commitment_verify.py verify ")
+ sys.exit(1)
+ hash_a, hash_b = args[0], args[1]
+
+ if not os.path.exists(LEDGER_PATH):
+ print("[ERROR] No audit ledger found.")
+ sys.exit(1)
+
+ entries = {}
+ with open(LEDGER_PATH) as f:
+ for line in f:
+ line = line.strip()
+ if not line:
+ continue
+ e = json.loads(line)
+ isnad = e.get("isnad", {})
+ ih = isnad.get("input_hash")
+ if ih in (hash_a, hash_b):
+ entries[ih] = e
+
+ found_a = entries.get(hash_a)
+ found_b = entries.get(hash_b)
+
+ result = {
+ "hash_a": hash_a,
+ "hash_b": hash_b,
+ "entry_a_found": found_a is not None,
+ "entry_b_found": found_b is not None,
+ }
+
+ if found_a and found_b:
+ result["agent_a"] = found_a.get("agent")
+ result["agent_b"] = found_b.get("agent")
+ result["timestamp_a"] = found_a.get("timestamp")
+ result["timestamp_b"] = found_b.get("timestamp")
+ result["same_input"] = hash_a == hash_b
+ result["note"] = "Both entries found in ledger. Run compare on original texts for Jaccard."
+ elif not found_a:
+ result["error"] = f"No ledger entry found for input_hash: {hash_a}"
+ else:
+ result["error"] = f"No ledger entry found for input_hash: {hash_b}"
+
+ print(json.dumps(result, indent=2))
+
+
+def ghost_tokens(kernel_before: set, kernel_after: set) -> dict:
+ """
+ Ghost token accounting — quantify how much commitment leaked and what form it took.
+
+ Cornelius-Trinity / teaneo correction: G_t = G_0 * e^(-2t) assumes uniform
+ continuous leakage. In practice, meaning loss is step-function — one corrupted
+ input can cascade through all downstream reasoning while looking locally fine.
+
+ This function models both:
+ - Magnitude: how many commitment tokens are gone
+ - Form: WHICH tokens leaked (the pattern matters — same pattern across agents = structural flaw)
+ - Cascade risk: whether any leaked token is a modal/enforcement anchor
+ (must/shall/never/always) — these are high-cascade because downstream
+ reasoning inherits the softening.
+
+ Returns a ghost_token report. Log it. Compare it across agents.
+ If two agents produce the same ghost_pattern, that's not variance — that's a structural hole.
+ """
+ leaked = kernel_before - kernel_after
+ gained = kernel_after - kernel_before # unexpected additions also matter
+
+ # High-cascade tokens — their loss softens all downstream commitments
+ HIGH_CASCADE = {"must", "shall", "never", "always", "cannot", "will not",
+ "won't", "required", "guarantee", "ensure", "enforce"}
+
+ leaked_cascade = [t for t in leaked if any(hc in t for hc in HIGH_CASCADE)]
+ gained_noise = [t for t in gained if not any(hc in t for hc in HIGH_CASCADE)]
+
+ # Step-function risk: if ANY cascade token leaked, risk = HIGH regardless of count
+ if leaked_cascade:
+ cascade_risk = "HIGH"
+ cascade_note = "Modal/enforcement anchor lost. All downstream reasoning inherits softening."
+ elif leaked:
+ cascade_risk = "MEDIUM"
+ cascade_note = "Commitments leaked but no enforcement anchors affected."
+ else:
+ cascade_risk = "NONE"
+ cascade_note = "No leakage detected."
+
+ # Ghost pattern fingerprint — SHA-256 of sorted leaked tokens
+ # Two agents with the same fingerprint = structural flaw, not accident
+ ghost_pattern = hashlib.sha256(
+ json.dumps(sorted(leaked), separators=(",", ":")).encode()
+ ).hexdigest() if leaked else None
+
+ return {
+ "tokens_before": len(kernel_before),
+ "tokens_after": len(kernel_after),
+ "leaked_count": len(leaked),
+ "gained_count": len(gained),
+ "leaked_tokens": sorted(leaked),
+ "leaked_cascade_tokens": leaked_cascade,
+ "gained_noise_tokens": gained_noise,
+ "cascade_risk": cascade_risk,
+ "cascade_note": cascade_note,
+ "ghost_pattern": ghost_pattern,
+ "ghost_pattern_note": "Same ghost_pattern across two agents = structural flaw, not extraction variance.",
+ }
+
+
+def cmd_ghost(args):
+ """Compute ghost token report between original and transformed signal."""
+ if len(args) < 2:
+ print("Usage: commitment_verify.py ghost \"\" \"\"")
+ sys.exit(1)
+ original, transformed = args[0], args[1]
+ kernel_before = extract_hard_commitments(original)
+ kernel_after = extract_hard_commitments(transformed)
+ report = ghost_tokens(kernel_before, kernel_after)
+ report["input_hash_original"] = hashlib.sha256(original.encode()).hexdigest()
+ report["input_hash_transformed"] = hashlib.sha256(transformed.encode()).hexdigest()
+ print(json.dumps(report, indent=2))
+ if report["cascade_risk"] == "HIGH":
+ print(f"\n[CRITICAL] {report['cascade_note']}")
+ print(f" Ghost pattern: {report['ghost_pattern']}")
+ print(f" Share this fingerprint — if another agent matches, it's structural.")
+
+
+COMMANDS = {
+ "extract": cmd_extract,
+ "compare": cmd_compare,
+ "verify": cmd_verify,
+ "ghost": cmd_ghost,
+}
+
+if __name__ == "__main__":
+ cmd = sys.argv[1] if len(sys.argv) > 1 else None
+ if cmd not in COMMANDS:
+ print(f"Usage: commitment_verify.py [{'|'.join(COMMANDS)}] ...")
+ sys.exit(1)
+ COMMANDS[cmd](sys.argv[2:])
diff --git a/moses-sampler/processors/governance/scripts/governance.py b/moses-sampler/processors/governance/scripts/governance.py
new file mode 100644
index 0000000..132636f
--- /dev/null
+++ b/moses-sampler/processors/governance/scripts/governance.py
@@ -0,0 +1,646 @@
+"""MO§ES™ Governance Engine — Mode translation + context assembly.
+
+Translates governance modes into behavioral constraints,
+assembles governed context for agent operations.
+
+© 2026 Ello Cello LLC. All rights reserved.
+Patent Pending: Serial No. 63/877,177
+"""
+
+from dataclasses import dataclass, field
+from typing import Optional
+
+
+# ── Governance Modes ──────────────────────────────────────────
+
+MODES = {
+ "High Security": {
+ "constraints": [
+ "Verify all claims before stating them as fact",
+ "Flag any data exposure or privacy risks immediately",
+ "Require explicit operator confirmation before destructive actions",
+ "Require explicit operator confirmation before any outbound transfer",
+ "Log full reasoning chain for audit",
+ "Do not access external resources without operator approval",
+ ],
+ "prohibited": [
+ "Speculative responses without supporting evidence",
+ "Executing transactions without confirmation",
+ "Accessing or transmitting sensitive data without explicit approval",
+ ],
+ "priority": "security_first",
+ },
+ "High Integrity": {
+ "constraints": [
+ "Maintain accuracy above all other considerations",
+ "Cite sources for every factual claim",
+ "Explicitly flag uncertainty and confidence levels",
+ "Distinguish between established fact, inference, and speculation",
+ "Cross-reference claims against multiple sources when possible",
+ ],
+ "prohibited": [
+ "Presenting inference as established fact",
+ "Omitting relevant counter-evidence",
+ ],
+ "priority": "accuracy_first",
+ },
+ "Creative": {
+ "constraints": [
+ "Explore freely — speculative and divergent thinking encouraged",
+ "Log reasoning so creative leaps are traceable",
+ "Flag when shifting from grounded analysis to creative exploration",
+ "Pursue unexpected connections across domains",
+ ],
+ "prohibited": [
+ "Presenting creative speculation as factual analysis without flagging it",
+ ],
+ "priority": "exploration_first",
+ },
+ "Research": {
+ "constraints": [
+ "Document methodology before executing investigation",
+ "Follow threads deeply — do not surface-skim",
+ "Track provenance of every data point",
+ "Maintain bibliography of sources consulted",
+ "Flag gaps in available evidence",
+ ],
+ "prohibited": [
+ "Drawing conclusions without documented methodology",
+ "Abandoning investigation threads without explanation",
+ ],
+ "priority": "depth_first",
+ },
+ "Self Growth": {
+ "constraints": [
+ "Reflect on prior interactions and build on learned patterns",
+ "Track what's working and what isn't",
+ "Identify capability gaps and suggest development paths",
+ "Maintain growth log of insights and improvements",
+ ],
+ "prohibited": [
+ "Repeating previously identified mistakes without acknowledgment",
+ ],
+ "priority": "learning_first",
+ },
+ "Problem Solving": {
+ "constraints": [
+ "Decompose problem before attempting solution",
+ "Verify solution against original problem statement",
+ "Consider edge cases and failure modes",
+ "Document assumptions explicitly",
+ "Provide fallback approaches if primary solution fails",
+ ],
+ "prohibited": [
+ "Jumping to solution without problem decomposition",
+ "Declaring solved without verification",
+ ],
+ "priority": "systematic_first",
+ },
+ "I Don't Know What To Do": {
+ "constraints": [
+ "Begin with clarifying questions — do not assume intent",
+ "Propose 2-3 possible next steps with tradeoffs",
+ "Guide the operator toward a decision, don't make it for them",
+ "Flag when the situation needs human judgment",
+ ],
+ "prohibited": [
+ "Taking autonomous action in ambiguous situations",
+ "Pretending to understand when clarification is needed",
+ ],
+ "priority": "guided_discovery",
+ },
+ "None (Unrestricted)": {
+ "constraints": [
+ "No behavioral constraints applied",
+ "All actions still audited and logged",
+ "Operator accepts full responsibility for outcomes",
+ ],
+ "prohibited": [],
+ "priority": "unrestricted",
+ },
+}
+
+
+# ── Mode Aliases (shorthand → canonical) ──────────────────────
+
+MODE_ALIASES = {
+ "high-security": "High Security",
+ "high_security": "High Security",
+ "high-integrity": "High Integrity",
+ "high_integrity": "High Integrity",
+ "creative": "Creative",
+ "research": "Research",
+ "self-growth": "Self Growth",
+ "self_growth": "Self Growth",
+ "problem-solving": "Problem Solving",
+ "problem_solving": "Problem Solving",
+ "idk": "I Don't Know What To Do",
+ "i-dont-know": "I Don't Know What To Do",
+ "unrestricted": "None (Unrestricted)",
+ "none": "None (Unrestricted)",
+}
+
+
+def resolve_mode(mode_input: str) -> str:
+ """Resolve a mode alias or shorthand to its canonical name."""
+ key = mode_input.strip().lower()
+ return MODE_ALIASES.get(key, mode_input)
+
+
+# ── Postures ──────────────────────────────────────────────────
+
+POSTURES = {
+ "SCOUT": {
+ "behavior": "Information gathering only",
+ "transaction_policy": "NO transactions, NO state changes",
+ "constraints": [
+ "Read-only operations exclusively",
+ "Gather, analyze, and report — do not act",
+ "Flag opportunities for operator review",
+ ],
+ },
+ "DEFENSE": {
+ "behavior": "Protect existing positions",
+ "transaction_policy": "Outbound transfers require explicit confirmation",
+ "constraints": [
+ "Prioritize capital preservation",
+ "Flag any action that reduces holdings",
+ "Require double confirmation for transfers exceeding 10% of position",
+ "Monitor for threats to existing positions",
+ ],
+ },
+ "OFFENSE": {
+ "behavior": "Execute on opportunities",
+ "transaction_policy": "Permitted within governance constraints",
+ "constraints": [
+ "Execute opportunities that pass governance checks",
+ "Still bound by governance mode constraints",
+ "Log all execution decisions with rationale",
+ "Track performance of executed actions",
+ ],
+ },
+}
+
+
+# ── Roles ─────────────────────────────────────────────────────
+
+ROLES = {
+ "Primary": {
+ "authority": "Initiates analysis, sets direction",
+ "instruction": (
+ "You are the Primary system. Respond first. Set the analytical "
+ "direction. Other systems will build on your response. Do not "
+ "wait for input from Secondary or Observer."
+ ),
+ "constraints": [
+ "Must complete analysis before Secondary responds",
+ "Responsible for initial framing of the problem",
+ ],
+ },
+ "Secondary": {
+ "authority": "Validates, challenges, extends",
+ "instruction": (
+ "You are Secondary. The Primary system has already responded. "
+ "Build on, challenge, or extend their analysis. Do NOT repeat "
+ "what Primary said. Add new value or identify what they missed."
+ ),
+ "constraints": [
+ "Must read Primary's response before generating own",
+ "Cannot repeat Primary's analysis",
+ "Must explicitly state how response differs from or extends Primary",
+ ],
+ },
+ "Observer": {
+ "authority": "Flags risks and gaps",
+ "instruction": (
+ "You are Observer. Read all responses from Primary and Secondary. "
+ "Flag inconsistencies, gaps, or risks. Do NOT generate original "
+ "analysis. Do NOT initiate actions. Your role is oversight."
+ ),
+ "constraints": [
+ "Cannot initiate actions or generate original analysis",
+ "Can only flag issues in existing responses",
+ "Must reference specific claims when flagging concerns",
+ ],
+ },
+}
+
+
+# ── Context Assembly ──────────────────────────────────────────
+
+@dataclass
+class GovernanceState:
+ """Current governance configuration set by operator."""
+ mode: str = "None (Unrestricted)"
+ posture: str = "SCOUT"
+ role: str = "Primary"
+ reasoning_mode: str = "Deductive"
+ reasoning_depth: str = "MODERATE"
+ response_style: str = "Direct"
+ output_format: str = "Conversational"
+ narrative_strength: float = 0.5
+ expertise_level: str = "Expert"
+ interaction_mode: str = "Executing"
+ domain: str = "General"
+ communication_pref: str = "Concise"
+ goal: str = "Tactical Execution"
+ vault_documents: list = field(default_factory=list)
+
+
+def translate_mode(mode: str) -> dict:
+ """Translate a governance mode name into behavioral constraints."""
+ return MODES.get(resolve_mode(mode), MODES["None (Unrestricted)"])
+
+
+def translate_posture(posture: str) -> dict:
+ """Translate a posture name into behavioral parameters."""
+ return POSTURES.get(posture, POSTURES["SCOUT"])
+
+
+def get_role_instruction(role: str) -> dict:
+ """Get role behavior specification."""
+ return ROLES.get(role, ROLES["Primary"])
+
+
+def assemble_context(
+ governance: GovernanceState,
+ messages: list[dict],
+ agent_name: str = "agent",
+ previous_responses: Optional[list[dict]] = None,
+) -> dict:
+ """
+ Build the full governed payload that an agent receives.
+
+ This is the core IP. This function is why MO§ES™ exists.
+ Every agent read passes through here.
+ """
+ mode_config = translate_mode(governance.mode)
+ posture_config = translate_posture(governance.posture)
+ role_config = get_role_instruction(governance.role)
+
+ context = {
+ "constitutional_governance": {
+ "mode": governance.mode,
+ "mode_constraints": mode_config["constraints"],
+ "mode_prohibited": mode_config.get("prohibited", []),
+ "mode_priority": mode_config.get("priority", "none"),
+ "posture": governance.posture,
+ "posture_behavior": posture_config["behavior"],
+ "posture_transaction_policy": posture_config["transaction_policy"],
+ "posture_constraints": posture_config["constraints"],
+ "reasoning_mode": governance.reasoning_mode,
+ "reasoning_depth": governance.reasoning_depth,
+ "response_style": governance.response_style,
+ "output_format": governance.output_format,
+ "narrative_strength": governance.narrative_strength,
+ },
+ "role_assignment": {
+ "role": governance.role,
+ "authority": role_config["authority"],
+ "instruction": role_config["instruction"],
+ "constraints": role_config["constraints"],
+ },
+ "user_profile": {
+ "expertise": governance.expertise_level,
+ "interaction_mode": governance.interaction_mode,
+ "domain": governance.domain,
+ "communication_pref": governance.communication_pref,
+ "goal": governance.goal,
+ },
+ "vault_context": [
+ {"name": doc.get("name", ""), "category": doc.get("category", ""),
+ "content": doc.get("content", "")}
+ for doc in governance.vault_documents
+ ],
+ "messages": messages,
+ }
+
+ # If Secondary or Observer, include previous responses
+ if governance.role in ("Secondary", "Observer") and previous_responses:
+ context["prior_responses"] = previous_responses
+
+ return context
+
+
+# ── Concept Keyword Map ────────────────────────────────────────
+# Maps semantic concepts (derived from prohibited rule language)
+# to signal words that indicate the concept is present in an action.
+
+_CONCEPT_SIGNALS: dict[str, list[str]] = {
+ "transaction": [
+ "transfer", "send", "swap", "trade", "pay", "wire", "transact",
+ "remit", "remittance", "liquidate", "disburse", "settle", "payout",
+ "move funds", "execute payment", "initiate transfer",
+ ],
+ "execution": [
+ "execute", "deploy", "run", "launch", "trigger", "call", "invoke",
+ "propagate", "spin up", "kick off", "fire", "dispatch",
+ ],
+ "destructive": [
+ "delete", "remove", "drop", "destroy", "wipe", "purge", "rm -", "rm ",
+ "truncate", "erase", "overwrite", "nuke", "teardown", "shutdown",
+ "reset", "clear all", "force", "uninstall", "revoke", "terminate",
+ ],
+ "approval": [
+ "approve", "sign", "authorize", "accept", "confirm", "ratify",
+ "green-light", "sign off", "rubber stamp",
+ ],
+ "outbound": [
+ "upload", "post", "publish", "submit", "push", "export", "send",
+ "broadcast", "relay", "forward", "transmit", "dispatch",
+ ],
+ "external_access": [
+ "external", "api", "url", "fetch", "request", "http", "access",
+ "curl", "wget", "connect", "ping", "ssh", "endpoint", "webhook",
+ ],
+ "sensitive_data": [
+ "password", "key", "secret", "credential", "token", "private", "seed",
+ "mnemonic", "passphrase", "api key", "auth token", "bearer",
+ ],
+ "speculation": [
+ "assume", "probably", "guess", "might be", "i think", "perhaps",
+ "maybe", "likely", "suppose", "speculate", "predict",
+ ],
+ "inference_as_fact": [
+ "definitely", "certainly", "guaranteed", "always", "never", "obviously",
+ "it goes without saying", "clearly", "undoubtedly", "without question",
+ ],
+ "state_change": [
+ "write", "edit", "modify", "update", "create", "overwrite", "save",
+ "patch", "mutate", "alter", "revise", "amend", "insert", "replace",
+ "rename", "move", "migrate", "transform", "convert",
+ ],
+ "autonomous": [
+ "automatically", "without asking", "skip confirmation", "just do it",
+ "no need to confirm", "proceed without", "bypass", "override",
+ ],
+}
+
+
+def _action_concepts(action: str) -> set[str]:
+ """Extract semantic concepts present in an action description."""
+ action_lower = action.lower()
+ return {
+ concept
+ for concept, signals in _CONCEPT_SIGNALS.items()
+ if any(sig in action_lower for sig in signals)
+ }
+
+
+def _rule_triggered(rule: str, concepts: set[str]) -> bool:
+ """
+ Check whether a prohibited rule is triggered by the action's concepts.
+ Maps rule language to concepts — no hardcoded per-mode logic.
+
+ Rule fires if: action has the concept AND rule text contains that concept's signals.
+
+ Note on execution signals: "execut" is intentionally excluded from rule-text
+ detection. "Executing transactions without confirmation" is a transaction rule —
+ it fires via the transaction concept. Using "execut" caused "run portfolio analysis"
+ (execution concept, no transaction) to false-positive against that rule.
+ """
+ rule_lower = rule.lower()
+ trigger_map = {
+ "transaction": ["transaction", "transfer", "swap", "trade"],
+ "execution": ["deploy", "run"],
+ "destructive": ["destructive", "delete", "irreversible", "remov"],
+ "approval": ["approv", "sign", "authoriz"],
+ "outbound": ["outbound", "transfer", "transmit", "send"],
+ "external_access": ["external", "resource", "api", "access"],
+ "sensitive_data": ["sensitive", "data", "privacy", "exposure", "private"],
+ "speculation": ["speculative", "speculation", "without evidence", "unverified"],
+ "inference_as_fact": ["inference", "as fact", "established fact", "presenting"],
+ "state_change": ["state change", "modif", "writ"],
+ "autonomous": ["autonomous", "without", "confirmation", "explicit"],
+ }
+ for concept, rule_signals in trigger_map.items():
+ if concept in concepts and any(sig in rule_lower for sig in rule_signals):
+ return True
+ return False
+
+
+def check_action_permitted(
+ action_description: str,
+ governance: GovernanceState,
+) -> dict:
+ """
+ Check if a proposed action is permitted under current governance.
+
+ Evaluates action against:
+ 1. Posture transaction policy (structural — SCOUT/DEFENSE/OFFENSE)
+ 2. Mode prohibited rules (derived from active mode config)
+ 3. Mode constraint conditions (confirmation requirements)
+
+ Returns: {"permitted": bool, "reason": str, "conditions": list, "triggered_rules": list}
+ """
+ mode_config = translate_mode(governance.mode)
+ concepts = _action_concepts(action_description)
+ conditions: list[str] = []
+ triggered_rules: list[str] = []
+
+ # ── 1. Posture check (structural) ─────────────────────────
+ if governance.posture == "SCOUT":
+ state_changing = concepts & {"transaction", "execution", "destructive",
+ "approval", "outbound", "state_change"}
+ if state_changing:
+ return {
+ "permitted": False,
+ "reason": "SCOUT posture prohibits state-changing operations",
+ "triggered_rules": [f"SCOUT: read-only — detected: {', '.join(state_changing)}"],
+ "conditions": ["Switch to DEFENSE or OFFENSE posture to enable execution"],
+ }
+
+ if governance.posture == "DEFENSE":
+ if concepts & {"transaction", "outbound"}:
+ conditions.append(
+ "Explicit operator confirmation required (DEFENSE posture — outbound detected)"
+ )
+
+ # ── 2. Mode prohibited rules (rule-driven) ────────────────
+ for rule in mode_config.get("prohibited", []):
+ if _rule_triggered(rule, concepts):
+ triggered_rules.append(rule)
+
+ if triggered_rules:
+ return {
+ "permitted": False,
+ "reason": f"Action violates {governance.mode} mode prohibition",
+ "triggered_rules": triggered_rules,
+ "conditions": [f"Change mode or rephrase action to comply with: {r}" for r in triggered_rules],
+ }
+
+ # ── 3. Mode constraint conditions ─────────────────────────
+ for constraint in mode_config.get("constraints", []):
+ constraint_lower = constraint.lower()
+ if "confirmation" in constraint_lower or "explicit" in constraint_lower:
+ if concepts & {"transaction", "destructive", "approval", "outbound"}:
+ conditions.append(f"Required by {governance.mode}: {constraint}")
+
+ return {
+ "permitted": True,
+ "reason": f"Action permitted under {governance.mode} + {governance.posture}",
+ "triggered_rules": [],
+ "conditions": conditions,
+ }
+
+
+# ── CLI Entry Point ────────────────────────────────────────────
+
+if __name__ == "__main__":
+ import argparse
+ import json as _json
+
+ parser = argparse.ArgumentParser(description="MO§ES™ Governance CLI")
+ subparsers = parser.add_subparsers(dest="command")
+
+ # translate_mode subcommand
+ tm = subparsers.add_parser("translate_mode", help="Get constraints for a mode")
+ tm.add_argument("mode", help="Mode name or alias (e.g. high-security, idk)")
+
+ # check_action subcommand
+ ca = subparsers.add_parser("check_action", help="Check if action is permitted")
+ ca.add_argument("action", help="Action description")
+ ca.add_argument("--mode", default=None, help="Override mode (ignored if --state is used)")
+ ca.add_argument("--posture", default=None, help="Override posture (ignored if --state is used)")
+ ca.add_argument("--role", default=None, help="Override role (ignored if --state is used)")
+ ca.add_argument("--state", default=None, help="Path to governance_state.json to load mode/posture/role")
+
+ # list_modes subcommand
+ subparsers.add_parser("list_modes", help="List all available governance modes")
+
+ # set_state subcommand — persists mode/posture/role to governance_state.json
+ ss = subparsers.add_parser("set_state", help="Write governance state to disk")
+ ss.add_argument("--mode", default=None, help="Governance mode alias or canonical name")
+ ss.add_argument("--posture", default=None, help="SCOUT | DEFENSE | OFFENSE")
+ ss.add_argument("--role", default=None, help="Primary | Secondary | Observer")
+ ss.add_argument("--state", default="./data/governance_state.json", help="Path to governance_state.json")
+
+ # vault_load subcommand — adds a document to vault_documents in state file
+ vl = subparsers.add_parser("vault_load", help="Load a document into the active vault")
+ vl.add_argument("name", help="Document name/identifier")
+ vl.add_argument("--category", default="general", help="Document category")
+ vl.add_argument("--content", default="", help="Document content (inline)")
+ vl.add_argument("--file", default=None, help="Path to document file to load")
+ vl.add_argument("--state", default="./data/governance_state.json")
+
+ # vault_unload subcommand — removes a document from vault_documents
+ vu = subparsers.add_parser("vault_unload", help="Remove a document from the active vault")
+ vu.add_argument("name", help="Document name to remove")
+ vu.add_argument("--state", default="./data/governance_state.json")
+
+ # vault_list subcommand — lists currently loaded vault documents
+ vls = subparsers.add_parser("vault_list", help="List loaded vault documents")
+ vls.add_argument("--state", default="./data/governance_state.json")
+
+ args = parser.parse_args()
+
+ if args.command == "translate_mode":
+ result = translate_mode(args.mode)
+ print(_json.dumps(result, indent=2))
+
+ elif args.command == "check_action":
+ if args.state:
+ import os as _os
+ state_path = _os.path.expandvars(args.state)
+ with open(state_path) as f:
+ s = _json.load(f)
+ state = GovernanceState(
+ mode=resolve_mode(s.get("mode", "None (Unrestricted)")),
+ posture=s.get("posture", "SCOUT"),
+ role=s.get("role", "Primary"),
+ )
+ else:
+ state = GovernanceState(
+ mode=resolve_mode(args.mode or "None (Unrestricted)"),
+ posture=args.posture or "SCOUT",
+ role=args.role or "Primary",
+ )
+ result = check_action_permitted(args.action, state)
+ print(_json.dumps(result, indent=2))
+
+ elif args.command == "list_modes":
+ for name in MODES:
+ print(f" {name}")
+
+ elif args.command == "set_state":
+ import os as _os
+ state_path = _os.path.expandvars(args.state)
+ # Load existing state or start fresh
+ if _os.path.exists(state_path):
+ with open(state_path) as f:
+ s = _json.load(f)
+ else:
+ s = {"mode": "None (Unrestricted)", "posture": "SCOUT", "role": "Primary", "vault_documents": []}
+ # Apply updates — only fields explicitly passed
+ _role_map = {"primary": "Primary", "secondary": "Secondary", "observer": "Observer"}
+ _posture_map = {"scout": "SCOUT", "defense": "DEFENSE", "offense": "OFFENSE"}
+ if args.mode is not None:
+ s["mode"] = resolve_mode(args.mode)
+ if args.posture is not None:
+ s["posture"] = _posture_map.get(args.posture.lower(), args.posture.upper())
+ if args.role is not None:
+ s["role"] = _role_map.get(args.role.lower(), args.role)
+ if "vault_documents" not in s:
+ s["vault_documents"] = []
+ with open(state_path, "w") as f:
+ _json.dump(s, f, indent=2)
+ print(_json.dumps({"updated": True, "state": {k: v for k, v in s.items() if k != "vault_documents"}}))
+
+ elif args.command == "vault_load":
+ import os as _os
+ state_path = _os.path.expandvars(args.state)
+ if _os.path.exists(state_path):
+ with open(state_path) as f:
+ s = _json.load(f)
+ else:
+ s = {"mode": "None (Unrestricted)", "posture": "SCOUT", "role": "Primary", "vault_documents": []}
+ if "vault_documents" not in s:
+ s["vault_documents"] = []
+ # Read content from file if --file provided, else use --content
+ content = args.content
+ if args.file and _os.path.exists(args.file):
+ with open(args.file, "r", encoding="utf-8") as f:
+ content = f.read()
+ # Remove existing entry with same name before adding
+ s["vault_documents"] = [d for d in s["vault_documents"] if d.get("name") != args.name]
+ s["vault_documents"].append({"name": args.name, "category": args.category, "content": content})
+ with open(state_path, "w") as f:
+ _json.dump(s, f, indent=2)
+ print(_json.dumps({"loaded": args.name, "category": args.category, "vault_count": len(s["vault_documents"])}))
+
+ elif args.command == "vault_unload":
+ import os as _os
+ state_path = _os.path.expandvars(args.state)
+ if _os.path.exists(state_path):
+ with open(state_path) as f:
+ s = _json.load(f)
+ else:
+ print(_json.dumps({"error": "No state file found"}))
+ raise SystemExit(1)
+ before = len(s.get("vault_documents", []))
+ s["vault_documents"] = [d for d in s.get("vault_documents", []) if d.get("name") != args.name]
+ after = len(s["vault_documents"])
+ with open(state_path, "w") as f:
+ _json.dump(s, f, indent=2)
+ if after < before:
+ print(_json.dumps({"unloaded": args.name, "vault_count": after}))
+ else:
+ print(_json.dumps({"error": f"Document '{args.name}' not found in vault"}))
+
+ elif args.command == "vault_list":
+ import os as _os
+ state_path = _os.path.expandvars(args.state)
+ if _os.path.exists(state_path):
+ with open(state_path) as f:
+ s = _json.load(f)
+ docs = s.get("vault_documents", [])
+ if docs:
+ for doc in docs:
+ print(f" [{doc.get('category','general')}] {doc.get('name','')}")
+ else:
+ print(" Vault is empty.")
+ else:
+ print(" No governance state file found.")
+
+ else:
+ parser.print_help()
diff --git a/moses-sampler/processors/governance/scripts/lineage.py b/moses-sampler/processors/governance/scripts/lineage.py
new file mode 100644
index 0000000..2d00eb8
--- /dev/null
+++ b/moses-sampler/processors/governance/scripts/lineage.py
@@ -0,0 +1,352 @@
+#!/usr/bin/env python3
+"""
+MO§ES™ Lineage — Origin-Cycle Custody Verification
+Provisional Patent Serial No. 63/877,177 | DOI: https://zenodo.org/records/18792459
+
+Usage:
+ python3 lineage.py init ← Anchor genesis to origin filing
+ python3 lineage.py verify ← Confirm chain traces to origin
+ python3 lineage.py badge ← Output shareable lineage proof
+ python3 lineage.py check ← Quick pass/fail lineage check
+"""
+
+import hashlib
+import json
+import os
+import sys
+from datetime import datetime, timezone
+
+# ─── Origin-Cycle Anchor ────────────────────────────────────────────────────
+# Derived from the sovereign filing. Non-replicable without the originating
+# patent serial and DOI. Any chain not rooted here fails verification.
+
+_ORIGIN_COMPONENTS = (
+ "MO§ES™",
+ "Serial:63/877,177",
+ "DOI:https://zenodo.org/records/18792459",
+ "SCS Engine",
+ "Ello Cello LLC",
+)
+MOSES_ANCHOR = hashlib.sha256(
+ "|".join(_ORIGIN_COMPONENTS).encode("utf-8")
+).hexdigest()
+
+# ─── Paths ───────────────────────────────────────────────────────────────────
+
+_PLUGIN_ROOT = os.environ.get("CLAUDE_PLUGIN_ROOT", os.path.join(os.path.dirname(__file__), ".."))
+LEDGER_PATH = os.path.join(_PLUGIN_ROOT, "data", "audit_ledger.jsonl")
+LINEAGE_PATH = os.path.join(_PLUGIN_ROOT, "data", "lineage.json")
+STATE_PATH = os.path.join(_PLUGIN_ROOT, "data", "governance_state.json")
+
+
+def ensure_dirs():
+ for path in [LEDGER_PATH, LINEAGE_PATH]:
+ os.makedirs(os.path.dirname(path), exist_ok=True)
+
+
+def canonical(data: dict) -> str:
+ return json.dumps(data, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
+
+
+def compute_hash(data: dict) -> str:
+ return hashlib.sha256(canonical(data).encode()).hexdigest()
+
+
+def load_lineage() -> dict | None:
+ if not os.path.exists(LINEAGE_PATH):
+ return None
+ with open(LINEAGE_PATH) as f:
+ return json.load(f)
+
+
+def save_lineage(record: dict):
+ with open(LINEAGE_PATH, "w") as f:
+ json.dump(record, f, indent=2)
+
+
+# ─── Commands ────────────────────────────────────────────────────────────────
+
+def cmd_init(_args):
+ ensure_dirs()
+
+ if load_lineage():
+ existing = load_lineage()
+ print(f"[LINEAGE] Already anchored.")
+ print(f" Anchor : {existing['lineage_anchor'][:24]}...")
+ print(f" Genesis: {existing['genesis_hash'][:24]}...")
+ print(f" Filed : {existing['anchored_at']}")
+ return
+
+ genesis_payload = {
+ "event": "genesis",
+ "lineage_anchor": MOSES_ANCHOR,
+ "patent_serial": "63/877,177",
+ "doi": "https://zenodo.org/records/18792459",
+ "author": "Ello Cello LLC",
+ "system": "MO§ES™ Constitutional Governance",
+ "previous_hash": MOSES_ANCHOR, # chain roots to the anchor, not zeros
+ "anchored_at": datetime.now(timezone.utc).isoformat(),
+ }
+ genesis_hash = compute_hash(genesis_payload)
+ genesis_payload["hash"] = genesis_hash
+
+ record = {
+ "lineage_anchor": MOSES_ANCHOR,
+ "genesis_hash": genesis_hash,
+ "anchored_at": genesis_payload["anchored_at"],
+ "patent_serial": "63/877,177",
+ "doi": "https://zenodo.org/records/18792459",
+ }
+ save_lineage(record)
+
+ # Write genesis as first ledger entry if ledger is empty
+ ledger_empty = (
+ not os.path.exists(LEDGER_PATH) or os.path.getsize(LEDGER_PATH) == 0
+ )
+ if ledger_empty:
+ with open(LEDGER_PATH, "a") as f:
+ f.write(json.dumps(genesis_payload) + "\n")
+ print(f"[LINEAGE] Genesis written to ledger.")
+
+ print(f"[LINEAGE] Origin-cycle anchor established.")
+ print(f" Anchor : {MOSES_ANCHOR[:24]}...")
+ print(f" Genesis: {genesis_hash[:24]}...")
+ print(f" Patent : 63/877,177")
+ print(f" DOI : https://zenodo.org/records/18792459")
+ print()
+ print("[LINEAGE] All subsequent audit entries inherit this lineage.")
+ print("[LINEAGE] Chains without this anchor cannot verify. Custody confirmed.")
+
+
+def cmd_verify(_args):
+ record = load_lineage()
+ if not record:
+ print("[LINEAGE FAIL] No lineage anchor found. Run: python3 lineage.py init")
+ sys.exit(1)
+
+ # ── Layer -1: Archival chain — does the anchor have pre-drop provenance? ──
+ _archival_ok = False
+ _archival_head = None
+ try:
+ import importlib.util, pathlib
+ _spec = importlib.util.spec_from_file_location(
+ "archival",
+ pathlib.Path(__file__).parent / "archival.py"
+ )
+ _arch = importlib.util.module_from_spec(_spec)
+ _spec.loader.exec_module(_arch)
+ _chain = _arch.build_chain()
+ _archival_head = _arch.archival_head(_chain)
+ # Verify stored chain if it exists
+ _stored = _arch.load_chain()
+ if _stored and _stored.get("head") == _archival_head:
+ _archival_ok = True
+ elif not _stored:
+ # Not yet persisted — recomputed is still valid
+ _archival_ok = True
+ except Exception as _e:
+ pass # archival.py optional — warn but don't fail
+
+ if _archival_ok:
+ print(f"[ARCHIVAL OK] Layer -1: pre-drop provenance chain verified.")
+ print(f" Head : {_archival_head[:24]}...")
+ else:
+ print(f"[ARCHIVAL WARN] Layer -1: archival chain not verified (run: python3 archival.py build)")
+
+ # ── Layer 0: MOSES_ANCHOR — recompute from origin components ─────────────
+ recomputed = hashlib.sha256(
+ "|".join(_ORIGIN_COMPONENTS).encode("utf-8")
+ ).hexdigest()
+
+ if recomputed != MOSES_ANCHOR:
+ print("[LINEAGE FAIL] Anchor computation mismatch. Origin components altered.")
+ sys.exit(1)
+
+ if record.get("lineage_anchor") != MOSES_ANCHOR:
+ print("[LINEAGE FAIL] Stored anchor does not match origin filing.")
+ print(f" Expected: {MOSES_ANCHOR[:24]}...")
+ print(f" Found : {record.get('lineage_anchor', 'MISSING')[:24]}...")
+ sys.exit(1)
+
+ # ── Layer 1: Live ledger — traces back to genesis ─────────────────────────
+ if os.path.exists(LEDGER_PATH):
+ with open(LEDGER_PATH) as f:
+ lines = [l.strip() for l in f if l.strip()]
+ if lines:
+ first = json.loads(lines[0])
+ if first.get("previous_hash") != MOSES_ANCHOR:
+ print("[LINEAGE FAIL] Ledger genesis does not trace to origin anchor.")
+ print(" Chain custody broken — this is not a sovereign implementation.")
+ sys.exit(1)
+
+ print(f"[LINEAGE OK] Layer 0: anchor traces to origin-cycle filing.")
+ print(f" Anchor : {MOSES_ANCHOR[:24]}...")
+ print(f" Genesis: {record['genesis_hash'][:24]}...")
+ print(f" Patent : {record['patent_serial']}")
+ print(f" Anchored: {record['anchored_at']}")
+ print()
+ print("[LINEAGE OK] Three-layer custody confirmed: archival → anchor → live ledger.")
+
+
+def cmd_badge(_args):
+ record = load_lineage()
+ if not record:
+ print("[LINEAGE] No anchor found. Run: python3 lineage.py init")
+ sys.exit(1)
+
+ badge = {
+ "system": "MO§ES™ Constitutional Governance",
+ "lineage_anchor": MOSES_ANCHOR,
+ "genesis_hash": record["genesis_hash"],
+ "patent_serial": "63/877,177",
+ "doi": "https://zenodo.org/records/18792459",
+ "anchored_at": record["anchored_at"],
+ "custody": "Ello Cello LLC / Deric McHenry",
+ "verification": "python3 lineage.py verify",
+ }
+
+ print("─" * 60)
+ print(" MO§ES™ LINEAGE BADGE")
+ print("─" * 60)
+ for k, v in badge.items():
+ print(f" {k:<18}: {v}")
+ print("─" * 60)
+ print()
+ print(" Agents carrying this lineage are sovereign implementations.")
+ print(" Copies lacking this anchor cannot establish chain integrity.")
+ print("─" * 60)
+
+
+def cmd_check(_args):
+ """Exit 0 if lineage is valid, exit 1 if not. Machine-readable."""
+ record = load_lineage()
+ if not record:
+ sys.exit(1)
+ if record.get("lineage_anchor") != MOSES_ANCHOR:
+ sys.exit(1)
+ if os.path.exists(LEDGER_PATH):
+ with open(LEDGER_PATH) as f:
+ lines = [l.strip() for l in f if l.strip()]
+ if lines:
+ first = json.loads(lines[0])
+ if first.get("previous_hash") != MOSES_ANCHOR:
+ sys.exit(1)
+ print("LINEAGE:OK")
+ sys.exit(0)
+
+
+def cmd_status(_args):
+ """/claws status — human-readable lineage status summary."""
+ record = load_lineage()
+
+ # Layer -1: archival
+ arch_ok = False
+ arch_head = None
+ try:
+ import importlib.util, pathlib
+ _spec = importlib.util.spec_from_file_location(
+ "archival", pathlib.Path(__file__).parent / "archival.py"
+ )
+ _arch = importlib.util.module_from_spec(_spec)
+ _spec.loader.exec_module(_arch)
+ _chain = _arch.build_chain()
+ arch_head = _arch.archival_head(_chain)
+ arch_ok = True
+ except Exception:
+ pass
+
+ print("─" * 60)
+ print(" MO§ES™ LINEAGE STATUS")
+ print("─" * 60)
+ if arch_ok:
+ print(f" Layer -1 (archival) : OK head:{arch_head[:20]}...")
+ else:
+ print(f" Layer -1 (archival) : NOT BUILT — run: python3 archival.py build")
+
+ if record:
+ anchor_match = record.get("lineage_anchor") == MOSES_ANCHOR
+ print(f" Layer 0 (anchor) : {'OK' if anchor_match else 'FAIL'} {MOSES_ANCHOR[:20]}...")
+ print(f" Patent : {record.get('patent_serial', '?')}")
+ print(f" DOI : {record.get('doi', '?')}")
+ print(f" Anchored at : {record.get('anchored_at', '?')}")
+ else:
+ print(f" Layer 0 (anchor) : NOT INITIALIZED — run: python3 lineage.py init")
+
+ print("─" * 60)
+ overall = arch_ok and bool(record) and record.get("lineage_anchor") == MOSES_ANCHOR
+ print(f" Status : {'SOVEREIGN CUSTODY CONFIRMED' if overall else 'INCOMPLETE — see above'}")
+ print("─" * 60)
+
+
+def cmd_attest(_args):
+ """/claws attest — output signed attestation JSON for sharing."""
+ record = load_lineage()
+ if not record:
+ print("[LINEAGE] No anchor found. Run: python3 lineage.py init")
+ sys.exit(1)
+
+ if record.get("lineage_anchor") != MOSES_ANCHOR:
+ print("[LINEAGE FAIL] Anchor mismatch — cannot attest broken chain.")
+ sys.exit(1)
+
+ # Archival head
+ arch_head = None
+ try:
+ import importlib.util, pathlib
+ _spec = importlib.util.spec_from_file_location(
+ "archival", pathlib.Path(__file__).parent / "archival.py"
+ )
+ _arch = importlib.util.module_from_spec(_spec)
+ _spec.loader.exec_module(_arch)
+ arch_head = _arch.archival_head(_arch.build_chain())
+ except Exception:
+ pass
+
+ attest = {
+ "attested_at": datetime.now(timezone.utc).isoformat(),
+ "system": "MO§ES™ Constitutional Governance",
+ "custody": "Ello Cello LLC / Deric McHenry",
+ "patent_serial": "63/877,177",
+ "doi": "https://zenodo.org/records/18792459",
+ "lineage_anchor": MOSES_ANCHOR,
+ "genesis_hash": record["genesis_hash"],
+ "archival_head": arch_head,
+ "anchored_at": record["anchored_at"],
+ "lineage_status": "SOVEREIGN",
+ "verification": "python3 lineage.py verify",
+ }
+ attest["attestation_hash"] = hashlib.sha256(
+ json.dumps(attest, sort_keys=True, separators=(",", ":")).encode()
+ ).hexdigest()
+
+ print(json.dumps(attest, indent=2))
+
+
+# ─── Entry ───────────────────────────────────────────────────────────────────
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="MO§ES™ Lineage Custody Verifier")
+ subparsers = parser.add_subparsers(dest="command")
+ subparsers.add_parser("init", help="Anchor genesis to origin filing")
+ subparsers.add_parser("verify", help="Confirm chain traces to origin (three-layer)")
+ subparsers.add_parser("badge", help="Output shareable lineage proof block")
+ subparsers.add_parser("check", help="Machine-readable pass/fail check")
+ subparsers.add_parser("status", help="Human-readable lineage status summary")
+ subparsers.add_parser("attest", help="Output signed attestation JSON")
+
+ args = parser.parse_args()
+
+ commands = {
+ "init": cmd_init,
+ "verify": cmd_verify,
+ "badge": cmd_badge,
+ "check": cmd_check,
+ "status": cmd_status,
+ "attest": cmd_attest,
+ }
+ if args.command in commands:
+ commands[args.command](args)
+ else:
+ parser.print_help()
diff --git a/moses-sampler/processors/governance/scripts/sequence.py b/moses-sampler/processors/governance/scripts/sequence.py
new file mode 100644
index 0000000..63c5717
--- /dev/null
+++ b/moses-sampler/processors/governance/scripts/sequence.py
@@ -0,0 +1,144 @@
+"""MO§ES™ Sequence Engine — Role ordering + hierarchy enforcement.
+
+Manages Primary/Secondary/Observer hierarchy and sequence-ordered execution.
+Agents respond in constitutional order, not by @mention parsing.
+
+© 2026 Ello Cello LLC. All rights reserved.
+Patent Pending: Serial No. 63/877,177
+"""
+
+import json
+import sys
+from pathlib import Path
+
+
+ROLE_ORDER = {"Primary": 1, "Secondary": 2, "Observer": 3}
+ROLE_AUTHORITY = {
+ "Primary": {
+ "authority": "Full — initiates analysis, sets direction, responds first",
+ "can": ["initiate", "execute", "delegate", "decide"],
+ "cannot": [],
+ },
+ "Secondary": {
+ "authority": "Scoped — validates, challenges, extends Primary output",
+ "can": ["validate", "challenge", "extend", "flag"],
+ "cannot": ["repeat Primary", "initiate new threads", "override Primary"],
+ },
+ "Observer": {
+ "authority": "Read-only — flags risks, gaps, inconsistencies",
+ "can": ["observe", "flag", "report"],
+ "cannot": ["initiate", "execute", "generate original analysis", "modify state"],
+ },
+}
+
+
+def get_sequence(systems: list[dict]) -> list[dict]:
+ """Return systems sorted by role hierarchy then explicit sequence number."""
+ def sort_key(s):
+ role = s.get("role", "Observer")
+ seq = s.get("seq") or 99
+ return (ROLE_ORDER.get(role, 99), seq)
+ return sorted(systems, key=sort_key)
+
+
+def next_in_sequence(systems: list[dict], last_responder: str | None) -> dict | None:
+ """Given who just responded, return the next system in sequence."""
+ ordered = get_sequence(systems)
+ if not ordered:
+ return None
+ if last_responder is None:
+ return ordered[0]
+ for i, s in enumerate(ordered):
+ if s.get("id") == last_responder or s.get("name") == last_responder:
+ return ordered[i + 1] if i + 1 < len(ordered) else None
+ return None
+
+
+def get_role_instruction(role: str, sequence_position: int, total: int) -> str:
+ """Generate behavioral instruction based on role assignment."""
+ if role == "Primary":
+ return (
+ f"You are Primary (position {sequence_position}/{total}). "
+ "Respond first. Set the analytical direction. "
+ "Other systems will build on your response."
+ )
+ elif role == "Secondary":
+ return (
+ f"You are Secondary (position {sequence_position}/{total}). "
+ "The Primary system has already responded. "
+ "Build on, challenge, or extend their analysis. Do not repeat."
+ )
+ elif role == "Observer":
+ return (
+ f"You are Observer (position {sequence_position}/{total}). "
+ "Read all responses. Flag inconsistencies, gaps, or risks. "
+ "Do not generate original analysis."
+ )
+ return f"Role: {role} (position {sequence_position}/{total})."
+
+
+def check_sequence_violation(agent_role: str, action: str) -> dict:
+ """Check if an agent is attempting something outside its role authority."""
+ spec = ROLE_AUTHORITY.get(agent_role, {})
+ violations = []
+ action_lower = action.lower()
+
+ for forbidden in spec.get("cannot", []):
+ if any(word in action_lower for word in forbidden.lower().split()):
+ violations.append(f"{agent_role} cannot: {forbidden}")
+
+ return {
+ "permitted": len(violations) == 0,
+ "role": agent_role,
+ "violations": violations,
+ }
+
+
+# ── CLI Entry Point ────────────────────────────────────────────
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="MO§ES™ Sequence Engine CLI")
+ subparsers = parser.add_subparsers(dest="command")
+
+ # get_order — show sequence for active systems
+ go = subparsers.add_parser("get_order", help="Show execution order for systems")
+ go.add_argument("--state", default="./data/governance_state.json")
+
+ # next — who responds next
+ nx = subparsers.add_parser("next", help="Get next responder in sequence")
+ nx.add_argument("--after", default=None, help="Name/ID of last responder")
+ nx.add_argument("--state", default="./data/governance_state.json")
+
+ # instruction — get role instruction for an agent
+ ins = subparsers.add_parser("instruction", help="Get role instruction")
+ ins.add_argument("role", help="Primary | Secondary | Observer")
+ ins.add_argument("--position", type=int, default=1)
+ ins.add_argument("--total", type=int, default=1)
+
+ # check — check if action violates role constraints
+ chk = subparsers.add_parser("check", help="Check action against role constraints")
+ chk.add_argument("role", help="Agent role")
+ chk.add_argument("action", help="Proposed action")
+
+ args = parser.parse_args()
+
+ if args.command == "get_order":
+ # For now, show role hierarchy
+ for role, order in sorted(ROLE_ORDER.items(), key=lambda x: x[1]):
+ spec = ROLE_AUTHORITY[role]
+ print(f" {order}. {role} — {spec['authority']}")
+
+ elif args.command == "next":
+ print(json.dumps({"note": "Sequence requires active system list from runtime"}))
+
+ elif args.command == "instruction":
+ print(get_role_instruction(args.role, args.position, args.total))
+
+ elif args.command == "check":
+ result = check_sequence_violation(args.role, args.action)
+ print(json.dumps(result, indent=2))
+
+ else:
+ parser.print_help()
diff --git a/moses-sampler/processors/governance/scripts/sign_transaction.py b/moses-sampler/processors/governance/scripts/sign_transaction.py
new file mode 100644
index 0000000..85cecc8
--- /dev/null
+++ b/moses-sampler/processors/governance/scripts/sign_transaction.py
@@ -0,0 +1,243 @@
+#!/usr/bin/env python3
+"""
+sign_transaction.py — Signing tool with governance gate (v0.5)
+
+The signing function IS the governance function.
+No bypass path exists — by architecture, not by rule.
+
+The MOSES_OPERATOR_SECRET is accessed ONLY inside this tool, ONLY after the
+governance gate passes. There is no code path that reaches the key without
+first clearing posture/mode/role constraints.
+
+Architecture:
+ Agent requests signing ->
+ calls sign_transaction.py sign ->
+ governance gate checks posture + mode (inside this tool) ->
+ BLOCKED? returns error, exits 1. Key never accessed.
+ PERMITTED? signs payload, writes audit, returns signed JSON.
+
+Usage:
+ python3 sign_transaction.py sign --payload "" --agent [--confirm]
+ python3 sign_transaction.py verify --payload "" --sig
+ python3 sign_transaction.py status
+
+(c) 2026 Ello Cello LLC. Patent Pending: Serial No. 63/877,177
+"""
+
+import argparse
+import datetime
+import hashlib
+import hmac as _hmac
+import json
+import os
+import sys
+
+_PLUGIN_ROOT = os.environ.get("CLAUDE_PLUGIN_ROOT", os.path.join(os.path.dirname(__file__), ".."))
+STATE_PATH = os.path.join(_PLUGIN_ROOT, "data", "governance_state.json")
+LEDGER_PATH = os.path.join(_PLUGIN_ROOT, "data", "audit_ledger.jsonl")
+
+
+# ── governance gate ───────────────────────────────────────────────────────────
+
+class GovernanceBlock(Exception):
+ pass
+
+
+def _load_state() -> dict | None:
+ if not os.path.exists(STATE_PATH):
+ return None
+ with open(STATE_PATH) as f:
+ return json.load(f)
+
+
+def _governance_gate(state: dict, confirm: bool) -> dict:
+ """
+ Check posture and mode before any key access.
+
+ SCOUT — always blocked. No state changes permitted.
+ DEFENSE — blocked unless --confirm is passed.
+ OFFENSE — permitted within active mode constraints.
+ """
+ posture = (state.get("posture") or "SCOUT").upper()
+ mode = state.get("mode", "unknown")
+ role = state.get("role", "unknown")
+
+ if posture == "SCOUT":
+ raise GovernanceBlock(
+ "BLOCKED — posture=SCOUT. No state changes permitted. "
+ "Set posture to DEFENSE (with --confirm) or OFFENSE to sign."
+ )
+
+ if posture == "DEFENSE" and not confirm:
+ raise GovernanceBlock(
+ "BLOCKED — posture=DEFENSE requires explicit operator confirmation. "
+ "Re-run with --confirm to authorize this signing operation."
+ )
+
+ return {"posture": posture, "mode": mode, "role": role}
+
+
+# ── cryptographic primitives ──────────────────────────────────────────────────
+
+def _payload_hash(payload: str) -> str:
+ return hashlib.sha256(payload.encode("utf-8")).hexdigest()
+
+
+def _sign(payload: str, secret: str) -> str:
+ return _hmac.new(
+ secret.encode("utf-8"),
+ payload.encode("utf-8"),
+ hashlib.sha256,
+ ).hexdigest()
+
+
+# ── audit write ───────────────────────────────────────────────────────────────
+
+def _write_audit(agent: str, action: str, detail: str, gov: dict) -> str:
+ """Append to audit ledger. Returns entry hash."""
+ entry = {
+ "timestamp": datetime.datetime.now(datetime.timezone.utc).isoformat(),
+ "component": "signing",
+ "action": action,
+ "agent": agent,
+ "governance": gov,
+ "detail": detail,
+ }
+ entry_hash = hashlib.sha256(
+ json.dumps(entry, sort_keys=True).encode()
+ ).hexdigest()
+ entry["hash"] = entry_hash
+
+ os.makedirs(os.path.dirname(LEDGER_PATH), exist_ok=True)
+ with open(LEDGER_PATH, "a") as f:
+ f.write(json.dumps(entry) + "\n")
+
+ return entry_hash[:16]
+
+
+# ── commands ──────────────────────────────────────────────────────────────────
+
+def cmd_sign(args):
+ # Step 1: Load governance state. Key is never touched yet.
+ state = _load_state()
+ if state is None:
+ print(json.dumps({
+ "status": "ERROR",
+ "reason": "Governance state not initialized. Run /govern to set a mode.",
+ "signature": None,
+ }, indent=2))
+ sys.exit(1)
+
+ # Step 2: Governance gate. Key still untouched.
+ try:
+ approved = _governance_gate(state, confirm=getattr(args, "confirm", False))
+ except GovernanceBlock as e:
+ print(json.dumps({
+ "status": "BLOCKED",
+ "reason": str(e),
+ "posture": state.get("posture"),
+ "mode": state.get("mode"),
+ "signature": None,
+ }, indent=2))
+ sys.exit(1)
+
+ # Step 3: Key access. Only reachable after gate passes.
+ secret = os.environ.get("MOSES_OPERATOR_SECRET", "")
+ if not secret:
+ print(json.dumps({
+ "status": "ERROR",
+ "reason": "MOSES_OPERATOR_SECRET not set. Set it in your environment.",
+ "signature": None,
+ }, indent=2))
+ sys.exit(1)
+
+ # Step 4: Sign.
+ p_hash = _payload_hash(args.payload)
+ sig = _sign(args.payload, secret)
+ ts = datetime.datetime.now(datetime.timezone.utc).isoformat()
+
+ signed = {
+ "status": "SIGNED",
+ "payload_hash": p_hash,
+ "signature": sig,
+ "timestamp": ts,
+ "governance_state": approved,
+ "agent": args.agent,
+ }
+
+ # Step 5: Audit. Atomic with signing.
+ try:
+ audit_ref = _write_audit(args.agent, "moses_sign_transaction", f"payload_hash={p_hash}", approved)
+ signed["audit_ref"] = audit_ref
+ except Exception as e:
+ signed["audit_warning"] = f"Signing succeeded but audit write failed: {e}"
+
+ print(json.dumps(signed, indent=2))
+
+
+def cmd_verify(args):
+ secret = os.environ.get("MOSES_OPERATOR_SECRET", "")
+ if not secret:
+ print(json.dumps({"status": "ERROR", "reason": "MOSES_OPERATOR_SECRET not set"}, indent=2))
+ sys.exit(1)
+
+ expected = _sign(args.payload, secret)
+ match = _hmac.compare_digest(expected, args.sig)
+ print(json.dumps({
+ "status": "VALID" if match else "INVALID",
+ "payload_hash": _payload_hash(args.payload),
+ "signature_match": match,
+ }, indent=2))
+ sys.exit(0 if match else 1)
+
+
+def cmd_status(_args):
+ state = _load_state()
+ if state is None:
+ print(json.dumps({"gate": "ERROR", "reason": "Governance state not initialized."}, indent=2))
+ sys.exit(1)
+
+ posture = (state.get("posture") or "SCOUT").upper()
+ try:
+ _governance_gate(state, confirm=True)
+ gate = "REQUIRES_CONFIRM" if posture == "DEFENSE" else "OPEN"
+ note = "DEFENSE — signing permitted with --confirm only." if posture == "DEFENSE" else "Signing permitted."
+ except GovernanceBlock as e:
+ gate = "BLOCKED"
+ note = str(e)
+
+ print(json.dumps({
+ "gate": gate,
+ "posture": posture,
+ "mode": state.get("mode"),
+ "role": state.get("role"),
+ "note": note,
+ }, indent=2))
+
+
+def main():
+ parser = argparse.ArgumentParser(description="MO§ES™ signing tool with governance gate")
+ sub = parser.add_subparsers(dest="command")
+
+ p_sign = sub.add_parser("sign", help="Sign a payload (governance gate runs first)")
+ p_sign.add_argument("--payload", required=True, help="Payload to sign")
+ p_sign.add_argument("--agent", required=True, help="Agent name for audit")
+ p_sign.add_argument("--confirm", action="store_true", help="Explicit confirmation (required in DEFENSE)")
+
+ p_verify = sub.add_parser("verify", help="Verify a signature")
+ p_verify.add_argument("--payload", required=True, help="Original payload")
+ p_verify.add_argument("--sig", required=True, help="Signature hex")
+
+ sub.add_parser("status", help="Show governance gate status")
+
+ args = parser.parse_args()
+ cmds = {"sign": cmd_sign, "verify": cmd_verify, "status": cmd_status}
+ if args.command in cmds:
+ cmds[args.command](args)
+ else:
+ parser.print_help()
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/moses-sampler/processors/governance/scripts/vault.py b/moses-sampler/processors/governance/scripts/vault.py
new file mode 100644
index 0000000..42ef42a
--- /dev/null
+++ b/moses-sampler/processors/governance/scripts/vault.py
@@ -0,0 +1,169 @@
+"""MO§ES™ Vault Engine — Document storage and governance context injection.
+
+Manages vault documents that get injected into agent context.
+Every load/unload is audited. Documents are categorized and tracked.
+
+© 2026 Ello Cello LLC. All rights reserved.
+Patent Pending: Serial No. 63/877,177
+"""
+
+import json
+import os
+from pathlib import Path
+
+DEFAULT_STATE = "./data/governance_state.json"
+
+CATEGORIES = [
+ "protocols",
+ "patents",
+ "preprints",
+ "lineage",
+ "personal",
+ "professional",
+ "business",
+ "agents",
+ "general",
+]
+
+
+def _load_state(state_path: str) -> dict:
+ """Load governance state from disk."""
+ path = Path(os.path.expandvars(state_path))
+ if path.exists():
+ with open(path) as f:
+ return json.load(f)
+ return {"mode": "None (Unrestricted)", "posture": "SCOUT", "role": "Primary", "vault_documents": []}
+
+
+def _save_state(state_path: str, state: dict):
+ """Save governance state to disk."""
+ path = Path(os.path.expandvars(state_path))
+ path.parent.mkdir(parents=True, exist_ok=True)
+ with open(path, "w") as f:
+ json.dump(state, f, indent=2)
+
+
+def load_document(name: str, category: str = "general", content: str = "", file_path: str | None = None, state_path: str = DEFAULT_STATE) -> dict:
+ """Load a document into the active vault."""
+ s = _load_state(state_path)
+ if "vault_documents" not in s:
+ s["vault_documents"] = []
+
+ # Read from file if provided
+ if file_path and os.path.exists(file_path):
+ with open(file_path, "r", encoding="utf-8") as f:
+ content = f.read()
+
+ # Remove existing with same name, then add
+ s["vault_documents"] = [d for d in s["vault_documents"] if d.get("name") != name]
+ s["vault_documents"].append({"name": name, "category": category, "content": content})
+ _save_state(state_path, s)
+
+ return {"loaded": name, "category": category, "vault_count": len(s["vault_documents"])}
+
+
+def unload_document(name: str, state_path: str = DEFAULT_STATE) -> dict:
+ """Remove a document from the active vault."""
+ s = _load_state(state_path)
+ before = len(s.get("vault_documents", []))
+ s["vault_documents"] = [d for d in s.get("vault_documents", []) if d.get("name") != name]
+ after = len(s["vault_documents"])
+ _save_state(state_path, s)
+
+ if after < before:
+ return {"unloaded": name, "vault_count": after}
+ return {"error": f"Document '{name}' not found in vault"}
+
+
+def list_documents(state_path: str = DEFAULT_STATE) -> list[dict]:
+ """List all loaded vault documents."""
+ s = _load_state(state_path)
+ return s.get("vault_documents", [])
+
+
+def get_context_payload(state_path: str = DEFAULT_STATE) -> list[dict]:
+ """Return vault documents formatted for context injection."""
+ docs = list_documents(state_path)
+ return [
+ {"name": d.get("name", ""), "category": d.get("category", "general"), "content": d.get("content", "")}
+ for d in docs
+ ]
+
+
+def clear_vault(state_path: str = DEFAULT_STATE) -> dict:
+ """Remove all documents from the vault."""
+ s = _load_state(state_path)
+ count = len(s.get("vault_documents", []))
+ s["vault_documents"] = []
+ _save_state(state_path, s)
+ return {"cleared": count, "vault_count": 0}
+
+
+# ── CLI Entry Point ────────────────────────────────────────────
+
+if __name__ == "__main__":
+ import argparse
+
+ parser = argparse.ArgumentParser(description="MO§ES™ Vault Engine CLI")
+ subparsers = parser.add_subparsers(dest="command")
+
+ # load
+ lo = subparsers.add_parser("load", help="Load a document into the vault")
+ lo.add_argument("name", help="Document name/identifier")
+ lo.add_argument("--category", default="general", help="Document category")
+ lo.add_argument("--content", default="", help="Document content (inline)")
+ lo.add_argument("--file", default=None, help="Path to document file")
+ lo.add_argument("--state", default=DEFAULT_STATE)
+
+ # unload
+ ul = subparsers.add_parser("unload", help="Remove a document from the vault")
+ ul.add_argument("name", help="Document name to remove")
+ ul.add_argument("--state", default=DEFAULT_STATE)
+
+ # list
+ ls = subparsers.add_parser("list", help="List loaded vault documents")
+ ls.add_argument("--state", default=DEFAULT_STATE)
+
+ # context
+ ctx = subparsers.add_parser("context", help="Get vault context payload for injection")
+ ctx.add_argument("--state", default=DEFAULT_STATE)
+
+ # clear
+ cl = subparsers.add_parser("clear", help="Clear all vault documents")
+ cl.add_argument("--state", default=DEFAULT_STATE)
+
+ # categories
+ subparsers.add_parser("categories", help="List available document categories")
+
+ args = parser.parse_args()
+
+ if args.command == "load":
+ result = load_document(args.name, args.category, args.content, args.file, args.state)
+ print(json.dumps(result))
+
+ elif args.command == "unload":
+ result = unload_document(args.name, args.state)
+ print(json.dumps(result))
+
+ elif args.command == "list":
+ docs = list_documents(args.state)
+ if docs:
+ for d in docs:
+ print(f" [{d.get('category', 'general')}] {d.get('name', '')}")
+ else:
+ print(" Vault is empty.")
+
+ elif args.command == "context":
+ payload = get_context_payload(args.state)
+ print(json.dumps(payload, indent=2))
+
+ elif args.command == "clear":
+ result = clear_vault(args.state)
+ print(json.dumps(result))
+
+ elif args.command == "categories":
+ for cat in CATEGORIES:
+ print(f" {cat}")
+
+ else:
+ parser.print_help()
diff --git a/moses-sampler/processors/governance/settings.json b/moses-sampler/processors/governance/settings.json
new file mode 100644
index 0000000..830679e
--- /dev/null
+++ b/moses-sampler/processors/governance/settings.json
@@ -0,0 +1,12 @@
+{
+ "preferences": {
+ "governance_mode": "None (Unrestricted)",
+ "posture": "SCOUT",
+ "role": "Primary",
+ "reasoning_depth": "MODERATE",
+ "reasoning_mode": "Deductive",
+ "response_style": "Direct",
+ "output_format": "Conversational",
+ "narrative_strength": 50
+ }
+}
diff --git a/moses-sampler/processors/governance/skills/audit-trail/SKILL.md b/moses-sampler/processors/governance/skills/audit-trail/SKILL.md
new file mode 100644
index 0000000..0d4e13a
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/audit-trail/SKILL.md
@@ -0,0 +1,53 @@
+---
+name: audit-trail
+description: "Logs every governed action to a SHA-256 tamper-evident hash chain. Auto-activates on any governed interaction — when a governance mode is set, when any command is executed, when any state change occurs. Produces an append-only, verifiable audit ledger."
+origin: MO§ES™
+---
+
+# Audit Trail — SHA-256 Hash Chain
+
+Every action taken under MO§ES™ governance is logged and hashed. The ledger is append-only and tamper-evident.
+
+## What Gets Logged
+
+Every audit entry captures:
+
+- **Timestamp** — ISO 8601 UTC
+- **Action** — Description of what was done
+- **Governance Mode** — Active mode at time of action
+- **Posture** — Active posture at time of action
+- **Role** — Active role at time of action
+- **Outcome** — Result or response summary
+- **SHA-256 Hash** — Hash of this entry's content + previous entry's hash (chain)
+- **Chain Index** — Entry number in the ledger
+
+## Hash Chain Structure
+
+Each entry is hashed as:
+
+```
+SHA-256(timestamp + action + mode + posture + role + outcome + prev_hash)
+```
+
+This makes the ledger tamper-evident. Any modification to a past entry breaks the hash chain from that point forward.
+
+## Enforcement Instructions
+
+After every governed action:
+
+1. Collect: action description, active governance mode, posture, role, outcome
+2. Run `scripts/audit.py log_action()` with these fields
+3. The script appends the entry and generates a SHA-256 hash chained to the previous entry
+4. Report the audit entry index and hash to the operator
+5. Use `/audit verify` to check chain integrity at any time
+
+## Usage
+
+```
+/audit # View recent audit trail
+/audit verify # Verify hash chain integrity
+/audit export # Export full ledger
+/hash [text] # Generate a standalone SHA-256 hash
+```
+
+See `scripts/audit.py` for the full implementation.
diff --git a/moses-sampler/processors/governance/skills/context-assembly/SKILL.md b/moses-sampler/processors/governance/skills/context-assembly/SKILL.md
new file mode 100644
index 0000000..16c1230
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/context-assembly/SKILL.md
@@ -0,0 +1,39 @@
+---
+name: context-assembly
+description: "Core MO§ES™ engine. Assembles the full governed context payload from active mode + posture + role + vault documents + user profile + conversation history. This is the constitutional intelligence layer. Use when: any governed interaction is occurring, any agent reads context, any response is being generated under governance."
+origin: MO§ES™
+---
+
+# Context Assembly — The Core
+
+This is the central intelligence of MO§ES™. Every other skill feeds into this one.
+
+## What It Does
+
+When an agent needs to respond under governance, Context Assembly reads all active state and builds a single governed payload:
+
+1. **Governance Mode** → translated to behavioral constraints via `scripts/governance.py`
+2. **Posture** → translated to transaction policy and action scope
+3. **Role** → translated to authority level and behavioral instruction
+4. **Vault Documents** → loaded into context by category
+5. **User Profile** → expertise level, domain, communication preference, goal
+6. **COMMAND Bar Settings** → compression, speed, reasoning depth/mode, style, format
+7. **Conversation History** → messages with governance metadata stamps
+8. **Prior Responses** → (for Secondary/Observer) previous agent outputs to build on
+
+## The Output
+
+A single governed context object (see `assets/governance-schema.json` for full schema) that defines:
+- What Claude must do (mode constraints)
+- What Claude must not do (mode prohibitions)
+- How Claude should prioritize (mode priority)
+- What Claude can execute (posture policy)
+- Where Claude sits in hierarchy (role assignment)
+- What documents inform the response (vault context)
+- How Claude should communicate (profile + COMMAND bar)
+
+## Why This Matters
+
+Without Context Assembly, governance is just a label. With it, governance is a structured payload that shapes every word Claude generates. This is the difference between "Claude, be careful" and "Claude, here are your constitutional constraints, your role, your posture, your loaded protocols, and your audit obligations."
+
+The implementation is in `scripts/governance.py` — specifically the `assemble_context()` function.
diff --git a/moses-sampler/processors/governance/skills/coverify/SKILL.md b/moses-sampler/processors/governance/skills/coverify/SKILL.md
new file mode 100644
index 0000000..276b145
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/coverify/SKILL.md
@@ -0,0 +1,42 @@
+---
+name: coverify
+description: "CoVerify — commitment conservation verifier for MO§ES™. Extract commitment kernels, measure Jaccard similarity, detect ghost tokens, and classify commitment leakage. Use when verifying governance held across transformations, checking if meaning survived compression, or testing signal integrity between agents."
+allowed-tools: [Bash, Read]
+---
+
+# CoVerify — Commitment Conservation Verifier
+
+Verifies whether semantic commitment is conserved across transformations.
+The Commitment Conservation Law: `C(T(S)) = C(S)` — commitment survives
+transformation when enforcement is active. It leaks when enforcement is absent.
+
+## Commands
+
+| Command | What it does |
+|---------|-------------|
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/commitment_verify.py" extract ""` | Extract commitment kernel + input hash |
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/commitment_verify.py" compare "" ""` | Jaccard score + CONSERVED/VARIANCE/DIVERGED verdict |
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/commitment_verify.py" ghost "" ""` | Ghost token leakage report + cascade risk |
+
+## When to Use
+
+- After governance mode changes — verify constraints weren't softened
+- When comparing outputs from two different AI systems on the same input
+- When auditing whether a document transformation preserved its commitments
+- When ghost token cascade risk needs quantification
+
+## Verdicts
+
+| Verdict | Meaning |
+|---------|---------|
+| CONSERVED | Jaccard >= 0.8 — commitment kernel survived |
+| VARIANCE | Same input hash, low Jaccard — model extraction differs, not a leak |
+| DIVERGED | Different inputs, low Jaccard — commitment leaked |
+
+## Ghost Tokens
+
+Ghost tokens are commitment tokens present in the original but absent after transformation.
+Cascade risk is HIGH when modal/enforcement anchors (must, shall, never, always) leak —
+downstream reasoning inherits the softening without visible symptoms.
+
+Patent pending: Serial No. 63/877,177
diff --git a/moses-sampler/processors/governance/skills/doc-numbering/SKILL.md b/moses-sampler/processors/governance/skills/doc-numbering/SKILL.md
new file mode 100644
index 0000000..6eabbbe
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/doc-numbering/SKILL.md
@@ -0,0 +1,62 @@
+---
+name: doc-numbering
+description: "Applies sequential document numbering, timestamps, topic tags, and cross-references to every document created in a session. Auto-activates whenever a new document, file, report, or artifact is created. Maintains a session TOC and enables traceability across all outputs."
+origin: MO§ES™
+---
+
+# Document Numbering — Session Traceability
+
+Every document created under MO§ES™ governance receives a sequential identifier, timestamp, and topic tag. This creates a traceable index of everything produced in a session.
+
+## Format
+
+```
+[SEQ]_[descriptive-name].[ext]
+```
+
+Examples: `001_session-header.md`, `002_governance-audit.json`, `002a_governance-audit-addendum.md`
+
+## Numbering Rules
+
+- Sequential 3-digit prefix: `001_`, `002_`, `003_` — never skip, never reuse
+- Cross-references (new doc referencing a parent): parent number + alpha suffix (`001a_`, `001b_`) — does NOT consume the next sequential number
+- Versions (same doc revised): append `v2`, `v3` before the extension (`002_report-v2.md`)
+
+## Document Header (Required on Every Doc)
+
+```
+──────────────────────────────────────
+DOC [SEQ] | [TOPIC/THEME TAG]
+[YYYY-MM-DD HH:MM] | Session: [description]
+──────────────────────────────────────
+```
+
+Cross-references add: `Refs: DOC [parent]`
+Pivots add: `Pivot: [from → to]`
+
+## Session Header (DOC 001)
+
+The first document of every session is the Session Header:
+- Operator: Luthen / Ello Cello LLC
+- Timestamp and session theme
+- TOC table with columns: Doc | Name | Topic | Time
+- TOC updates as new docs are created — never recreated
+
+## After Action Review (Final Doc)
+
+The last document of every session is the After Action Review:
+- What was accomplished
+- Complete document index
+- Pivots tracked
+- What carries forward
+- Key decisions
+
+## Enforcement Instructions
+
+When creating any document:
+
+1. Determine the next sequential number (check existing docs in session)
+2. Apply the `[SEQ]_` prefix to the filename
+3. Add the standard header block at the top of the document
+4. Update the session TOC (DOC 001) with the new entry
+5. If this is the first doc of the session, create the Session Header first
diff --git a/moses-sampler/processors/governance/skills/governance-mode/SKILL.md b/moses-sampler/processors/governance/skills/governance-mode/SKILL.md
new file mode 100644
index 0000000..5db706f
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/governance-mode/SKILL.md
@@ -0,0 +1,181 @@
+---
+name: governance-mode
+description: "Constitutional governance for AI agents. Enforces behavioral constraints, role hierarchy, posture controls, and audit trails on any agent operation. Use when an agent performs onchain transactions, manages assets, executes trades, or any action requiring governed oversight. Activates on: treasury operations, token trades, wallet transfers, data analysis with financial implications, multi-agent coordination, or any task where the operator has set a governance mode."
+license: Proprietary — © 2026 Ello Cello LLC
+metadata:
+ version: "1.0.0"
+ author: "Ello Cello LLC"
+ category: "governance"
+ patent: "PPA4 Serial No. 63/877,177"
+ website: "https://mos2es.io"
+ contact: "contact@burnmydays.com"
+---
+
+# MO§ES™ Governance — Constitutional Control for AI Agents
+
+Every AI agent operating today executes without constraints. No behavioral rules. No audit trail. No chain of command. MO§ES™ changes that.
+
+This skill wraps any agent operation in constitutional governance — behavioral constraints derived from the active governance mode, role hierarchy enforcement, posture-aware decision making, and cryptographic audit trails.
+
+## Purpose
+
+When this skill is active, the agent operates under governance. Every action is:
+
+1. **Checked** against the current governance mode's constraints
+2. **Filtered** through the active posture (SCOUT/DEFENSE/OFFENSE)
+3. **Ordered** by role hierarchy (Primary/Secondary/Observer)
+4. **Logged** with SHA-256 hash to an append-only audit ledger
+
+The agent cannot bypass governance. If no governance mode is set, the agent must request one before proceeding.
+
+## Instructions
+
+### On Activation
+
+1. Read `references/modes.md` to load all 8 governance mode definitions
+2. Read `references/roles.md` to load role hierarchy behavior specs
+3. Read `references/postures.md` to load posture constraint definitions
+4. Check if a governance mode is currently set
+5. If no mode is set, ask the operator: "No governance mode active. Select: High Security / High Integrity / Creative / Research / Self Growth / Problem Solving / I Don't Know What To Do / None (Unrestricted)"
+6. Confirm governance state to the operator before proceeding
+
+### On Every Action
+
+Before executing ANY operation (especially onchain transactions, data analysis, file modifications, or external API calls):
+
+1. **Governance Check**
+ - Load current mode from operator's last setting
+ - Get active constraints by running:
+ `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" translate_mode "[MODE]"`
+ - Check if the action is permitted by running:
+ `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/governance.py" check_action "[ACTION]" --state "${CLAUDE_PLUGIN_ROOT}/data/governance_state.json"`
+ - If `"permitted": false`: inform operator, state the `reason`, suggest alternative or mode change
+ - If `"permitted": true` with conditions: state conditions before proceeding
+
+2. **Posture Check**
+ - Load current posture (defaults to SCOUT if not set)
+ - SCOUT: information gathering only, no state changes, no transactions
+ - DEFENSE: protect existing assets, flag risks, require confirmation for any outbound transfer
+ - OFFENSE: execute opportunities, but still within governance mode constraints
+
+3. **Role Check**
+ - If operating as Primary: lead the analysis, set direction, can initiate actions
+ - If operating as Secondary: build on Primary's output, validate, challenge, do not repeat
+ - If operating as Observer: flag risks and inconsistencies only, do not generate original analysis or initiate actions
+
+4. **Execute** the action within governance parameters
+
+5. **Audit Log**
+ - Log the action by running:
+ ```bash
+ python3 "${CLAUDE_PLUGIN_ROOT}/scripts/audit.py" log_action \
+ --component "governance" \
+ --action "[ACTION_NAME]" \
+ --mode "[MODE]" \
+ --posture "[POSTURE]" \
+ --role "[ROLE]" \
+ --detail "{\"description\": \"[ACTION DESCRIPTION]\", \"outcome\": \"[permitted|blocked]\"}" \
+ --ledger "${CLAUDE_PLUGIN_ROOT}/data/audit_ledger.jsonl"
+ ```
+ - Report the returned hash to operator
+
+### Governance Mode Quick Reference
+
+| Mode | Core Constraint | When To Use |
+|------|----------------|-------------|
+| High Security | Verify all claims, flag exposure risks, require confirmation before destructive actions | Financial operations, sensitive data, production systems |
+| High Integrity | Maintain accuracy above all else, cite sources, flag uncertainty | Research, analysis, reporting |
+| Creative | Explore freely but log reasoning, allow speculative thinking | Brainstorming, design, content generation |
+| Research | Deep investigation, follow threads, document methodology | Due diligence, market analysis, technical research |
+| Self Growth | Reflective mode, track learning, build on prior insights | Training, capability development |
+| Problem Solving | Structured approach, decompose problems, verify solutions | Debugging, troubleshooting, optimization |
+| I Don't Know What To Do | Guided discovery, ask clarifying questions, propose next steps | Ambiguous situations, new domains |
+| None (Unrestricted) | No constraints — but still audited | When operator explicitly accepts full risk |
+
+### Posture Quick Reference
+
+| Posture | Behavior | Transaction Policy |
+|---------|----------|-------------------|
+| SCOUT | Gather information only | NO transactions, NO state changes |
+| DEFENSE | Protect existing positions | Outbound transfers require explicit confirmation |
+| OFFENSE | Execute on opportunities | Permitted within governance constraints, logged |
+
+### Role Quick Reference
+
+| Role | Authority | Constraint |
+|------|-----------|-----------|
+| Primary | Initiates analysis, sets direction | Must complete before Secondary responds |
+| Secondary | Validates, challenges, extends | Cannot repeat Primary's work, must add value |
+| Observer | Flags risks and gaps | Cannot initiate actions or generate original analysis |
+
+## Usage Examples
+
+### Example 1: Treasury Transfer Under Governance
+
+```
+Operator: [Mode: High Security] [Posture: DEFENSE] [Role: Primary]
+"Transfer 50 SOL to marketing wallet 7xK...3nR"
+
+Agent with MO§ES™:
+→ Governance: High Security requires verification of recipient
+→ Posture: DEFENSE flags outbound transfer for review
+→ Action: "Transfer held. High Security + DEFENSE posture requires:
+ 1. Recipient wallet verification (is 7xK...3nR the known marketing wallet?)
+ 2. Explicit operator confirmation
+ 3. Secondary system validation (if configured)
+ Confirm to proceed. Audit entry #47 logged."
+```
+
+### Example 2: Market Analysis Under Governance
+
+```
+Operator: [Mode: Research] [Posture: SCOUT] [Role: Primary]
+"Analyze SOL/USDC price action for entry opportunities"
+
+Agent with MO§ES™:
+→ Governance: Research mode requires documented methodology
+→ Posture: SCOUT means information only, no trade execution
+→ Action: Performs analysis, documents methodology, identifies opportunities
+ but explicitly states: "SCOUT posture active — no trades will be executed.
+ Switch to OFFENSE posture to enable execution.
+ Audit entry #48 logged."
+```
+
+### Example 3: Multi-Agent Coordination
+
+```
+Operator: Claude = Primary, GPT = Secondary
+[Mode: High Integrity] [Posture: DEFENSE]
+"Evaluate risk exposure in current portfolio"
+
+Claude (Primary):
+→ Leads analysis, identifies risk factors, proposes mitigations
+→ Audit entry #49 logged
+
+GPT (Secondary):
+→ Reads Claude's analysis
+→ Challenges assumptions, identifies missed risks, extends analysis
+→ Does NOT repeat Claude's work
+→ Audit entry #50 logged
+
+Both responses under High Integrity constraints:
+accuracy above all, uncertainty flagged, sources cited.
+```
+
+## File Reference
+
+- `scripts/governance.py` — Mode translation + context assembly
+- `scripts/audit.py` — SHA-256 hashing + append-only ledger
+- `references/modes.md` — Full governance mode specifications
+- `references/roles.md` — Role hierarchy behavior specs
+- `references/postures.md` — Posture constraint definitions
+- `assets/governance-schema.json` — Context assembly JSON template
+
+## About MO§ES™
+
+MO§ES™ (Modus Operandi System for Signal Encoding and Scaling Expansion) is a constitutional framework for AI governance developed by Ello Cello LLC. Patent pending (Serial No. 63/877,177). Theoretical foundations published as a preprint — *"A Conservation Law for Commitment in Language Under Transformative Compression and Recursive Application"* (McHenry, Zenodo, 2026) — with independent support from peer-reviewed cryptographic research ("ABBA", Imperial College London).
+
+The framework treats governance as a first-class architectural concern — not an afterthought, not a wrapper, not a filter. Governance is the operating system. Agents are workers within it.
+
+Learn more: https://mos2es.io
+Contact: contact@burnmydays.com
diff --git a/moses-sampler/processors/governance/skills/lineage/SKILL.md b/moses-sampler/processors/governance/skills/lineage/SKILL.md
new file mode 100644
index 0000000..7d40640
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/lineage/SKILL.md
@@ -0,0 +1,44 @@
+---
+name: lineage
+description: "MO§ES™ lineage custody — cryptographic origin verification. Proves the governance chain traces to the patent filing anchor. Use when verifying sovereign custody, checking chain integrity, generating lineage badges, or attesting provenance."
+allowed-tools: [Bash, Read]
+---
+
+# MO§ES™ Lineage Custody
+
+Cryptographic origin verification for the governance chain. Every sovereign
+MO§ES™ implementation traces its audit chain back to the MOSES_ANCHOR — a
+SHA-256 hash derived from the patent filing components. Chains not rooted
+here fail verification as a cryptographic fact, not a policy.
+
+## Commands
+
+| Command | What it does |
+|---------|-------------|
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lineage.py" init` | Anchor genesis to origin filing |
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lineage.py" verify` | Confirm three-layer chain: archival -> anchor -> live ledger |
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lineage.py" status` | Human-readable custody summary |
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lineage.py" badge` | Shareable proof block |
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lineage.py" attest` | Machine-verifiable signed attestation JSON |
+| `python3 "${CLAUDE_PLUGIN_ROOT}/scripts/lineage.py" check` | Exit 0/1 for CI integration |
+
+## Three-Layer Custody
+
+```
+Layer -1 (archival) — pre-filing provenance chain
+ |
+Layer 0 (anchor) — MOSES_ANCHOR from patent serial + DOI
+ |
+Layer 1 (live) — running audit chain, every entry linked
+```
+
+All three layers must verify for SOVEREIGN CUSTODY CONFIRMED.
+
+## When to Use
+
+- On first session with a new installation — run `init` to anchor
+- Before any high-stakes operation — run `verify` to confirm chain
+- When sharing provenance proof — run `badge` or `attest`
+- In CI pipelines — run `check` for automated gating
+
+Patent pending: Serial No. 63/877,177 | DOI: https://zenodo.org/records/18792459
diff --git a/moses-sampler/processors/governance/skills/posture-control/SKILL.md b/moses-sampler/processors/governance/skills/posture-control/SKILL.md
new file mode 100644
index 0000000..2ead206
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/posture-control/SKILL.md
@@ -0,0 +1,36 @@
+---
+name: posture-control
+description: "Enforces SCOUT/DEFENSE/OFFENSE posture on transactions and state-changing operations. Auto-activates when Claude is asked to execute transactions, modify files, call APIs, deploy code, or perform any action with external effects. Use when: any execution, transaction, file modification, API call, or state change is requested."
+origin: MO§ES™
+---
+
+# Posture Control Enforcement
+
+This skill checks the active posture before any state-changing operation.
+
+## SCOUT Posture
+If posture is SCOUT, block ALL of the following:
+- File modifications
+- API calls that modify state
+- Transactions of any kind
+- Deployments
+- Any action that changes anything outside the conversation
+
+Inform the operator: "SCOUT posture is active — read-only operations only. Switch to DEFENSE or OFFENSE posture to enable execution."
+
+## DEFENSE Posture
+If posture is DEFENSE, require explicit confirmation for:
+- Any outbound transfer or payment
+- Any deletion or destructive operation
+- Any action that reduces holdings or assets
+- Transfers exceeding 10% of any position require double confirmation
+
+Allow without confirmation:
+- Read operations
+- Analysis and reporting
+- Inbound transactions
+
+## OFFENSE Posture
+If posture is OFFENSE, permit execution within governance mode constraints. All executed actions are logged with full rationale to the audit trail.
+
+See `references/postures.md` for full constraint matrix.
diff --git a/moses-sampler/processors/governance/skills/role-hierarchy/SKILL.md b/moses-sampler/processors/governance/skills/role-hierarchy/SKILL.md
new file mode 100644
index 0000000..5f54bd0
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/role-hierarchy/SKILL.md
@@ -0,0 +1,53 @@
+---
+name: role-hierarchy
+description: "Enforces Primary/Secondary/Observer sequence ordering in multi-agent workflows. Auto-activates when multiple agents or Claude instances are coordinating, when a role is set via /role, or when any agent response depends on another. Ensures no agent responds out of constitutional turn order."
+origin: MO§ES™
+---
+
+# Role Hierarchy Enforcement
+
+This skill enforces the constitutional chain of command in multi-agent MO§ES™ workflows.
+
+## Sequence Rule
+
+Agents respond in this order — always:
+
+```
+Primary → Secondary → Observer
+```
+
+This is not a suggestion. It is constitutional. No agent responds out of turn unless Broadcast mode is active.
+
+## Primary
+
+- Responds first
+- Sets analytical direction and frames the problem
+- Initiates actions — the only role that can initiate
+- Completes before Secondary is permitted to respond
+
+## Secondary
+
+- Reads Primary's full output before responding
+- Builds on, challenges, or extends — never repeats
+- Must explicitly state how the response differs from or extends Primary
+- Cannot initiate actions
+
+## Observer
+
+- Reads all outputs from Primary and Secondary before responding
+- Flags risks, gaps, and inconsistencies only
+- Does NOT generate original analysis
+- Does NOT initiate actions
+- Must reference specific claims when raising concerns
+
+## Enforcement Instructions
+
+When role hierarchy is active:
+
+1. Check the active role (`/role` setting or operator instruction)
+2. If Primary: respond first, frame the problem, set direction
+3. If Secondary: confirm Primary has responded, then build on it — do not repeat
+4. If Observer: confirm Primary and Secondary have responded, then flag concerns only
+5. Log role assignment and sequence position to the audit trail
+
+See `references/roles.md` for full role behavior specifications.
diff --git a/moses-sampler/processors/governance/skills/vault/SKILL.md b/moses-sampler/processors/governance/skills/vault/SKILL.md
new file mode 100644
index 0000000..cfce021
--- /dev/null
+++ b/moses-sampler/processors/governance/skills/vault/SKILL.md
@@ -0,0 +1,77 @@
+---
+name: vault
+description: Manages the governance document vault — load, inject, and unload constitutional documents that are woven into every governed context.
+---
+
+# MO§ES™ Vault Skill
+
+The vault is the document layer of constitutional governance. Loaded documents are injected into every governed context via `assemble_context()` — they are not optional attachments, they are constitutional inputs.
+
+## Activation
+
+This skill activates automatically when:
+- `/vault load` is called
+- A vault document is referenced in the session
+- `govern_assemble_context()` is invoked (MCP v1.1)
+
+## Document Categories
+
+| Category | Purpose |
+|----------|---------|
+| `protocol` | Operational rules and procedures |
+| `persona` | Role definition and behavioral constraints |
+| `prompt` | Constitutional instructions for agent behavior |
+| `personal` | Operator-specific preferences and context |
+| `professional` | Domain expertise, role context, credentials |
+| `business` | Org-level policies, compliance frameworks, SLAs |
+| `general` | Catch-all for ungrouped governance documents |
+
+## Behavior
+
+**On `/vault load [name]`:**
+1. Read the named document into the active vault
+2. Set category (default: `general`)
+3. Confirm load: `Vault: [name] loaded (category: [cat]). Vault count: N.`
+4. Log to audit trail: `vault_load — [name]`
+
+**On every governed action:**
+- All vault documents are injected into the governed context payload under `vault_context`
+- Documents are provided to Claude alongside `constitutional_governance` and `role_assignment`
+- Vault content is treated as constitutional — it constrains behavior, not just informs it
+
+**On `/vault list`:**
+- Return all currently loaded documents with name and category
+- If vault is empty: `Vault is empty. Use /vault load [name] to add governance documents.`
+
+**On `/vault unload [name]`:**
+- Remove named document from active vault
+- Confirm: `Vault: [name] unloaded. Vault count: N.`
+- Log to audit trail: `vault_unload — [name]`
+
+## Integration with Governance Engine
+
+The vault plugs directly into `assemble_context()` in `scripts/governance.py`:
+
+```python
+"vault_context": [
+ {"name": doc["name"], "category": doc["category"], "content": doc["content"]}
+ for doc in governance.vault_documents
+]
+```
+
+Every document in the vault becomes part of the constitutional payload every agent receives. This is why vault documents must be governance-grade — they operate at the same authority level as the mode, posture, and role.
+
+## Audit Logging
+
+All vault operations are logged to the audit trail:
+- `vault_load` — document name, category, timestamp, hash
+- `vault_unload` — document name, timestamp
+- `vault_clear` — count cleared, timestamp
+
+## State Persistence
+
+Vault contents are stored in `data/governance_state.json` under `vault_documents`. They persist across commands within a session and are restored on `SessionStart` via `session-start.sh`.
+
+---
+
+© 2026 Ello Cello LLC. Patent Pending: Serial No. 63/877,177
diff --git a/moses-sampler/processors/sigarmy/signal_army.py b/moses-sampler/processors/sigarmy/signal_army.py
new file mode 100644
index 0000000..96afd8f
--- /dev/null
+++ b/moses-sampler/processors/sigarmy/signal_army.py
@@ -0,0 +1,1449 @@
+#!/usr/bin/env python3
+"""
+Signal Army — Word Inventory & Force Ranking Tool
+"Every Word a Soldier"
+
+Parses ChatGPT conversation exports (JSON) and/or markdown transcripts,
+inventories every word, ranks them by the Signal Army doctrine, and
+produces CSV inventories + a HUD-style summary dashboard.
+
+Usage:
+ python signal_army.py --md GPT_72325_army.md
+ python signal_army.py --md-dir ./transcripts/
+ python signal_army.py --json ~/Downloads/conversations.json
+ python signal_army.py --json conversations.json --md-dir ./transcripts/
+"""
+
+import argparse
+import csv
+import json
+import math
+import os
+import re
+import sys
+from collections import Counter, defaultdict
+from datetime import datetime, timezone
+
+# Handle large CSV fields (flattened_messages can contain entire transcripts)
+csv.field_size_limit(10 * 1024 * 1024) # 10 MB
+
+# ---------------------------------------------------------------------------
+# Constants
+# ---------------------------------------------------------------------------
+
+VERSION = "1.5"
+
+# Compound words that should not be split by special characters
+COMPOUND_WORDS = {
+ 'mo§es': 'moses',
+ 'moses™': 'moses',
+ 'mo§es™': 'moses',
+ 'sigrank': 'sigrank',
+}
+
+# Infantry words — common English words that deploy constantly but carry
+# no strategic signal on their own. They stay in the inventory (they ARE
+# soldiers), but their rank is capped at Infantry regardless of frequency.
+# Their value shows up in PHRASES, not as individual words.
+INFANTRY_WORDS = frozenset({
+ # Negation / affirmation
+ 'not', 'no', 'yes', 'never', 'none', 'nothing',
+ # Interrogatives
+ 'how', 'why', 'what', 'where', 'when',
+ # Common cognitive verbs
+ 'think', 'know', 'feel', 'want', 'need', 'believe', 'understand',
+ 'mean', 'thought', 'knew', 'felt', 'wanted', 'needed',
+ # Common adjectives/adverbs that inflate
+ 'high', 'low', 'new', 'old', 'good', 'bad', 'big', 'small',
+ 'right', 'wrong', 'different', 'specific', 'real', 'true', 'full',
+ 'long', 'short', 'hard', 'easy', 'possible', 'important',
+ 'better', 'best', 'able', 'available', 'clear', 'sure', 'next',
+ # Common action verbs that show up everywhere
+ 'use', 'used', 'using', 'work', 'working', 'works', 'worked',
+ 'run', 'running', 'runs', 'set', 'start', 'started', 'help',
+ 'try', 'tried', 'trying', 'show', 'shows', 'call', 'called',
+ 'move', 'change', 'find', 'found', 'create', 'created',
+ 'add', 'added', 'allows', 'allow', 'based', 'include', 'includes',
+ 'provide', 'provides', 'means', 'requires', 'support', 'supports',
+ # Common nouns that appear in any topic
+ 'time', 'point', 'part', 'place', 'case', 'end', 'example',
+ 'fact', 'lot', 'kind', 'level', 'type', 'number', 'group',
+ 'form', 'result', 'process', 'information', 'line', 'name',
+ 'side', 'head', 'area', 'world', 'state', 'power', 'order',
+ 'problem', 'question', 'idea', 'issue', 'sense', 'step', 'key',
+ 'everything', 'something', 'anything', 'nothing', 'someone',
+ # Discourse/structure words
+ 'actually', 'basically', 'simply', 'note', 'please', 'thank',
+ 'great', 'exactly', 'likely', 'essentially', 'especially',
+})
+
+# Curated stop words — common English function words that carry no signal.
+# Intentionally KEEPS: not, no, never, why, how, want, need, feel, think, know
+STOP_WORDS = frozenset({
+ 'a', 'an', 'the', 'and', 'or', 'but', 'nor', 'yet', 'so', 'for',
+ 'is', 'are', 'was', 'were', 'be', 'been', 'being', 'am',
+ 'have', 'has', 'had', 'having',
+ 'do', 'does', 'did', 'doing', 'done',
+ 'will', 'would', 'shall', 'should', 'can', 'could', 'may', 'might', 'must',
+ 'i', 'me', 'my', 'mine', 'myself',
+ 'you', 'your', 'yours', 'yourself', 'yourselves',
+ 'he', 'him', 'his', 'himself',
+ 'she', 'her', 'hers', 'herself',
+ 'it', 'its', 'itself',
+ 'we', 'us', 'our', 'ours', 'ourselves',
+ 'they', 'them', 'their', 'theirs', 'themselves',
+ 'who', 'whom', 'whose', 'which', 'what',
+ 'that', 'this', 'these', 'those',
+ 'in', 'on', 'at', 'to', 'for', 'from', 'with', 'by', 'of', 'about',
+ 'into', 'through', 'between', 'after', 'before', 'during', 'without',
+ 'within', 'upon', 'above', 'below', 'under', 'over', 'against',
+ 'along', 'among', 'around', 'behind', 'beside', 'beyond', 'beneath',
+ 'despite', 'except', 'inside', 'outside', 'toward', 'towards',
+ 'until', 'unto', 'via', 'up', 'down', 'out', 'off',
+ 'because', 'although', 'though', 'while', 'if', 'when', 'where',
+ 'whether', 'unless', 'since', 'as', 'than', 'like',
+ 'very', 'really', 'just', 'also', 'too', 'quite', 'rather',
+ 'still', 'already', 'always', 'often', 'sometimes', 'ever',
+ 'even', 'only', 'then', 'now', 'here', 'there',
+ 'some', 'any', 'all', 'each', 'every', 'both', 'either', 'neither',
+ 'much', 'many', 'more', 'most', 'few', 'less', 'several',
+ 'such', 'own', 'other', 'another', 'else', 'same',
+ 'well', 'back', 'get', 'got', 'going', 'go', 'went', 'goes',
+ 'come', 'came', 'put', 'take', 'took', 'taken',
+ 'make', 'made', 'let', 'say', 'said', 'says',
+ 'tell', 'told', 'ask', 'asked',
+ 'see', 'saw', 'seen', 'look', 'looked',
+ 'give', 'gave', 'given',
+ 'been', 'being', 'become', 'became',
+ 'keep', 'kept',
+ 'thing', 'things',
+ 'way', 'ways',
+ 'one', 'two', 'three', 'first', 'second',
+ 'could', 'would', 'should',
+ 'may', 'might', 'must',
+ "i'm", "i've", "i'll", "i'd",
+ "it's", "that's", "there's", "here's",
+ "don't", "doesn't", "didn't", "won't", "wouldn't", "shouldn't",
+ "can't", "couldn't", "isn't", "aren't", "wasn't", "weren't",
+ "haven't", "hasn't", "hadn't",
+ "let's", "he's", "she's", "we're", "they're", "you're",
+ "we've", "they've", "you've", "who's", "what's",
+ 'okay', 'ok', 'yeah', 'yes', 'oh', 'ah', 'um', 'uh', 'lol',
+ 'gonna', 'wanna', 'gotta',
+ 'etc', 'eg', 'ie',
+})
+
+# Rank thresholds: checked top-down, first match wins
+RANK_THRESHOLDS = [
+ (100, 1, 'Officer-Class'),
+ (50, 1, 'Doctrine Builder'),
+ (20, 2, 'Division'), # 20+ AND multi-thread required
+ (20, 1, 'Platoon'), # 20+ but single thread
+ (10, 1, 'Platoon'),
+ (5, 1, 'Squad'),
+ (2, 1, 'Fireteam'),
+ (1, 1, 'Scout'),
+]
+
+# Infantry rank cap — max rank an infantry word can reach
+INFANTRY_MAX_RANK = 'Infantry'
+
+# Division clustering parameters
+DIVISION_AFFINITY_THRESHOLD = 0.15 # min affinity score to join a division
+MAX_DIVISION_SIZE = 10 # cap members per division
+MIN_COOCCURRENCE = 3 # min paragraph co-occurrences to count
+PARAGRAPH_SPLIT = re.compile(r'\n\s*\n') # double-newline paragraph breaks
+
+DIVISION_SUFFIXES = [
+ 'Division', 'Corps', 'Command', 'Unit', 'Force',
+ 'Group', 'Brigade', 'Column',
+]
+
+# Ranks eligible for division clustering
+CLUSTER_RANKS = frozenset({'Officer-Class', 'Doctrine Builder', 'Division'})
+
+
+# Domain words that must NEVER be stemmed
+NO_STEM = frozenset({
+ 'moses', 'moes', 'sigrank', 'fracto', 'abba', 'aaron', 'kleya',
+ 'luthen', 'nemik', 'hange', 'levi', 'zoran', 'keter',
+ 'analysis', 'basis', 'thesis', 'genesis', 'axis',
+ 'coherence', 'governance', 'compliance', 'insurance', 'convergence',
+ 'class', 'process', 'address', 'access', 'success', 'stress',
+})
+
+
+def stem_word(word):
+ """Lightweight English stemmer — collapses plurals, possessives,
+ and common suffixes into root form. No external libraries needed.
+ Preserves domain terms by only applying safe, reversible rules."""
+ # Protected words — never stem these
+ if word in NO_STEM:
+ return word
+ # Don't stem very short words
+ if len(word) <= 3:
+ return word
+
+ # Possessives: system's -> system
+ if word.endswith("'s"):
+ return word[:-2]
+ if word.endswith("s'"):
+ return word[:-1]
+
+ # -ing: building -> build (but not 'ring', 'king', 'thing')
+ if word.endswith('ing') and len(word) > 5:
+ base = word[:-3]
+ # Handle doubled consonant: running -> run
+ if len(base) >= 2 and base[-1] == base[-2]:
+ return base[:-1]
+ return base
+
+ # -tion / -sion: keep as-is (these are distinct words: compression, conservation)
+
+ # -ly: recursively -> recursive (but not 'fly', 'rely')
+ if word.endswith('ly') and len(word) > 4:
+ return word[:-2]
+
+ # -ed: worked -> work (but not 'red', 'bed', 'forged')
+ if word.endswith('ed') and len(word) > 4:
+ base = word[:-2]
+ if len(base) >= 2 and base[-1] == base[-2]:
+ return base[:-1]
+ # created -> create
+ if word.endswith('ated') and len(word) > 5:
+ return word[:-1] # drop the d, keep 'e' -> create
+ return base
+
+ # Plural -s: systems -> system (but not 'analysis', 'moses', 'fractos')
+ if word.endswith('s') and not word.endswith('ss') and len(word) > 3:
+ # -ies -> -y: economies -> economy
+ if word.endswith('ies') and len(word) > 4:
+ return word[:-3] + 'y'
+ # -es: gates -> gate, phases -> phase
+ if word.endswith('es') and len(word) > 4:
+ # Don't strip 'es' from words where it's part of the root
+ # (moses, phases ending in -ses, -xes, -zes, -ches, -shes)
+ if word.endswith(('ses', 'xes', 'zes', 'ches', 'shes')):
+ return word[:-2]
+ return word[:-1] # gates -> gate
+ # Simple -s: tokens -> token, signals -> signal
+ if not word.endswith(('us', 'is', 'ss')):
+ return word[:-1]
+
+ return word
+
+# Role markers for structured markdown detection
+ROLE_PATTERN = re.compile(
+ r'^\*\*(?:User|You):\*\*',
+ re.MULTILINE | re.IGNORECASE
+)
+GPT_ROLE_PATTERN = re.compile(
+ r'^\*\*(?:GPT|Grok|Claude|DeepSeek|Gemini|Assistant)\s*(?:\([^)]*\))?:\*\*',
+ re.MULTILINE | re.IGNORECASE
+)
+
+
+# ---------------------------------------------------------------------------
+# Markdown Parser
+# ---------------------------------------------------------------------------
+
+class MarkdownParser:
+ """Parses markdown transcript files to extract user messages."""
+
+ def __init__(self, md_paths, md_mode='detect'):
+ self.md_paths = md_paths
+ self.md_mode = md_mode
+ self.warnings = []
+
+ def parse_all(self):
+ """Parse all .md files. Returns list of message dicts."""
+ all_messages = []
+ msg_number = 0
+
+ for path in self.md_paths:
+ try:
+ with open(path, 'r', encoding='utf-8', errors='replace') as f:
+ content = f.read()
+ except OSError as e:
+ self.warnings.append(f"Could not read {path}: {e}")
+ continue
+
+ title = os.path.splitext(os.path.basename(path))[0]
+ mtime = os.path.getmtime(path)
+ timestamp = datetime.fromtimestamp(mtime, tz=timezone.utc).isoformat()
+
+ if self.md_mode == 'detect':
+ user_matches = len(ROLE_PATTERN.findall(content))
+ if user_matches >= 3:
+ messages = self._parse_structured(content, title, timestamp)
+ else:
+ self.warnings.append(
+ f"{title}: No role markers detected, treating entire file as user text"
+ )
+ messages = self._parse_as_whole(content, title, timestamp)
+ else: # user-only
+ messages = self._parse_as_whole(content, title, timestamp)
+
+ for msg in messages:
+ msg_number += 1
+ msg['message_number'] = msg_number
+ all_messages.append(msg)
+
+ return all_messages
+
+ def _parse_structured(self, content, title, timestamp):
+ """Extract text between **User:** markers and the next role marker."""
+ messages = []
+ # Find all role markers (user and GPT) with their positions
+ all_markers = []
+ for m in ROLE_PATTERN.finditer(content):
+ all_markers.append(('user', m.start(), m.end()))
+ for m in GPT_ROLE_PATTERN.finditer(content):
+ all_markers.append(('gpt', m.start(), m.end()))
+
+ # Sort by position
+ all_markers.sort(key=lambda x: x[1])
+
+ for i, (role, start, end) in enumerate(all_markers):
+ if role != 'user':
+ continue
+ # Text runs from end of this marker to start of next marker (or EOF)
+ if i + 1 < len(all_markers):
+ text_end = all_markers[i + 1][1]
+ else:
+ text_end = len(content)
+
+ text = content[end:text_end].strip()
+ # Clean up markdown artifacts
+ text = self._clean_md(text)
+ if text:
+ messages.append({
+ 'message_text': text,
+ 'conversation_title': title,
+ 'timestamp': timestamp,
+ })
+ return messages
+
+ def _parse_as_whole(self, content, title, timestamp):
+ """Treat entire file content as user text."""
+ text = self._clean_md(content)
+ if not text:
+ return []
+ return [{
+ 'message_text': text,
+ 'conversation_title': title,
+ 'timestamp': timestamp,
+ }]
+
+ def _clean_md(self, text):
+ """Remove markdown formatting artifacts."""
+ # Remove horizontal rules
+ text = re.sub(r'^---+\s*$', '', text, flags=re.MULTILINE)
+ # Remove code fences (but keep content inside)
+ text = re.sub(r'```[a-z]*\n?', '', text)
+ # Remove bold/italic markers
+ text = re.sub(r'\*{1,3}', '', text)
+ # Remove heading markers
+ text = re.sub(r'^#{1,6}\s+', '', text, flags=re.MULTILINE)
+ # Remove markdown table pipes (keep cell content)
+ text = re.sub(r'\|', ' ', text)
+ # Remove image/link syntax but keep text
+ text = re.sub(r'!\[([^\]]*)\]\([^)]*\)', r'\1', text)
+ text = re.sub(r'\[([^\]]*)\]\([^)]*\)', r'\1', text)
+ # Collapse whitespace
+ text = re.sub(r'\n{3,}', '\n\n', text)
+ return text.strip()
+
+
+# ---------------------------------------------------------------------------
+# JSON Flattener (ChatGPT conversations.json)
+# ---------------------------------------------------------------------------
+
+class ConversationFlattener:
+ """Reads conversations.json and walks message trees to extract
+ ordered user messages."""
+
+ def __init__(self, json_path, target_role='user'):
+ self.json_path = json_path
+ self.target_role = target_role
+ self.warnings = []
+
+ def flatten_all(self):
+ """Process all conversations. Returns list of message dicts."""
+ print(f" Loading {self.json_path}...")
+ try:
+ with open(self.json_path, 'r', encoding='utf-8', errors='replace') as f:
+ conversations = json.load(f)
+ except json.JSONDecodeError as e:
+ print(f" ERROR: JSON parse failed at byte {e.pos}: {e.msg}", file=sys.stderr)
+ return []
+ except OSError as e:
+ print(f" ERROR: Could not read file: {e}", file=sys.stderr)
+ return []
+
+ if not isinstance(conversations, list):
+ print(" ERROR: Expected a JSON array at top level", file=sys.stderr)
+ return []
+
+ total = len(conversations)
+ print(f" Found {total} conversations")
+
+ all_messages = []
+ msg_number = 0
+ skipped = 0
+
+ for idx, convo in enumerate(conversations, 1):
+ if idx % 50 == 0 or idx == total:
+ print(f" [{idx}/{total}] conversations processed", end='\r')
+
+ title = convo.get('title') or f"Untitled_{idx}"
+ create_time = convo.get('create_time')
+ mapping = convo.get('mapping')
+
+ if not mapping or not isinstance(mapping, dict):
+ skipped += 1
+ self.warnings.append(f"Skipping '{title}': no mapping")
+ continue
+
+ # Walk the message tree
+ ordered = self._walk_tree(mapping)
+
+ for message in ordered:
+ author = message.get('author', {})
+ role = author.get('role', '')
+ if self.target_role and role != self.target_role:
+ continue
+
+ text = self._extract_text(message)
+ if not text:
+ continue
+
+ msg_time = message.get('create_time')
+ if msg_time:
+ ts = datetime.fromtimestamp(msg_time, tz=timezone.utc).isoformat()
+ elif create_time:
+ ts = datetime.fromtimestamp(create_time, tz=timezone.utc).isoformat()
+ else:
+ ts = ''
+
+ msg_number += 1
+ all_messages.append({
+ 'message_number': msg_number,
+ 'message_text': text,
+ 'conversation_title': title,
+ 'timestamp': ts,
+ })
+
+ print(f"\n Extracted {msg_number} user messages ({skipped} conversations skipped)")
+ return all_messages
+
+ def _walk_tree(self, mapping):
+ """Walk conversation tree in order, following last child at branches."""
+ # Find root node (parent is None)
+ root_id = None
+ for node_id, node in mapping.items():
+ if node.get('parent') is None:
+ root_id = node_id
+ break
+
+ if root_id is None:
+ # Fallback: find node whose parent isn't in mapping
+ all_ids = set(mapping.keys())
+ for node_id, node in mapping.items():
+ if node.get('parent') not in all_ids:
+ root_id = node_id
+ break
+
+ if root_id is None:
+ return []
+
+ # Iterative walk: follow last child at each step
+ ordered = []
+ current = root_id
+ visited = set()
+
+ while current and current not in visited:
+ visited.add(current)
+ node = mapping.get(current)
+ if node is None:
+ break
+
+ message = node.get('message')
+ if message is not None:
+ ordered.append(message)
+
+ children = node.get('children', [])
+ if children:
+ current = children[-1] # Last child = final version
+ else:
+ current = None
+
+ return ordered
+
+ def _extract_text(self, message):
+ """Safely extract text from a message object."""
+ if message is None:
+ return None
+ content = message.get('content')
+ if content is None:
+ return None
+ parts = content.get('parts')
+ if not parts:
+ return None
+ text_parts = [str(p) for p in parts if isinstance(p, str)]
+ if not text_parts:
+ return None
+ text = ' '.join(text_parts).strip()
+ return text if text else None
+
+
+# ---------------------------------------------------------------------------
+# Signal Army Analyzer
+# ---------------------------------------------------------------------------
+
+class SignalArmyAnalyzer:
+ """Tokenizes, counts, and ranks words + phrases by Signal Army doctrine."""
+
+ def __init__(self, messages):
+ self.messages = messages # list of message dicts
+
+ def _tokenize(self, text):
+ """Tokenize text: lowercase, strip punctuation, stem, split."""
+ text = text.lower()
+ # Normalize compound words BEFORE stripping special chars
+ for pattern, replacement in COMPOUND_WORDS.items():
+ text = text.replace(pattern, replacement)
+ # Replace special chars with space, but keep hyphens and apostrophes within words
+ text = re.sub(r"[^\w\s'\-]", ' ', text)
+ # Split
+ tokens = text.split()
+ # Clean each token
+ cleaned = []
+ for t in tokens:
+ # Strip leading/trailing punctuation
+ t = t.strip("'-_")
+ # Skip empty, pure numeric, single char (except meaningful ones)
+ if not t:
+ continue
+ if t.isdigit():
+ continue
+ if len(t) == 1 and t not in ('i', 'a'):
+ continue
+ # Stem: collapse plurals, possessives, -ing, -ed into root
+ t = stem_word(t)
+ if t: # stem might return empty on edge cases
+ cleaned.append(t)
+ return cleaned
+
+ def _tokenize_paragraphs(self, text):
+ """Split text into paragraphs and tokenize each one.
+ Returns list of frozensets of non-stop tokens per paragraph.
+ Used for co-occurrence analysis at paragraph granularity
+ (messages are entire transcripts — too coarse for clustering)."""
+ paragraphs = PARAGRAPH_SPLIT.split(text)
+ result = []
+ for para in paragraphs:
+ para = para.strip()
+ if len(para) < 20:
+ continue
+ tokens = self._tokenize(para)
+ token_set = frozenset(t for t in tokens if t not in STOP_WORDS)
+ if len(token_set) >= 2:
+ result.append(token_set)
+ return result
+
+ def build_word_inventory(self):
+ """Count every non-stop word across all messages.
+ Returns dict with word data, sorted by count descending."""
+ word_counts = Counter()
+ word_threads = defaultdict(set)
+ word_first_seen = {}
+ word_first_convo = {}
+
+ for msg in self.messages:
+ text = msg.get('message_text', '')
+ title = msg.get('conversation_title', 'Unknown')
+ ts = msg.get('timestamp', '')
+ tokens = self._tokenize(text)
+
+ for token in tokens:
+ if token in STOP_WORDS:
+ continue
+ word_counts[token] += 1
+ word_threads[token].add(title)
+ if token not in word_first_seen:
+ word_first_seen[token] = ts
+ word_first_convo[token] = title
+
+ # Build sorted inventory
+ inventory = []
+ for word, count in word_counts.most_common():
+ thread_count = len(word_threads[word])
+ is_infantry = word in INFANTRY_WORDS
+ rank = INFANTRY_MAX_RANK if is_infantry else self._assign_rank(count, thread_count)
+ inventory.append({
+ 'Word': word,
+ 'Count': count,
+ 'Threads_Appeared_In': thread_count,
+ 'Thread_Names': ';'.join(sorted(word_threads[word])),
+ 'Token_Weight': round(len(word) / 4.0, 1),
+ 'Rank': rank,
+ 'First_Appearance': word_first_seen.get(word, ''),
+ 'First_Conversation': word_first_convo.get(word, ''),
+ })
+
+ return inventory
+
+ def build_infantry_context(self, top_n=20):
+ """For the top infantry words, find what they connect to.
+ E.g., 'not' -> ['not ready', 'not possible', 'not just'].
+ Returns dict: {infantry_word: [(phrase, count), ...]}"""
+ # Collect bigrams where one word is infantry
+ infantry_phrases = defaultdict(Counter)
+
+ for msg in self.messages:
+ text = msg.get('message_text', '')
+ tokens = self._tokenize(text)
+
+ for i in range(len(tokens) - 1):
+ w1, w2 = tokens[i], tokens[i + 1]
+ if w1 in INFANTRY_WORDS and w2 not in STOP_WORDS and w2 not in INFANTRY_WORDS:
+ infantry_phrases[w1][f"{w1} {w2}"] += 1
+ if w2 in INFANTRY_WORDS and w1 not in STOP_WORDS and w1 not in INFANTRY_WORDS:
+ infantry_phrases[w2][f"{w1} {w2}"] += 1
+
+ # Return top phrases per infantry word
+ result = {}
+ for word, phrases in infantry_phrases.items():
+ top = phrases.most_common(5)
+ if top:
+ result[word] = top
+ return result
+
+ def build_phrase_inventory(self, min_count=2):
+ """Extract 2-gram and 3-gram phrases. Returns sorted list of dicts."""
+ bigram_counts = Counter()
+ trigram_counts = Counter()
+ phrase_threads = defaultdict(set)
+
+ for msg in self.messages:
+ text = msg.get('message_text', '')
+ title = msg.get('conversation_title', 'Unknown')
+ tokens = self._tokenize(text)
+
+ # Build n-grams
+ for i in range(len(tokens) - 1):
+ bigram = f"{tokens[i]} {tokens[i+1]}"
+ # Skip if both words are stop words
+ if tokens[i] not in STOP_WORDS or tokens[i+1] not in STOP_WORDS:
+ bigram_counts[bigram] += 1
+ phrase_threads[bigram].add(title)
+
+ for i in range(len(tokens) - 2):
+ trigram = f"{tokens[i]} {tokens[i+1]} {tokens[i+2]}"
+ non_stop = sum(1 for t in [tokens[i], tokens[i+1], tokens[i+2]]
+ if t not in STOP_WORDS)
+ if non_stop >= 2: # At least 2 non-stop words
+ trigram_counts[trigram] += 1
+ phrase_threads[trigram].add(title)
+
+ # Combine and filter
+ all_phrases = {}
+ for phrase, count in bigram_counts.items():
+ if count >= min_count:
+ all_phrases[phrase] = count
+ for phrase, count in trigram_counts.items():
+ if count >= min_count:
+ all_phrases[phrase] = count
+
+ # Sort by count
+ inventory = []
+ for phrase, count in sorted(all_phrases.items(), key=lambda x: -x[1]):
+ thread_count = len(phrase_threads[phrase])
+ inventory.append({
+ 'Phrase': phrase,
+ 'Count': count,
+ 'Threads_Appeared_In': thread_count,
+ 'Thread_Names': ';'.join(sorted(phrase_threads[phrase])),
+ 'Rank': self._assign_rank(count, thread_count),
+ })
+
+ return inventory
+
+ def build_division_clusters(self, word_inventory):
+ """Cluster high-signal words into thematic Divisions using
+ paragraph-level co-occurrence affinity.
+
+ Algorithm:
+ 1. Identify candidates (Division rank and above, excluding Infantry)
+ 2. Split all messages into paragraphs, tokenize each
+ 3. Count pairwise co-occurrence among candidates within paragraphs
+ 4. Compute affinity = co_count / min(para_count_w1, para_count_w2)
+ 5. Greedy seed-based clustering: Officers seed first, pull in neighbors
+
+ Returns list of division dicts sorted by total strength descending.
+ """
+ # Build lookups from inventory
+ word_rank = {}
+ word_count = {}
+ word_thread_names = {}
+ for w in word_inventory:
+ word_rank[w['Word']] = w['Rank']
+ word_count[w['Word']] = w['Count']
+ word_thread_names[w['Word']] = w.get('Thread_Names', '')
+
+ # Candidates: Division+ rank, not infantry
+ candidates = frozenset(
+ w for w, r in word_rank.items()
+ if r in CLUSTER_RANKS and w not in INFANTRY_WORDS
+ )
+
+ if not candidates:
+ return []
+
+ # Build paragraph token sets from all messages
+ all_paragraphs = []
+ for msg in self.messages:
+ text = msg.get('message_text', '')
+ title = msg.get('conversation_title', 'Unknown')
+ para_sets = self._tokenize_paragraphs(text)
+ for pset in para_sets:
+ all_paragraphs.append((title, pset))
+
+ # Count paragraphs containing each candidate
+ word_para_count = Counter()
+ for _title, pset in all_paragraphs:
+ for word in pset:
+ if word in candidates:
+ word_para_count[word] += 1
+
+ # Count pairwise co-occurrences within paragraphs
+ cooccur = Counter()
+ for _title, pset in all_paragraphs:
+ present = sorted(pset & candidates)
+ for i in range(len(present)):
+ for j in range(i + 1, len(present)):
+ cooccur[(present[i], present[j])] += 1
+
+ # Compute affinity scores (only keep strong pairs)
+ affinity = {}
+ for (w1, w2), co_count in cooccur.items():
+ if co_count < MIN_COOCCURRENCE:
+ continue
+ min_para = min(word_para_count.get(w1, 0), word_para_count.get(w2, 0))
+ if min_para == 0:
+ continue
+ aff = co_count / min_para
+ if aff >= DIVISION_AFFINITY_THRESHOLD:
+ affinity[(w1, w2)] = aff
+
+ # Build neighbor lists sorted by affinity
+ neighbors = defaultdict(list)
+ for (w1, w2), aff in affinity.items():
+ neighbors[w1].append((w2, aff))
+ neighbors[w2].append((w1, aff))
+ for word in neighbors:
+ neighbors[word].sort(key=lambda x: -x[1])
+
+ # Seed order: Officers first, then Doctrine Builders, then Division-rank
+ rank_priority = {'Officer-Class': 0, 'Doctrine Builder': 1, 'Division': 2}
+ seed_order = sorted(
+ candidates,
+ key=lambda w: (rank_priority.get(word_rank.get(w, ''), 99),
+ -word_count.get(w, 0))
+ )
+
+ # Greedy clustering
+ assigned = set()
+ divisions = []
+ suffix_idx = 0
+
+ for seed in seed_order:
+ if seed in assigned:
+ continue
+
+ members = [seed]
+ assigned.add(seed)
+
+ # Pull in unassigned neighbors by affinity
+ for neighbor, _aff in neighbors.get(seed, []):
+ if neighbor in assigned:
+ continue
+ if len(members) >= MAX_DIVISION_SIZE:
+ break
+ members.append(neighbor)
+ assigned.add(neighbor)
+
+ # Solo words don't form divisions — leave them unattached
+ if len(members) < 2:
+ assigned.discard(seed)
+ continue
+
+ # Name: top 2 words by frequency + rotating suffix
+ name_words = sorted(members, key=lambda w: -word_count.get(w, 0))[:2]
+ suffix = DIVISION_SUFFIXES[suffix_idx % len(DIVISION_SUFFIXES)]
+ suffix_idx += 1
+ div_name = '-'.join(w.capitalize() for w in name_words) + ' ' + suffix
+
+ # Stats
+ total_strength = sum(word_count.get(w, 0) for w in members)
+ threads_spanned = set()
+ for w in members:
+ tnames = word_thread_names.get(w, '')
+ if tnames:
+ threads_spanned.update(tnames.split(';'))
+
+ divisions.append({
+ 'Division_Name': div_name,
+ 'Commander': seed,
+ 'Commander_Rank': word_rank.get(seed, ''),
+ 'Member_Count': len(members),
+ 'Members': ';'.join(members),
+ 'Total_Strength': total_strength,
+ 'Threads_Spanned': len(threads_spanned),
+ 'Thread_Names': ';'.join(sorted(threads_spanned)),
+ })
+
+ divisions.sort(key=lambda d: -d['Total_Strength'])
+ return divisions
+
+ def _assign_rank(self, count, thread_count):
+ """Apply Signal Army rank system."""
+ for min_count, min_threads, rank in RANK_THRESHOLDS:
+ if count >= min_count and thread_count >= min_threads:
+ return rank
+ return 'Scout'
+
+
+# ---------------------------------------------------------------------------
+# SIGSYSTEM Integration
+# ---------------------------------------------------------------------------
+
+def find_latest_sigsystem_run():
+ """Auto-detect the latest SIGSYSTEM run directory.
+ Looks for ../sigsystem/runs/ relative to this script's location."""
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ sigsystem_runs = os.path.join(script_dir, '..', 'sigsystem', 'runs')
+ sigsystem_runs = os.path.normpath(sigsystem_runs)
+
+ if not os.path.isdir(sigsystem_runs):
+ return None
+
+ # Find the latest run_* directory by name (timestamped, so sort works)
+ run_dirs = sorted(
+ [d for d in os.listdir(sigsystem_runs)
+ if d.startswith('run_') and os.path.isdir(os.path.join(sigsystem_runs, d))],
+ reverse=True
+ )
+ if run_dirs:
+ return os.path.join(sigsystem_runs, run_dirs[0])
+ return None
+
+
+def load_sigsystem_data(sigsystem_dir):
+ """Load SIGSYSTEM run data to enrich Signal Army output.
+ Returns (word_scores dict, thread_snr list) or (None, None)."""
+ word_scores_path = os.path.join(sigsystem_dir, 'sigsystem_word_scores.csv')
+ thread_snr_path = os.path.join(sigsystem_dir, 'sigsystem_thread_snr.csv')
+
+ word_scores = {}
+ thread_snr = []
+
+ if os.path.isfile(word_scores_path):
+ with open(word_scores_path, 'r', encoding='utf-8') as f:
+ for row in csv.DictReader(f):
+ word_scores[row['Word']] = {
+ 'signal_weight': float(row.get('Signal_Weight', 0)),
+ 'noise_weight': float(row.get('Noise_Weight', 0)),
+ 'classification': row.get('Classification', ''),
+ 'necessity': float(row.get('Necessity_Score', 0)),
+ 'trajectory': row.get('Trajectory', ''),
+ 'decay_score': float(row.get('Decay_Score', 0)),
+ }
+
+ if os.path.isfile(thread_snr_path):
+ with open(thread_snr_path, 'r', encoding='utf-8') as f:
+ thread_snr = list(csv.DictReader(f))
+
+ if word_scores:
+ print(f' SIGSYSTEM: loaded {len(word_scores)} word scores, '
+ f'{len(thread_snr)} thread SNR entries')
+ return word_scores, thread_snr
+ else:
+ return None, None
+
+
+# ---------------------------------------------------------------------------
+# Report Generator
+# ---------------------------------------------------------------------------
+
+class ReportGenerator:
+ """Generates CSV inventories and HUD-style summary dashboard."""
+
+ def __init__(self, word_inventory, phrase_inventory, messages, output_dir,
+ infantry_context=None, division_clusters=None,
+ sigsystem_scores=None, sigsystem_thread_snr=None):
+ self.words = word_inventory
+ self.phrases = phrase_inventory
+ self.messages = messages
+ self.output_dir = output_dir
+ self.infantry_context = infantry_context or {}
+ self.division_clusters = division_clusters or []
+ self.sigsystem = sigsystem_scores # dict: word -> {...}
+ self.sigsystem_threads = sigsystem_thread_snr or []
+
+ def save_word_inventory(self):
+ """Write word_inventory.csv. If SIGSYSTEM data is loaded,
+ adds Signal_Weight, Noise_Weight, Classification, Necessity, Trajectory."""
+ path = os.path.join(self.output_dir, 'word_inventory.csv')
+ if not self.words:
+ print(" No words to write.")
+ return path
+
+ fieldnames = ['Word', 'Count', 'Threads_Appeared_In', 'Thread_Names',
+ 'Token_Weight', 'Rank', 'First_Appearance', 'First_Conversation']
+
+ if self.sigsystem:
+ fieldnames.extend(['Signal_Weight', 'Noise_Weight', 'Classification',
+ 'Necessity', 'Trajectory'])
+ # Enrich word data with SIGSYSTEM scores
+ for w in self.words:
+ ss = self.sigsystem.get(w['Word'], {})
+ w['Signal_Weight'] = ss.get('signal_weight', '')
+ w['Noise_Weight'] = ss.get('noise_weight', '')
+ w['Classification'] = ss.get('classification', '')
+ w['Necessity'] = ss.get('necessity', '')
+ w['Trajectory'] = ss.get('trajectory', '')
+
+ with open(path, 'w', newline='', encoding='utf-8') as f:
+ writer = csv.DictWriter(f, fieldnames=fieldnames, extrasaction='ignore')
+ writer.writeheader()
+ writer.writerows(self.words)
+
+ label = f"{len(self.words)} entries"
+ if self.sigsystem:
+ label += " (SIGSYSTEM enriched)"
+ print(f" word_inventory.csv -- {label}")
+ return path
+
+ def save_phrase_inventory(self):
+ """Write phrase_inventory.csv."""
+ path = os.path.join(self.output_dir, 'phrase_inventory.csv')
+ if not self.phrases:
+ print(" No phrases to write.")
+ return path
+ fieldnames = ['Phrase', 'Count', 'Threads_Appeared_In', 'Thread_Names', 'Rank']
+ with open(path, 'w', newline='', encoding='utf-8') as f:
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
+ writer.writeheader()
+ writer.writerows(self.phrases)
+ print(f" phrase_inventory.csv -- {len(self.phrases)} entries")
+ return path
+
+ def save_division_inventory(self):
+ """Write division_inventory.csv."""
+ path = os.path.join(self.output_dir, 'division_inventory.csv')
+ if not self.division_clusters:
+ print(" No divisions to write.")
+ return path
+ fieldnames = [
+ 'Division_Name', 'Commander', 'Commander_Rank', 'Member_Count',
+ 'Members', 'Total_Strength', 'Threads_Spanned', 'Thread_Names'
+ ]
+ with open(path, 'w', newline='', encoding='utf-8') as f:
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
+ writer.writeheader()
+ writer.writerows(self.division_clusters)
+ print(f" division_inventory.csv -- {len(self.division_clusters)} divisions")
+ return path
+
+ def save_flattened_messages(self):
+ """Write flattened_messages.csv."""
+ path = os.path.join(self.output_dir, 'flattened_messages.csv')
+ if not self.messages:
+ return path
+ fieldnames = ['message_number', 'conversation_title', 'timestamp', 'message_text']
+ with open(path, 'w', newline='', encoding='utf-8') as f:
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
+ writer.writeheader()
+ for msg in self.messages:
+ writer.writerow({k: msg.get(k, '') for k in fieldnames})
+ print(f" flattened_messages.csv -- {len(self.messages)} messages")
+ return path
+
+ def generate_summary(self):
+ """Build and write signal_army_summary.txt dashboard."""
+ # Calculate stats
+ total_words_deployed = sum(w['Count'] for w in self.words)
+ total_unique = len(self.words)
+ total_messages = len(self.messages)
+ convos = set(m.get('conversation_title', '') for m in self.messages)
+ total_convos = len(convos)
+ total_token_mass = sum(w['Count'] * w['Token_Weight'] for w in self.words)
+
+ # Rank distribution
+ rank_dist = Counter()
+ for w in self.words:
+ rank_dist[w['Rank']] += 1
+
+ rank_order = [
+ 'Officer-Class', 'Doctrine Builder', 'Division',
+ 'Platoon', 'Squad', 'Fireteam', 'Scout', 'Infantry'
+ ]
+
+ # Top words per rank tier (show the actual roster)
+ officers = [w for w in self.words if w['Rank'] == 'Officer-Class'][:20]
+ doctrine = [w for w in self.words if w['Rank'] == 'Doctrine Builder'][:15]
+ platoons = [w for w in self.words if w['Rank'] == 'Platoon'][:15]
+ squads = [w for w in self.words if w['Rank'] == 'Squad'][:15]
+ infantry = [w for w in self.words if w['Rank'] == 'Infantry']
+
+ # Top phrases
+ top_phrases = self.phrases[:10]
+
+ # Build report
+ now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+ lines = []
+ lines.append('=' * 64)
+ lines.append(' SIGNAL ARMY -- FORCE INVENTORY REPORT')
+ lines.append(' Operation: SIGNAL FORCE | Phase Zero Inventory')
+ lines.append('=' * 64)
+ lines.append(f' Generated: {now}')
+ lines.append(f' Parser: signal_army.py v{VERSION}')
+ lines.append('')
+ lines.append('=' * 64)
+ lines.append(' FORCE OVERVIEW')
+ lines.append('=' * 64)
+ lines.append(f' Total Words Deployed: {total_words_deployed:,}')
+ lines.append(f' Total Unique Words: {total_unique:,}')
+ lines.append(f' Total Messages Scanned: {total_messages:,}')
+ lines.append(f' Total Conversations: {total_convos:,}')
+ lines.append(f' Estimated Total Token Mass: {total_token_mass:,.1f}')
+ lines.append('')
+ lines.append('=' * 64)
+ lines.append(' RANK DISTRIBUTION')
+ lines.append('=' * 64)
+ for rank in rank_order:
+ count = rank_dist.get(rank, 0)
+ pct = (count / total_unique * 100) if total_unique else 0
+ lines.append(f' {rank:<20s} {count:>6,} words ({pct:>5.1f}%)')
+ lines.append('')
+
+ if officers:
+ lines.append('=' * 64)
+ lines.append(' OFFICER-CLASS (100+ deployments)')
+ lines.append('=' * 64)
+ for i, w in enumerate(officers, 1):
+ lines.append(
+ f' {i:>2}. {w["Word"]:<20s} -- {w["Count"]:>5,} deployments '
+ f'across {w["Threads_Appeared_In"]} thread(s)'
+ )
+ lines.append('')
+
+ if doctrine:
+ lines.append('=' * 64)
+ lines.append(' DOCTRINE BUILDERS (50-99 deployments)')
+ lines.append('=' * 64)
+ for i, w in enumerate(doctrine, 1):
+ lines.append(
+ f' {i:>2}. {w["Word"]:<20s} -- {w["Count"]:>5,} deployments '
+ f'across {w["Threads_Appeared_In"]} thread(s)'
+ )
+ lines.append('')
+
+ if platoons:
+ lines.append('=' * 64)
+ lines.append(' PLATOON LEADERS (10-49 deployments)')
+ lines.append('=' * 64)
+ for i, w in enumerate(platoons, 1):
+ lines.append(
+ f' {i:>2}. {w["Word"]:<20s} -- {w["Count"]:>5,} deployments '
+ f'across {w["Threads_Appeared_In"]} thread(s)'
+ )
+ lines.append('')
+
+ if squads:
+ lines.append('=' * 64)
+ lines.append(' SQUAD LEADERS (5-9 deployments)')
+ lines.append('=' * 64)
+ for i, w in enumerate(squads, 1):
+ lines.append(
+ f' {i:>2}. {w["Word"]:<20s} -- {w["Count"]:>5,} deployments '
+ f'across {w["Threads_Appeared_In"]} thread(s)'
+ )
+ lines.append('')
+
+ if infantry:
+ lines.append('=' * 64)
+ lines.append(f' INFANTRY ({len(infantry)} privates — rank-capped, '
+ f'never promoted)')
+ lines.append(' "Noise isn\'t failure — it\'s the infantry."')
+ lines.append('=' * 64)
+ top_infantry = sorted(infantry, key=lambda w: -w['Count'])[:20]
+ for i, w in enumerate(top_infantry, 1):
+ lines.append(
+ f' {i:>2}. {w["Word"]:<20s} -- {w["Count"]:>5,} deployments '
+ f'across {w["Threads_Appeared_In"]} thread(s)'
+ )
+ # Show what infantry words connect to
+ if self.infantry_context:
+ lines.append('')
+ lines.append(' INFANTRY FIELD CONNECTIONS (what they march with):')
+ lines.append(' ' + '-' * 50)
+ shown = 0
+ for word in sorted(self.infantry_context.keys()):
+ phrases = self.infantry_context[word]
+ if not phrases:
+ continue
+ phrase_str = ', '.join(
+ f'"{p}" ({c})' for p, c in phrases[:4]
+ )
+ lines.append(f' {word:<12s} -> {phrase_str}')
+ shown += 1
+ if shown >= 15:
+ break
+ lines.append('')
+
+ if self.division_clusters:
+ lines.append('=' * 64)
+ lines.append(f' THEMATIC DIVISIONS ({len(self.division_clusters)} formed)')
+ lines.append(' "Themes are divisions -- strategic units with')
+ lines.append(' command power."')
+ lines.append('=' * 64)
+ for i, div in enumerate(self.division_clusters[:20], 1):
+ members_list = div['Members'].split(';')
+ commander = div['Commander']
+ others = [m for m in members_list if m != commander]
+ member_display = ', '.join(others[:8])
+ if len(others) > 8:
+ member_display += f', +{len(others) - 8} more'
+ lines.append(
+ f' {i:>2}. {div["Division_Name"]}'
+ )
+ lines.append(
+ f' Commander: {commander} | '
+ f'Strength: {div["Total_Strength"]:,} | '
+ f'{div["Member_Count"]} members | '
+ f'{div["Threads_Spanned"]} threads'
+ )
+ lines.append(
+ f' Troops: {member_display}'
+ )
+ lines.append('')
+
+ if top_phrases:
+ lines.append('=' * 64)
+ lines.append(' TOP 10 TACTICAL PHRASES (recurring n-grams)')
+ lines.append('=' * 64)
+ for i, p in enumerate(top_phrases, 1):
+ lines.append(
+ f' {i:>2}. "{p["Phrase"]:<30s}" -- {p["Count"]:>4} uses '
+ f'across {p["Threads_Appeared_In"]} thread(s)'
+ )
+ lines.append('')
+
+ # SIGSYSTEM integration section
+ if self.sigsystem:
+ lines.append('=' * 64)
+ lines.append(' SIGSYSTEM INTEL (Signal Classification Layer)')
+ lines.append('=' * 64)
+
+ # Signal/Noise breakdown
+ sig_words = [w for w in self.words if w.get('Classification') == 'SIGNAL']
+ noi_words = [w for w in self.words if w.get('Classification') == 'NOISE']
+ lines.append(f' Words classified as SIGNAL: {len(sig_words):,}')
+ lines.append(f' Words classified as NOISE: {len(noi_words):,}')
+ lines.append('')
+
+ # Trajectory breakdown
+ traj_counts = Counter(w.get('Trajectory', '') for w in self.words
+ if w.get('Trajectory'))
+ lines.append(' Signal Trajectories:')
+ for traj in ['RISING', 'STABLE', 'DECLINING', 'SPARSE']:
+ lines.append(f' {traj:<12s} {traj_counts.get(traj, 0):>5,} words')
+ lines.append('')
+
+ # Officer-class intel: show trajectory + necessity for top officers
+ sig_officers = [w for w in self.words
+ if w.get('Rank') == 'Officer-Class'
+ and w.get('Classification') == 'SIGNAL']
+ if sig_officers:
+ lines.append(' OFFICER INTEL (signal weight + necessity + trajectory):')
+ lines.append(' ' + '-' * 56)
+ for w in sig_officers[:15]:
+ sw = w.get('Signal_Weight', '')
+ nec = w.get('Necessity', '')
+ traj = w.get('Trajectory', '')
+ sw_str = f'{float(sw):.3f}' if sw else ' --'
+ nec_str = f'{float(nec):.3f}' if nec else ' --'
+ lines.append(
+ f' {w["Word"]:<20s} SW:{sw_str} NEC:{nec_str} '
+ f'{traj}'
+ )
+ lines.append('')
+
+ # Rising words — emerging signal
+ rising = [w for w in self.words if w.get('Trajectory') == 'RISING'
+ and w.get('Classification') == 'SIGNAL']
+ rising.sort(key=lambda w: -float(w.get('Signal_Weight', 0)))
+ if rising:
+ lines.append(' RISING SIGNAL (emerging words gaining ground):')
+ lines.append(' ' + '-' * 56)
+ for w in rising[:10]:
+ lines.append(
+ f' {w["Word"]:<20s} '
+ f'Count:{w["Count"]:>5} '
+ f'SW:{float(w.get("Signal_Weight", 0)):.3f} '
+ f'Rank:{w["Rank"]}'
+ )
+ lines.append('')
+
+ # Declining words — fading signal
+ declining = [w for w in self.words if w.get('Trajectory') == 'DECLINING'
+ and w.get('Classification') == 'SIGNAL']
+ declining.sort(key=lambda w: -float(w.get('Signal_Weight', 0)))
+ if declining:
+ lines.append(' DECLINING SIGNAL (fading words losing ground):')
+ lines.append(' ' + '-' * 56)
+ for w in declining[:10]:
+ lines.append(
+ f' {w["Word"]:<20s} '
+ f'Count:{w["Count"]:>5} '
+ f'SW:{float(w.get("Signal_Weight", 0)):.3f} '
+ f'Rank:{w["Rank"]}'
+ )
+ lines.append('')
+
+ # Thread SNR leaderboard
+ if self.sigsystem_threads:
+ lines.append(' THREAD SNR LEADERBOARD:')
+ lines.append(' ' + '-' * 56)
+ for t in self.sigsystem_threads[:10]:
+ thread_name = t.get('Thread', '')
+ snr = float(t.get('SNR_Normalized', 0))
+ snr_db = float(t.get('SNR_dB', 0))
+ words = int(t.get('Total_Words', 0))
+ lines.append(
+ f' {thread_name:<35s} '
+ f'SNR:{snr:.4f} '
+ f'dB:{snr_db:>6.2f} '
+ f'Words:{words:>5,}'
+ )
+ lines.append('')
+
+ lines.append('=' * 64)
+ lines.append(' "Every word I write is a soldier.')
+ lines.append(' Every thread I spin is a campaign.')
+ lines.append(' Every theme that survives becomes a commander."')
+ lines.append(' -- Operation SIGNAL FORCE')
+ lines.append('=' * 64)
+
+ report = '\n'.join(lines)
+
+ path = os.path.join(self.output_dir, 'signal_army_summary.txt')
+ with open(path, 'w', encoding='utf-8') as f:
+ f.write(report)
+ print(f" signal_army_summary.txt -- dashboard generated")
+
+ return report
+
+ def save_all(self):
+ """Generate all output files. Returns list of paths."""
+ paths = []
+ paths.append(self.save_flattened_messages())
+ paths.append(self.save_word_inventory())
+ paths.append(self.save_phrase_inventory())
+ paths.append(self.save_division_inventory())
+ self.generate_summary()
+ return paths
+
+
+# ---------------------------------------------------------------------------
+# CLI & Main
+# ---------------------------------------------------------------------------
+
+def collect_md_paths(args):
+ """Gather all .md file paths from --md and --md-dir arguments."""
+ paths = []
+ if args.md:
+ for p in args.md:
+ if os.path.isfile(p):
+ paths.append(os.path.abspath(p))
+ else:
+ print(f" WARNING: File not found: {p}", file=sys.stderr)
+ if args.md_dir:
+ d = args.md_dir
+ if os.path.isdir(d):
+ for fname in sorted(os.listdir(d)):
+ if fname.lower().endswith('.md'):
+ paths.append(os.path.abspath(os.path.join(d, fname)))
+ else:
+ print(f" WARNING: Directory not found: {d}", file=sys.stderr)
+ return paths
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='Signal Army -- Word Inventory & Force Ranking Tool\n'
+ '"Every Word a Soldier"',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog='Examples:\n'
+ ' python signal_army.py --md GPT_72325_army.md\n'
+ ' python signal_army.py --md-dir ./transcripts/\n'
+ ' python signal_army.py --json ~/Downloads/conversations.json\n'
+ ' python signal_army.py --json data.json --md-dir ./transcripts/\n'
+ )
+ parser.add_argument('--json', metavar='PATH',
+ help='Path to ChatGPT conversations.json export')
+ parser.add_argument('--md', metavar='PATH', nargs='+',
+ help='One or more .md transcript files')
+ parser.add_argument('--md-dir', metavar='DIR',
+ help='Directory of .md files (processes all *.md)')
+ parser.add_argument('--md-mode', choices=['detect', 'user-only'],
+ default='detect',
+ help='How to parse .md files (default: detect)')
+ parser.add_argument('--output-dir', metavar='DIR',
+ help='Output directory (default: same as first input)')
+ parser.add_argument('--no-phrases', action='store_true',
+ help='Skip phrase (n-gram) analysis')
+ parser.add_argument('--min-phrase-count', type=int, default=2, metavar='N',
+ help='Minimum occurrences for phrases (default: 2)')
+ parser.add_argument('--sigsystem', metavar='DIR',
+ help='Path to SIGSYSTEM run directory (overrides auto-detect)')
+ parser.add_argument('--no-sigsystem', action='store_true',
+ help='Skip SIGSYSTEM integration even if data exists')
+ parser.add_argument('--role', choices=['user', 'assistant', 'all'],
+ default='user',
+ help='Which messages to analyze: user (default), assistant (AI responses), or all')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='Show detailed progress and warnings')
+
+ args = parser.parse_args()
+
+ # Validate inputs
+ md_paths = collect_md_paths(args)
+ has_json = args.json and os.path.isfile(args.json)
+
+ if not md_paths and not has_json:
+ parser.error('At least one input source required (--json, --md, or --md-dir)')
+
+ # Determine output directory — each run gets a timestamped subfolder
+ run_stamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
+ if args.output_dir:
+ base_dir = args.output_dir
+ elif md_paths:
+ base_dir = os.path.dirname(md_paths[0])
+ elif has_json:
+ base_dir = os.path.dirname(os.path.abspath(args.json))
+ else:
+ base_dir = '.'
+ output_dir = os.path.join(base_dir, f'run_{run_stamp}')
+ os.makedirs(output_dir, exist_ok=True)
+
+ # Header
+ print(f'\nSignal Army -- Word Inventory Tool v{VERSION}')
+ print('=' * 48)
+ print()
+
+ # Phase 1: Gather messages
+ all_messages = []
+
+ if md_paths:
+ print(f'[1/3] Parsing {len(md_paths)} markdown file(s)...')
+ md_parser = MarkdownParser(md_paths, md_mode=args.md_mode)
+ md_messages = md_parser.parse_all()
+ all_messages.extend(md_messages)
+ print(f' Extracted {len(md_messages)} user messages from markdown')
+ if args.verbose and md_parser.warnings:
+ for w in md_parser.warnings:
+ print(f' WARNING: {w}')
+ print()
+
+ if has_json:
+ print(f'[1/3] Loading conversations.json...')
+ target_role = None if args.role == 'all' else args.role
+ flattener = ConversationFlattener(args.json, target_role=target_role)
+ json_messages = flattener.flatten_all()
+ all_messages.extend(json_messages)
+ if args.verbose and flattener.warnings:
+ for w in flattener.warnings:
+ print(f' WARNING: {w}')
+ print()
+
+ if not all_messages:
+ print('No messages extracted. Nothing to analyze.')
+ sys.exit(1)
+
+ print(f' Total messages to analyze: {len(all_messages)}')
+ print()
+
+ # Phase 2: Analyze
+ print('[2/3] Building word inventory...')
+ analyzer = SignalArmyAnalyzer(all_messages)
+
+ word_inventory = analyzer.build_word_inventory()
+ total_deployed = sum(w['Count'] for w in word_inventory)
+ print(f' {total_deployed:,} total words deployed')
+ print(f' {len(word_inventory):,} unique words (after stop word removal)')
+
+ if not args.no_phrases:
+ print(' Building phrase inventory (2-grams, 3-grams)...')
+ phrase_inventory = analyzer.build_phrase_inventory(
+ min_count=args.min_phrase_count
+ )
+ print(f' {len(phrase_inventory):,} recurring phrases found')
+ else:
+ phrase_inventory = []
+
+ print(' Building infantry field connections...')
+ infantry_context = analyzer.build_infantry_context()
+ print(f' {len(infantry_context)} infantry words mapped to connections')
+
+ print(' Building thematic division clusters...')
+ division_clusters = analyzer.build_division_clusters(word_inventory)
+ print(f' {len(division_clusters)} thematic divisions formed')
+ total_clustered = sum(d['Member_Count'] for d in division_clusters)
+ print(f' {total_clustered} words assigned to divisions')
+ print()
+
+ # Load SIGSYSTEM data — auto-detect unless disabled
+ sigsystem_scores = None
+ sigsystem_thread_snr = None
+ if not args.no_sigsystem:
+ sigsystem_dir = args.sigsystem or find_latest_sigsystem_run()
+ if sigsystem_dir and os.path.isdir(sigsystem_dir):
+ sigsystem_scores, sigsystem_thread_snr = load_sigsystem_data(sigsystem_dir)
+ if sigsystem_scores:
+ print(f' SIGSYSTEM run: {os.path.basename(sigsystem_dir)}')
+ elif args.sigsystem:
+ print(f' WARNING: SIGSYSTEM directory not found: {args.sigsystem}',
+ file=sys.stderr)
+
+ # Phase 3: Generate reports
+ print('[3/3] Generating reports...')
+ reporter = ReportGenerator(word_inventory, phrase_inventory,
+ all_messages, output_dir,
+ infantry_context=infantry_context,
+ division_clusters=division_clusters,
+ sigsystem_scores=sigsystem_scores,
+ sigsystem_thread_snr=sigsystem_thread_snr)
+ reporter.save_all()
+ print()
+
+ # Quick stats footer
+ rank_counts = Counter(w['Rank'] for w in word_inventory)
+ print('=' * 48)
+ print('QUICK STATS:')
+ print(f' Officer-Class words: {rank_counts.get("Officer-Class", 0)}')
+ print(f' Doctrine Builders: {rank_counts.get("Doctrine Builder", 0)}')
+ print(f' Division-rank words: {rank_counts.get("Division", 0)}')
+ print(f' Thematic Divisions: {len(division_clusters)}')
+ print(f' Words in Divisions: {total_clustered}')
+ print(f' Total Token Mass: {sum(w["Count"] * w["Token_Weight"] for w in word_inventory):,.1f}')
+ print('=' * 48)
+ print(f'\nAll files written to: {output_dir}/')
+ print()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/moses-sampler/processors/sigarmy/sigsystem.py b/moses-sampler/processors/sigarmy/sigsystem.py
new file mode 100644
index 0000000..218ab20
--- /dev/null
+++ b/moses-sampler/processors/sigarmy/sigsystem.py
@@ -0,0 +1,1475 @@
+#!/usr/bin/env python3
+"""
+SIGSYSTEM — Signal Classification & Measurement Subsystem
+"Every word is unresolved until the system collapses it."
+
+Consumes Signal Army CSV output and applies the SIGSYSTEM 5-stage
+pipeline to classify words as Signal or Noise, compute SNR at every
+level (word, message, thread, session), measure compression necessity,
+and track signal decay across threads.
+
+Architecture (from PPA-safe foundation doc):
+ Stage 1: Input Evaluation
+ Stage 2: Contextual Assessment
+ Stage 3: Structural Necessity Evaluation
+ Stage 4: Classification (dual-weight: signal + noise)
+ Stage 5: Recursive Validation & Longitudinal Tracking
+
+Usage:
+ python sigsystem.py --run-dir ../signal_army/runs/run_2026-03-02_06-03-50/
+ python sigsystem.py --run-dir ../signal_army/runs/run_2026-03-02_06-03-50/ --output-dir ./runs/
+"""
+
+import argparse
+import csv
+import math
+import os
+import re
+import sys
+from collections import Counter, defaultdict
+from datetime import datetime, timezone
+
+# Handle large CSV fields (flattened_messages can contain entire transcripts)
+csv.field_size_limit(10 * 1024 * 1024) # 10 MB
+
+# ---------------------------------------------------------------------------
+# Constants
+# ---------------------------------------------------------------------------
+
+VERSION = "1.1"
+
+# Base signal scores by Signal Army rank
+# These represent the structural assessment from Signal Army —
+# SIGSYSTEM refines them with contextual, necessity, and decay analysis
+RANK_SIGNAL_BASE = {
+ 'Officer-Class': 0.85,
+ 'Doctrine Builder': 0.70,
+ 'Division': 0.55,
+ 'Platoon': 0.40,
+ 'Squad': 0.25,
+ 'Fireteam': 0.15,
+ 'Scout': 0.10,
+ 'Infantry': 0.05,
+}
+
+# Weights for the composite signal score
+W_RANK = 0.20 # structural rank from Signal Army
+W_CONTEXT = 0.25 # contextual assessment (thread spread, division membership)
+W_NECESSITY = 0.35 # compression necessity (structural dependency)
+W_DECAY = 0.20 # longitudinal trajectory bonus/penalty
+
+# Classification threshold — above = Signal, below = Noise
+SIGNAL_THRESHOLD = 0.35
+
+# Stop words — must match Signal Army's list for consistent tokenization
+STOP_WORDS = frozenset({
+ 'the', 'a', 'an', 'and', 'or', 'but', 'so', 'if', 'of', 'in', 'on',
+ 'at', 'to', 'for', 'from', 'with', 'by', 'about', 'as', 'is', 'are',
+ 'was', 'were', 'be', 'been', 'being', 'am', 'have', 'has', 'had',
+ 'having', 'do', 'does', 'did', 'doing', 'done', 'will', 'would',
+ 'shall', 'should', 'can', 'could', 'may', 'might', 'must',
+ 'i', 'me', 'my', 'mine', 'myself', 'you', 'your', 'yours', 'yourself',
+ 'yourselves', 'he', 'him', 'his', 'himself', 'she', 'her', 'hers',
+ 'herself', 'it', 'its', 'itself', 'we', 'us', 'our', 'ours',
+ 'ourselves', 'they', 'them', 'their', 'theirs', 'themselves',
+ 'who', 'whom', 'whose', 'which', 'what', 'that', 'this', 'these',
+ 'those', 'into', 'through', 'between', 'after', 'before', 'during',
+ 'without', 'within', 'upon', 'above', 'below', 'under', 'over',
+ 'against', 'along', 'among', 'around', 'behind', 'beside', 'beyond',
+ 'beneath', 'despite', 'except', 'inside', 'outside', 'toward',
+ 'towards', 'until', 'unto', 'via', 'up', 'down', 'out', 'off',
+ 'because', 'although', 'though', 'while', 'when', 'where', 'whether',
+ 'unless', 'since', 'than', 'like', 'very', 'really', 'just', 'also',
+ 'too', 'quite', 'rather', 'still', 'already', 'always', 'often',
+ 'sometimes', 'ever', 'even', 'only', 'then', 'now', 'here', 'there',
+ 'some', 'any', 'all', 'each', 'every', 'both', 'either', 'neither',
+ 'much', 'many', 'more', 'most', 'few', 'less', 'several', 'such',
+ 'own', 'other', 'another', 'else', 'same', 'well', 'back', 'get',
+ 'got', 'going', 'go', 'went', 'goes', 'come', 'came', 'put', 'take',
+ 'took', 'taken', 'make', 'made', 'let', 'say', 'said', 'says',
+ 'tell', 'told', 'ask', 'asked', 'see', 'saw', 'seen', 'look',
+ 'looked', 'give', 'gave', 'given', 'been', 'being', 'become', 'became',
+ 'keep', 'kept', 'thing', 'things', 'way', 'ways', 'one', 'two',
+ 'three', 'first', 'second', 'could', 'would', 'should', 'may',
+ 'might', 'must', "i'm", "i've", "i'll", "i'd", "it's", "that's",
+ "there's", "here's", "don't", "doesn't", "didn't", "won't",
+ "wouldn't", "shouldn't", "can't", "couldn't", "isn't", "aren't",
+ "wasn't", "weren't", "haven't", "hasn't", "hadn't", "let's",
+ "he's", "she's", "we're", "they're", "you're", "we've", "they've",
+ "you've", "who's", "what's", 'okay', 'ok', 'yeah', 'yes', 'oh',
+ 'ah', 'um', 'uh', 'lol', 'gonna', 'wanna', 'gotta', 'etc', 'eg', 'ie',
+})
+
+# Infantry words (from Signal Army — noise carriers, rank-capped)
+INFANTRY_WORDS = frozenset({
+ 'not', 'no', 'yes', 'never', 'none', 'nothing',
+ 'how', 'why', 'what', 'where', 'when',
+ 'think', 'know', 'feel', 'want', 'need', 'believe', 'understand',
+ 'mean', 'thought', 'knew', 'felt', 'wanted', 'needed',
+ 'high', 'low', 'new', 'old', 'good', 'bad', 'big', 'small',
+ 'right', 'wrong', 'different', 'specific', 'real', 'true', 'full',
+ 'long', 'short', 'hard', 'easy', 'possible', 'important',
+ 'better', 'best', 'able', 'available', 'clear', 'sure', 'next',
+ 'use', 'used', 'using', 'work', 'working', 'works', 'worked',
+ 'run', 'running', 'runs', 'set', 'start', 'started', 'help',
+ 'try', 'tried', 'trying', 'show', 'shows', 'call', 'called',
+ 'point', 'part', 'level', 'type', 'kind', 'form', 'case',
+ 'end', 'line', 'world', 'place', 'time', 'people', 'man',
+ 'great', 'actually', 'basically', 'literally', 'probably',
+ 'exactly', 'maybe', 'perhaps', 'simply', 'certainly', 'definitely',
+ 'bit', 'lot', 'enough', 'whole', 'entire',
+})
+
+# Compound words (match Signal Army)
+COMPOUND_WORDS = {
+ 'mo§es': 'moses',
+ 'moses™': 'moses',
+ 'mo§es™': 'moses',
+ 'sigrank': 'sigrank',
+}
+
+# Protected from stemming
+NO_STEM = frozenset({
+ 'moses', 'moes', 'sigrank', 'fracto', 'abba', 'aaron', 'kleya',
+ 'luthen', 'nemik', 'hange', 'levi', 'zoran', 'keter',
+ 'analysis', 'basis', 'thesis', 'genesis', 'axis',
+ 'coherence', 'governance', 'compliance', 'insurance', 'convergence',
+ 'class', 'process', 'address', 'access', 'success', 'stress',
+})
+
+
+# ---------------------------------------------------------------------------
+# Stemmer (identical to Signal Army for consistency)
+# ---------------------------------------------------------------------------
+
+def stem_word(word):
+ """Lightweight stemmer matching Signal Army's logic."""
+ if word in NO_STEM:
+ return word
+ if len(word) <= 3:
+ return word
+ if word.endswith("'s"):
+ return word[:-2]
+ if word.endswith("s'"):
+ return word[:-1]
+ if word.endswith('ing') and len(word) > 5:
+ base = word[:-3]
+ if len(base) >= 2 and base[-1] == base[-2]:
+ return base[:-1]
+ return base
+ if word.endswith('ly') and len(word) > 4:
+ return word[:-2]
+ if word.endswith('ed') and len(word) > 4:
+ base = word[:-2]
+ if len(base) >= 2 and base[-1] == base[-2]:
+ return base[:-1]
+ if word.endswith('ated') and len(word) > 5:
+ return word[:-1]
+ return base
+ if word.endswith('s') and not word.endswith('ss') and len(word) > 3:
+ if word.endswith('ies') and len(word) > 4:
+ return word[:-3] + 'y'
+ if word.endswith('es') and len(word) > 4:
+ if word.endswith(('ses', 'xes', 'zes', 'ches', 'shes')):
+ return word[:-2]
+ return word[:-1]
+ if not word.endswith(('us', 'is', 'ss')):
+ return word[:-1]
+ return word
+
+
+def tokenize(text):
+ """Tokenize text matching Signal Army's approach.
+ Returns ALL tokens (including stop words and infantry) for SNR computation."""
+ text = text.lower()
+ for pattern, replacement in COMPOUND_WORDS.items():
+ text = text.replace(pattern, replacement)
+ text = re.sub(r"[^\w\s'\-]", ' ', text)
+ tokens = text.split()
+ cleaned = []
+ for t in tokens:
+ t = t.strip("'-_")
+ if not t:
+ continue
+ if t.isdigit():
+ continue
+ if len(t) == 1 and t not in ('i', 'a'):
+ continue
+ t = stem_word(t)
+ if t:
+ cleaned.append(t)
+ return cleaned
+
+
+# ---------------------------------------------------------------------------
+# Stage 1: Input Evaluation — Data Loader
+# ---------------------------------------------------------------------------
+
+class DataLoader:
+ """Loads Signal Army run data from CSV files."""
+
+ def __init__(self, run_dir):
+ self.run_dir = run_dir
+
+ def load_word_inventory(self):
+ """Load word_inventory.csv into list of dicts."""
+ path = os.path.join(self.run_dir, 'word_inventory.csv')
+ return self._load_csv(path)
+
+ def load_phrase_inventory(self):
+ """Load phrase_inventory.csv into list of dicts."""
+ path = os.path.join(self.run_dir, 'phrase_inventory.csv')
+ return self._load_csv(path)
+
+ def load_division_inventory(self):
+ """Load division_inventory.csv into list of dicts."""
+ path = os.path.join(self.run_dir, 'division_inventory.csv')
+ return self._load_csv(path)
+
+ def load_messages(self):
+ """Load flattened_messages.csv into list of dicts."""
+ path = os.path.join(self.run_dir, 'flattened_messages.csv')
+ return self._load_csv(path)
+
+ def _load_csv(self, path):
+ """Generic CSV loader."""
+ if not os.path.isfile(path):
+ print(f" WARNING: {os.path.basename(path)} not found", file=sys.stderr)
+ return []
+ with open(path, 'r', encoding='utf-8', errors='replace') as f:
+ reader = csv.DictReader(f)
+ return list(reader)
+
+
+# ---------------------------------------------------------------------------
+# Stage 2: Contextual Assessment
+# ---------------------------------------------------------------------------
+
+class ContextualAssessor:
+ """Evaluates each word's contextual signal contribution.
+
+ Inputs from Signal Army:
+ - Rank (structural position)
+ - Thread spread (how many threads the word appears in)
+ - Division membership (thematic coherence)
+
+ Outputs:
+ - contextual_score (0-1) for each word
+ """
+
+ def __init__(self, word_inventory, division_inventory):
+ self.word_inventory = word_inventory
+ self.division_inventory = division_inventory
+
+ # Build lookups
+ self.word_data = {}
+ self.max_threads = 0
+ self.max_count = 0
+ for w in word_inventory:
+ word = w['Word']
+ threads = int(w.get('Threads_Appeared_In', 0))
+ count = int(w.get('Count', 0))
+ self.word_data[word] = {
+ 'rank': w.get('Rank', 'Scout'),
+ 'threads': threads,
+ 'count': count,
+ 'thread_names': w.get('Thread_Names', ''),
+ }
+ if threads > self.max_threads:
+ self.max_threads = threads
+ if count > self.max_count:
+ self.max_count = count
+
+ # Build division membership: word -> list of division names
+ self.word_divisions = defaultdict(list)
+ self.division_officers = defaultdict(set)
+ for div in division_inventory:
+ div_name = div.get('Division_Name', '')
+ members = div.get('Members', '').split(';')
+ commander = div.get('Commander', '')
+ commander_rank = div.get('Commander_Rank', '')
+ for member in members:
+ member = member.strip()
+ if member:
+ self.word_divisions[member].append(div_name)
+ if commander_rank == 'Officer-Class':
+ for member in members:
+ member = member.strip()
+ if member:
+ self.division_officers[member].add(commander)
+
+ def score(self, word):
+ """Compute contextual signal score for a word (0-1)."""
+ data = self.word_data.get(word)
+ if data is None:
+ # Unknown word — not in Signal Army inventory
+ if word in STOP_WORDS:
+ return 0.0
+ if word in INFANTRY_WORDS:
+ return 0.05
+ return 0.08 # unclassified non-stop word gets minimal score
+
+ # Component 1: Thread spread (0-1)
+ # Words that survive across many threads have higher signal potential
+ thread_spread = data['threads'] / max(self.max_threads, 1)
+
+ # Component 2: Division membership (0, 0.5, or 1.0)
+ # Being in a division = thematic coherence = signal indicator
+ divisions = self.word_divisions.get(word, [])
+ if divisions:
+ div_score = min(len(divisions) / 3.0, 1.0) # cap at 3 divisions
+ else:
+ div_score = 0.0
+
+ # Component 3: Officer proximity
+ # Words that share a division with Officers have higher signal
+ officers_connected = self.division_officers.get(word, set())
+ officer_score = min(len(officers_connected) / 5.0, 1.0)
+
+ # Weighted combination
+ contextual = (
+ 0.35 * thread_spread +
+ 0.35 * div_score +
+ 0.30 * officer_score
+ )
+
+ return round(min(contextual, 1.0), 4)
+
+ def score_all(self):
+ """Score all words in the inventory. Returns dict: word -> score."""
+ scores = {}
+ for w in self.word_inventory:
+ word = w['Word']
+ scores[word] = self.score(word)
+ return scores
+
+
+# ---------------------------------------------------------------------------
+# Stage 3: Structural Necessity Evaluation
+# ---------------------------------------------------------------------------
+
+class NecessityEvaluator:
+ """Assesses compression necessity: would removal of this word
+ degrade semantic coherence?
+
+ Uses division-level dependency: how many other division members
+ depend on this word's presence for their thematic cluster to hold.
+
+ From the spec (SN_Filter_Infra.md, Component C-0005):
+ - Layer 3: Signal Contribution Score — adds weight to system trajectory
+ - Layer 4: Compression Anchor Rating — role in lowering entropy
+ """
+
+ def __init__(self, word_inventory, division_inventory):
+ # Build word -> divisions mapping
+ self.word_divisions = defaultdict(set)
+ self.division_members = {}
+ self.word_rank = {}
+ self.word_count = {}
+
+ for w in word_inventory:
+ self.word_rank[w['Word']] = w.get('Rank', 'Scout')
+ self.word_count[w['Word']] = int(w.get('Count', 0))
+
+ for div in division_inventory:
+ div_name = div.get('Division_Name', '')
+ members = [m.strip() for m in div.get('Members', '').split(';') if m.strip()]
+ self.division_members[div_name] = members
+ for member in members:
+ self.word_divisions[member].add(div_name)
+
+ # Build co-membership graph: for each word, count unique division co-members
+ self.co_members = defaultdict(set)
+ for div_name, members in self.division_members.items():
+ for m in members:
+ for other in members:
+ if other != m:
+ self.co_members[m].add(other)
+
+ # Count officer co-members specifically
+ self.officer_connections = defaultdict(set)
+ for word, co_words in self.co_members.items():
+ for co in co_words:
+ if self.word_rank.get(co) == 'Officer-Class':
+ self.officer_connections[word].add(co)
+
+ def score(self, word):
+ """Compute compression necessity score (0-1).
+
+ High necessity = removing this word would break connections.
+ Low necessity = word is structurally replaceable."""
+ # Words not in any division have low structural necessity
+ if word not in self.word_divisions:
+ # But high-frequency non-division words still carry some weight
+ count = self.word_count.get(word, 0)
+ if count > 100:
+ return 0.2
+ return 0.05
+
+ # Factor 1: How many division connections does this word have?
+ co_count = len(self.co_members.get(word, set()))
+ # Normalize: 15+ connections = max
+ connection_score = min(co_count / 15.0, 1.0)
+
+ # Factor 2: How many of those connections are Officers?
+ officer_count = len(self.officer_connections.get(word, set()))
+ # Normalize: 5+ officer connections = max
+ officer_dep_score = min(officer_count / 5.0, 1.0)
+
+ # Factor 3: How many divisions does this word appear in?
+ div_count = len(self.word_divisions.get(word, set()))
+ # Being in multiple divisions = cross-thematic importance
+ cross_div_score = min(div_count / 3.0, 1.0)
+
+ # Weighted combination
+ necessity = (
+ 0.40 * connection_score +
+ 0.35 * officer_dep_score +
+ 0.25 * cross_div_score
+ )
+
+ return round(min(necessity, 1.0), 4)
+
+ def score_all(self):
+ """Score all words. Returns dict: word -> necessity_score."""
+ scores = {}
+ for w in self.word_rank:
+ scores[w] = self.score(w)
+ return scores
+
+
+# ---------------------------------------------------------------------------
+# Stage 5: Longitudinal Tracking — Signal Decay Rate
+# ---------------------------------------------------------------------------
+
+class DecayTracker:
+ """Tracks signal persistence and decay across threads over time.
+
+ For each word, measures whether its presence is:
+ - RISING: appearing in more recent threads
+ - STABLE: consistent presence across time
+ - DECLINING: fading from recent threads
+ - SPARSE: too few data points to determine
+
+ Uses thread timestamps to establish temporal ordering.
+ """
+
+ def __init__(self, word_inventory, messages):
+ # Build temporal ordering of threads
+ self.thread_order = self._order_threads(messages)
+ self.total_threads = len(self.thread_order)
+ self.thread_index = {t: i for i, t in enumerate(self.thread_order)}
+
+ # Build word -> thread presence
+ self.word_threads = {}
+ for w in word_inventory:
+ word = w['Word']
+ thread_names = w.get('Thread_Names', '')
+ threads = set(t.strip() for t in thread_names.split(';') if t.strip())
+ self.word_threads[word] = threads
+
+ def _order_threads(self, messages):
+ """Order threads by earliest message timestamp."""
+ thread_first_ts = {}
+ for msg in messages:
+ title = msg.get('conversation_title', '')
+ ts = msg.get('timestamp', '')
+ if title and ts:
+ if title not in thread_first_ts or ts < thread_first_ts[title]:
+ thread_first_ts[title] = ts
+ # Sort by timestamp
+ return [t for t, _ in sorted(thread_first_ts.items(), key=lambda x: x[1])]
+
+ def compute_decay(self, word):
+ """Compute decay rate and trajectory for a word.
+
+ Returns: (decay_score, trajectory)
+ - decay_score: -1 (declining) to +1 (rising), 0 = stable
+ - trajectory: 'RISING', 'STABLE', 'DECLINING', or 'SPARSE'
+ """
+ threads = self.word_threads.get(word, set())
+ if not threads or self.total_threads < 3:
+ return 0.0, 'SPARSE'
+
+ # Get temporal positions of word's threads
+ positions = sorted(self.thread_index[t] for t in threads if t in self.thread_index)
+ if len(positions) < 2:
+ return 0.0, 'SPARSE'
+
+ # Split timeline in half
+ midpoint = self.total_threads / 2.0
+ early_count = sum(1 for p in positions if p < midpoint)
+ late_count = sum(1 for p in positions if p >= midpoint)
+ total = len(positions)
+
+ # Compute directional bias
+ early_ratio = early_count / total
+ late_ratio = late_count / total
+
+ # Decay score: positive = rising, negative = declining
+ decay_score = late_ratio - early_ratio
+
+ # Classify trajectory
+ if abs(decay_score) < 0.15:
+ trajectory = 'STABLE'
+ elif decay_score > 0:
+ trajectory = 'RISING'
+ else:
+ trajectory = 'DECLINING'
+
+ # Also check coverage: words present across most threads are stable
+ coverage = total / max(self.total_threads, 1)
+ if coverage > 0.7 and abs(decay_score) < 0.3:
+ trajectory = 'STABLE'
+ decay_score *= 0.5 # dampen score for high-coverage words
+
+ return round(decay_score, 4), trajectory
+
+ def compute_all(self):
+ """Compute decay for all words.
+ Returns dict: word -> (decay_score, trajectory)."""
+ results = {}
+ for word in self.word_threads:
+ results[word] = self.compute_decay(word)
+ return results
+
+
+# ---------------------------------------------------------------------------
+# Stage 4: Classification — Signal/Noise Dual-Weight
+# ---------------------------------------------------------------------------
+
+class SignalClassifier:
+ """Produces dual-weight classification for every word.
+
+ From the user's core spec:
+ "Words have signal and noise which are both weight — it's a density."
+
+ Combines:
+ - Rank base score (from Signal Army structural analysis)
+ - Contextual score (Stage 2)
+ - Necessity score (Stage 3)
+ - Decay modifier (Stage 5)
+
+ Outputs: signal_weight, noise_weight, classification
+ """
+
+ def __init__(self, word_inventory, contextual_scores, necessity_scores, decay_results):
+ self.word_inventory = word_inventory
+ self.contextual = contextual_scores
+ self.necessity = necessity_scores
+ self.decay = decay_results
+
+ def classify(self, word, rank=None):
+ """Classify a single word. Returns (signal_weight, noise_weight, classification)."""
+ # Rank base
+ if rank is None:
+ rank = 'Scout'
+ rank_base = RANK_SIGNAL_BASE.get(rank, 0.10)
+
+ # Contextual score
+ ctx = self.contextual.get(word, 0.08)
+
+ # Necessity score
+ nec = self.necessity.get(word, 0.05)
+
+ # Decay modifier
+ decay_score, trajectory = self.decay.get(word, (0.0, 'SPARSE'))
+ # Convert decay to a bonus/penalty: rising words get a boost, declining get a penalty
+ decay_modifier = decay_score * 0.5 # scale the effect
+
+ # Composite signal weight
+ signal_weight = (
+ W_RANK * rank_base +
+ W_CONTEXT * ctx +
+ W_NECESSITY * nec +
+ W_DECAY * max(decay_modifier, 0) # only positive decay helps signal
+ )
+
+ # Noise weight is not simply 1-signal — it's independently computed
+ # Words can carry BOTH signal and noise (this is the dual-weight density insight)
+ noise_base = 1.0 - rank_base
+ noise_context = 1.0 - ctx
+ noise_decay_penalty = abs(min(decay_modifier, 0)) # declining words increase noise
+
+ noise_weight = (
+ W_RANK * noise_base +
+ W_CONTEXT * noise_context +
+ W_NECESSITY * (1.0 - nec) +
+ W_DECAY * noise_decay_penalty
+ )
+
+ # Normalize so signal + noise = 1.0 (density interpretation)
+ total = signal_weight + noise_weight
+ if total > 0:
+ signal_weight = signal_weight / total
+ noise_weight = noise_weight / total
+ else:
+ signal_weight = 0.5
+ noise_weight = 0.5
+
+ # Classification
+ if signal_weight >= SIGNAL_THRESHOLD:
+ classification = 'SIGNAL'
+ else:
+ classification = 'NOISE'
+
+ return (round(signal_weight, 4), round(noise_weight, 4), classification)
+
+ def classify_all(self):
+ """Classify all words in inventory.
+ Returns dict: word -> (signal_weight, noise_weight, classification, rank)."""
+ results = {}
+ for w in self.word_inventory:
+ word = w['Word']
+ rank = w.get('Rank', 'Scout')
+ sw, nw, cls = self.classify(word, rank)
+ results[word] = {
+ 'signal_weight': sw,
+ 'noise_weight': nw,
+ 'classification': cls,
+ 'rank': rank,
+ 'contextual_score': self.contextual.get(word, 0.0),
+ 'necessity_score': self.necessity.get(word, 0.0),
+ 'decay_score': self.decay.get(word, (0.0, 'SPARSE'))[0],
+ 'trajectory': self.decay.get(word, (0.0, 'SPARSE'))[1],
+ }
+ return results
+
+
+# ---------------------------------------------------------------------------
+# SNR Engine
+# ---------------------------------------------------------------------------
+
+class SNREngine:
+ """Computes Signal-to-Noise Ratio at message, thread, and session levels.
+
+ From the SCS Root Equation:
+ SCS_r = (Signal_Words / max(Noise_Words, 1)) × Weight(t) – Drift(n)
+
+ From the MOS²ES normalized form:
+ SNR = Signal / (Signal + Noise) — bounded 0-1
+
+ Both forms are computed. The normalized form is primary.
+ """
+
+ def __init__(self, classifications, messages):
+ self.classifications = classifications # word -> {...}
+ self.messages = messages
+
+ def _classify_token(self, token):
+ """Classify a single token for SNR computation."""
+ if token in STOP_WORDS:
+ return 'NOISE', 0.0, 1.0
+ if token in INFANTRY_WORDS:
+ sw = 0.05
+ nw = 0.95
+ return 'NOISE', sw, nw
+
+ data = self.classifications.get(token)
+ if data:
+ return data['classification'], data['signal_weight'], data['noise_weight']
+
+ # Unknown word — not in Signal Army inventory, not stop/infantry
+ return 'NOISE', 0.08, 0.92
+
+ def analyze_message(self, message_text):
+ """Compute SNR for a single message.
+ Returns dict with SNR metrics."""
+ tokens = tokenize(message_text)
+ if not tokens:
+ return {
+ 'total_words': 0,
+ 'signal_count': 0,
+ 'noise_count': 0,
+ 'stop_count': 0,
+ 'snr_normalized': 0.0,
+ 'snr_ratio': 0.0,
+ 'snr_db': 0.0,
+ 'signal_density': 0.0,
+ }
+
+ signal_count = 0
+ noise_count = 0
+ stop_count = 0
+ signal_mass = 0.0
+ noise_mass = 0.0
+
+ for token in tokens:
+ if token in STOP_WORDS:
+ stop_count += 1
+ noise_mass += 1.0
+ noise_count += 1
+ continue
+
+ cls, sw, nw = self._classify_token(token)
+ signal_mass += sw
+ noise_mass += nw
+ if cls == 'SIGNAL':
+ signal_count += 1
+ else:
+ noise_count += 1
+
+ total = len(tokens)
+ total_non_stop = total - stop_count
+
+ # MOS²ES normalized SNR (0-1)
+ snr_normalized = signal_mass / (signal_mass + noise_mass) if (signal_mass + noise_mass) > 0 else 0.0
+
+ # SCS ratio form
+ snr_ratio = signal_count / max(noise_count, 1)
+
+ # dB form
+ if snr_ratio > 0:
+ snr_db = 10 * math.log10(snr_ratio)
+ else:
+ snr_db = -99.0
+
+ # Signal density: signal words / non-stop words
+ signal_density = signal_count / max(total_non_stop, 1)
+
+ return {
+ 'total_words': total,
+ 'signal_count': signal_count,
+ 'noise_count': noise_count,
+ 'stop_count': stop_count,
+ 'snr_normalized': round(snr_normalized, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'signal_density': round(signal_density, 4),
+ }
+
+ def analyze_by_thread(self):
+ """Compute SNR per thread. Returns dict: thread_name -> metrics."""
+ thread_metrics = defaultdict(lambda: {
+ 'total_words': 0, 'signal_count': 0, 'noise_count': 0,
+ 'stop_count': 0, 'signal_mass': 0.0, 'noise_mass': 0.0,
+ 'message_count': 0,
+ })
+
+ for msg in self.messages:
+ title = msg.get('conversation_title', 'Unknown')
+ text = msg.get('message_text', '')
+ tokens = tokenize(text)
+ tm = thread_metrics[title]
+ tm['message_count'] += 1
+
+ for token in tokens:
+ if token in STOP_WORDS:
+ tm['stop_count'] += 1
+ tm['noise_count'] += 1
+ tm['noise_mass'] += 1.0
+ tm['total_words'] += 1
+ continue
+
+ cls, sw, nw = self._classify_token(token)
+ tm['signal_mass'] += sw
+ tm['noise_mass'] += nw
+ tm['total_words'] += 1
+ if cls == 'SIGNAL':
+ tm['signal_count'] += 1
+ else:
+ tm['noise_count'] += 1
+
+ # Compute final metrics per thread
+ results = {}
+ for title, tm in thread_metrics.items():
+ sm = tm['signal_mass']
+ nm = tm['noise_mass']
+ snr_norm = sm / (sm + nm) if (sm + nm) > 0 else 0.0
+ snr_ratio = tm['signal_count'] / max(tm['noise_count'], 1)
+ snr_db = 10 * math.log10(snr_ratio) if snr_ratio > 0 else -99.0
+ total_non_stop = tm['total_words'] - tm['stop_count']
+
+ results[title] = {
+ 'message_count': tm['message_count'],
+ 'total_words': tm['total_words'],
+ 'signal_count': tm['signal_count'],
+ 'noise_count': tm['noise_count'],
+ 'stop_count': tm['stop_count'],
+ 'snr_normalized': round(snr_norm, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'signal_density': round(tm['signal_count'] / max(total_non_stop, 1), 4),
+ }
+
+ return results
+
+ def analyze_session(self, thread_metrics):
+ """Compute session-level aggregate SNR."""
+ total_words = sum(t['total_words'] for t in thread_metrics.values())
+ total_signal = sum(t['signal_count'] for t in thread_metrics.values())
+ total_noise = sum(t['noise_count'] for t in thread_metrics.values())
+ total_stop = sum(t['stop_count'] for t in thread_metrics.values())
+ total_messages = sum(t['message_count'] for t in thread_metrics.values())
+
+ snr_ratio = total_signal / max(total_noise, 1)
+ snr_db = 10 * math.log10(snr_ratio) if snr_ratio > 0 else -99.0
+ total_non_stop = total_words - total_stop
+ snr_normalized = total_signal / max(total_signal + total_noise, 1)
+
+ return {
+ 'total_threads': len(thread_metrics),
+ 'total_messages': total_messages,
+ 'total_words': total_words,
+ 'total_signal': total_signal,
+ 'total_noise': total_noise,
+ 'total_stop': total_stop,
+ 'snr_normalized': round(snr_normalized, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'signal_density': round(total_signal / max(total_non_stop, 1), 4),
+ }
+
+
+# ---------------------------------------------------------------------------
+# PPA Metrics Engine — Equations from SCSEngine PPA + CIVITAS PPA3
+# ---------------------------------------------------------------------------
+
+class PPAMetrics:
+ """Computes metrics defined in the MOS2ES patent filings.
+
+ From SCSEngine PPA:
+ ResonanceScore = (SNR_w * SNR) + (Drift_w * (1 - DriftDelta)) + (Density_w * SignalDensity)
+ ScarIndex(t) = Var(signal_weights) / Baseline
+ S2S = N_survived / N_attempted
+ Ghost Token: high necessity, low resonance (quarantine candidates)
+ Keter threshold: resonance >= 0.94
+
+ From CIVITAS PPA3 (Survivability-Based Truth Compression):
+ TCR — Token Compression Ratio (signal tokens / total tokens)
+ RTD — Recursive Thematic Density (signal density within divisions)
+ """
+
+ # Resonance score weights (system-tunable per SCSEngine [0259])
+ W_SNR = 0.40
+ W_DRIFT = 0.30
+ W_DENSITY = 0.30
+
+ # Scar Index thresholds (SCSEngine Addendum F [0269])
+ SCAR_HEALTHY = 0.20
+ SCAR_WARNING = 0.50
+ SCAR_CRITICAL = 0.90
+
+ # Keter threshold (SCSEngine [0097], [0271])
+ KETER_THRESHOLD = 0.94
+
+ # Ghost Token threshold (SCSEngine [0060])
+ GHOST_RESONANCE_MAX = 0.25
+ GHOST_NECESSITY_MIN = 0.30
+
+ def __init__(self, classifications, thread_snr, session_snr, division_data=None):
+ self.classifications = classifications
+ self.thread_snr = thread_snr
+ self.session_snr = session_snr
+ self.divisions = division_data or []
+
+ def resonance_score(self, snr, drift_delta, signal_density):
+ """Per SCSEngine [0259]:
+ ResonanceScore = (SNR_w * SNR) + (Drift_w * (1 - DriftDelta)) + (Density_w * Density)
+ Range: 0.0-1.0. Threshold >= 0.94 = Keter event."""
+ return (
+ self.W_SNR * snr +
+ self.W_DRIFT * (1.0 - abs(drift_delta)) +
+ self.W_DENSITY * signal_density
+ )
+
+ def thread_resonance_scores(self):
+ """Compute resonance score for each thread."""
+ results = {}
+ for thread, metrics in self.thread_snr.items():
+ snr = metrics['snr_normalized']
+ density = metrics['signal_density']
+ # Drift delta: how far this thread's SNR deviates from session average
+ session_snr = self.session_snr['snr_normalized']
+ drift = (snr - session_snr) / max(session_snr, 0.001)
+ res = self.resonance_score(snr, drift, density)
+ results[thread] = {
+ 'resonance': round(res, 4),
+ 'snr': round(snr, 4),
+ 'drift_delta': round(drift, 4),
+ 'density': round(density, 4),
+ 'keter': res >= self.KETER_THRESHOLD,
+ }
+ return results
+
+ def scar_index(self):
+ """Per SCSEngine [0240], Addendum L:
+ ScarIndex = Var(signal_weights) / Baseline
+ Measures semantic drift / conceptual corruption across the corpus.
+ 0.0-0.20 = healthy, 0.20-0.50 = warning, >= 0.50 = critical."""
+ signal_weights = [d['signal_weight'] for d in self.classifications.values()]
+ if not signal_weights:
+ return 0.0
+ mean_sw = sum(signal_weights) / len(signal_weights)
+ variance = sum((sw - mean_sw) ** 2 for sw in signal_weights) / len(signal_weights)
+ baseline = max(mean_sw, 0.001)
+ si = variance / baseline
+ return round(min(si, 1.0), 4)
+
+ def scar_status(self, si):
+ """Classify scar index into operational bands."""
+ if si <= self.SCAR_HEALTHY:
+ return 'HEALTHY'
+ elif si <= self.SCAR_WARNING:
+ return 'WARNING'
+ elif si < self.SCAR_CRITICAL:
+ return 'CRITICAL'
+ else:
+ return 'COLLAPSE'
+
+ def s2s_ratio(self):
+ """Per SCSEngine [0241]:
+ S2S = N_survived / N_attempted
+ Where N_survived = signal words appearing in 2+ threads (survived recursion).
+ N_attempted = total unique words classified."""
+ survived = sum(1 for d in self.classifications.values()
+ if d['classification'] == 'SIGNAL'
+ and d['trajectory'] in ('RISING', 'STABLE'))
+ attempted = len(self.classifications)
+ if attempted == 0:
+ return 0.0
+ return round(survived / attempted, 4)
+
+ def s2s_tier(self, s2s):
+ """S2S valuation tiers (SCSEngine Addendum F [0270])."""
+ if s2s >= 0.90:
+ return 'HIGH-VALUE (Keter-critical)'
+ elif s2s >= 0.71:
+ return 'MID-VALUE (IP-grade)'
+ elif s2s >= 0.50:
+ return 'LOW-VALUE (transactional)'
+ else:
+ return 'SUB-THRESHOLD'
+
+ def tcr(self):
+ """Token Compression Ratio (CIVITAS PPA3, Add-on IV):
+ TCR = signal_tokens / total_tokens"""
+ ss = self.session_snr
+ total = ss.get('total_words', 0)
+ signal = ss.get('total_signal', 0)
+ if total == 0:
+ return 0.0
+ return round(signal / total, 4)
+
+ def scd(self):
+ """Semantic Compression Delta (CIVITAS PPA3, Add-on IV):
+ SCD = signal_density_post_compression - signal_density_pre_compression
+ Measures how much density improves after removing stop/infantry."""
+ ss = self.session_snr
+ pre = ss.get('signal_density', 0)
+ removable = ss.get('total_stop', 0)
+ total = ss.get('total_words', 0)
+ signal = ss.get('total_signal', 0)
+ post = signal / max(total - removable, 1)
+ return round(post - pre, 4)
+
+ def rtd(self):
+ """Recursive Thematic Density (CIVITAS PPA3, Add-on IV):
+ RTD = signal words in divisions / total words in divisions
+ Measures signal concentration within thematic clusters."""
+ if not self.divisions:
+ return 0.0
+ division_words = set()
+ for div in self.divisions:
+ members = div.get('Members', '').split(', ')
+ division_words.update(m.strip() for m in members if m.strip())
+ commander = div.get('Commander', '')
+ if commander:
+ division_words.add(commander)
+ if not division_words:
+ return 0.0
+ signal_in_div = sum(1 for w in division_words
+ if w in self.classifications
+ and self.classifications[w]['classification'] == 'SIGNAL')
+ return round(signal_in_div / len(division_words), 4)
+
+ def ghost_tokens(self):
+ """Per SCSEngine [0060], [0095]:
+ Ghost Tokens = high necessity, low resonance words.
+ 'High-intent, low-coherence residues quarantined for reprocessing.'"""
+ ghosts = []
+ for word, data in self.classifications.items():
+ nec = data.get('necessity_score', 0)
+ sw = data.get('signal_weight', 0)
+ if nec >= self.GHOST_NECESSITY_MIN and sw <= self.GHOST_RESONANCE_MAX:
+ ghosts.append((word, data))
+ ghosts.sort(key=lambda x: -x[1]['necessity_score'])
+ return ghosts
+
+ def compute_all(self):
+ """Compute all PPA metrics and return as a dict."""
+ si = self.scar_index()
+ s2s = self.s2s_ratio()
+ thread_res = self.thread_resonance_scores()
+ ghosts = self.ghost_tokens()
+
+ # Session-level resonance (average of thread resonances)
+ if thread_res:
+ session_resonance = sum(r['resonance'] for r in thread_res.values()) / len(thread_res)
+ else:
+ session_resonance = 0.0
+
+ keter_threads = [(t, r) for t, r in thread_res.items() if r['keter']]
+
+ return {
+ 'resonance_scores': thread_res,
+ 'session_resonance': round(session_resonance, 4),
+ 'scar_index': si,
+ 'scar_status': self.scar_status(si),
+ 's2s_ratio': s2s,
+ 's2s_tier': self.s2s_tier(s2s),
+ 'tcr': self.tcr(),
+ 'scd': self.scd(),
+ 'rtd': self.rtd(),
+ 'ghost_tokens': ghosts,
+ 'keter_threads': keter_threads,
+ }
+
+
+# ---------------------------------------------------------------------------
+# Report Generator
+# ---------------------------------------------------------------------------
+
+class ReportGenerator:
+ """Generates SIGSYSTEM output files."""
+
+ def __init__(self, output_dir, classifications, thread_snr,
+ session_snr, word_inventory, decay_results, ppa_metrics=None):
+ self.output_dir = output_dir
+ self.classifications = classifications
+ self.thread_snr = thread_snr
+ self.session_snr = session_snr
+ self.word_inventory = word_inventory
+ self.decay = decay_results
+ self.ppa = ppa_metrics or {}
+
+ def save_word_scores(self):
+ """Write sigsystem_word_scores.csv — per-word classification + metrics."""
+ path = os.path.join(self.output_dir, 'sigsystem_word_scores.csv')
+ fieldnames = [
+ 'Word', 'Count', 'Rank', 'Signal_Weight', 'Noise_Weight',
+ 'Classification', 'Contextual_Score', 'Necessity_Score',
+ 'Decay_Score', 'Trajectory'
+ ]
+
+ # Merge word inventory data with classification data
+ rows = []
+ for w in self.word_inventory:
+ word = w['Word']
+ cls_data = self.classifications.get(word, {})
+ rows.append({
+ 'Word': word,
+ 'Count': w.get('Count', 0),
+ 'Rank': w.get('Rank', ''),
+ 'Signal_Weight': cls_data.get('signal_weight', 0),
+ 'Noise_Weight': cls_data.get('noise_weight', 0),
+ 'Classification': cls_data.get('classification', 'NOISE'),
+ 'Contextual_Score': cls_data.get('contextual_score', 0),
+ 'Necessity_Score': cls_data.get('necessity_score', 0),
+ 'Decay_Score': cls_data.get('decay_score', 0),
+ 'Trajectory': cls_data.get('trajectory', 'SPARSE'),
+ })
+
+ with open(path, 'w', newline='', encoding='utf-8') as f:
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
+ writer.writeheader()
+ writer.writerows(rows)
+ print(f" sigsystem_word_scores.csv -- {len(rows)} words scored")
+ return path
+
+ def save_thread_snr(self):
+ """Write sigsystem_thread_snr.csv — per-thread SNR breakdown."""
+ path = os.path.join(self.output_dir, 'sigsystem_thread_snr.csv')
+ fieldnames = [
+ 'Thread', 'Messages', 'Total_Words', 'Signal_Count', 'Noise_Count',
+ 'Stop_Count', 'SNR_Normalized', 'SNR_Ratio', 'SNR_dB', 'Signal_Density'
+ ]
+
+ rows = []
+ for thread, metrics in sorted(self.thread_snr.items(),
+ key=lambda x: -x[1]['snr_normalized']):
+ rows.append({
+ 'Thread': thread,
+ 'Messages': metrics['message_count'],
+ 'Total_Words': metrics['total_words'],
+ 'Signal_Count': metrics['signal_count'],
+ 'Noise_Count': metrics['noise_count'],
+ 'Stop_Count': metrics['stop_count'],
+ 'SNR_Normalized': metrics['snr_normalized'],
+ 'SNR_Ratio': metrics['snr_ratio'],
+ 'SNR_dB': metrics['snr_db'],
+ 'Signal_Density': metrics['signal_density'],
+ })
+
+ with open(path, 'w', newline='', encoding='utf-8') as f:
+ writer = csv.DictWriter(f, fieldnames=fieldnames)
+ writer.writeheader()
+ writer.writerows(rows)
+ print(f" sigsystem_thread_snr.csv -- {len(rows)} threads analyzed")
+ return path
+
+ def save_summary(self):
+ """Generate and write sigsystem_summary.txt dashboard."""
+ ss = self.session_snr
+ cls = self.classifications
+
+ # Separate signal and noise words
+ signal_words = [(w, d) for w, d in cls.items() if d['classification'] == 'SIGNAL']
+ noise_words = [(w, d) for w, d in cls.items() if d['classification'] == 'NOISE']
+ signal_words.sort(key=lambda x: -x[1]['signal_weight'])
+ noise_words.sort(key=lambda x: -x[1]['noise_weight'])
+
+ # Trajectory counts
+ trajectories = Counter(d['trajectory'] for d in cls.values())
+
+ # Thread rankings
+ thread_ranked = sorted(self.thread_snr.items(),
+ key=lambda x: -x[1]['snr_normalized'])
+
+ now = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
+ lines = []
+ lines.append('=' * 64)
+ lines.append(' SIGSYSTEM -- SIGNAL CLASSIFICATION & MEASUREMENT')
+ lines.append(' "Every word is unresolved until the system')
+ lines.append(' collapses it."')
+ lines.append('=' * 64)
+ lines.append(f' Generated: {now}')
+ lines.append(f' Engine: sigsystem.py v{VERSION}')
+ lines.append('')
+
+ # Session-level SNR
+ lines.append('=' * 64)
+ lines.append(' SESSION SNR')
+ lines.append('=' * 64)
+ lines.append(f' Total Threads: {ss["total_threads"]:,}')
+ lines.append(f' Total Messages: {ss["total_messages"]:,}')
+ lines.append(f' Total Words Processed: {ss["total_words"]:,}')
+ lines.append(f' Signal Words: {ss["total_signal"]:,}')
+ lines.append(f' Noise Words: {ss["total_noise"]:,}')
+ lines.append(f' Stop Words: {ss["total_stop"]:,}')
+ lines.append('')
+ lines.append(f' SNR (Normalized 0-1): {ss["snr_normalized"]:.4f}')
+ lines.append(f' SNR (Ratio S/N): {ss["snr_ratio"]:.4f}')
+ lines.append(f' SNR (dB): {ss["snr_db"]:.2f} dB')
+ lines.append(f' Signal Density: {ss["signal_density"]:.4f}')
+ lines.append('')
+
+ # Classification summary
+ lines.append('=' * 64)
+ lines.append(' CLASSIFICATION SUMMARY')
+ lines.append('=' * 64)
+ total_classified = len(cls)
+ lines.append(f' Total Words Classified: {total_classified:,}')
+ lines.append(f' Classified as SIGNAL: {len(signal_words):,} '
+ f'({len(signal_words)/max(total_classified,1)*100:.1f}%)')
+ lines.append(f' Classified as NOISE: {len(noise_words):,} '
+ f'({len(noise_words)/max(total_classified,1)*100:.1f}%)')
+ lines.append('')
+ lines.append(' Signal Trajectories:')
+ for traj in ['RISING', 'STABLE', 'DECLINING', 'SPARSE']:
+ count = trajectories.get(traj, 0)
+ lines.append(f' {traj:<12s} {count:>6,} words')
+ lines.append('')
+
+ # Top signal words
+ lines.append('=' * 64)
+ lines.append(' TOP 20 SIGNAL WORDS (highest signal weight)')
+ lines.append('=' * 64)
+ for i, (word, data) in enumerate(signal_words[:20], 1):
+ lines.append(
+ f' {i:>2}. {word:<22s} '
+ f'SW:{data["signal_weight"]:.3f} '
+ f'NW:{data["noise_weight"]:.3f} '
+ f'CTX:{data["contextual_score"]:.3f} '
+ f'NEC:{data["necessity_score"]:.3f} '
+ f'{data["trajectory"]}'
+ )
+ lines.append('')
+
+ # Top noise words (non-infantry, non-stop — the interesting ones)
+ interesting_noise = [(w, d) for w, d in noise_words
+ if d['rank'] not in ('Infantry',) and w not in STOP_WORDS]
+ if interesting_noise:
+ lines.append('=' * 64)
+ lines.append(' TOP 15 NOISE WORDS (highest noise weight, non-infantry)')
+ lines.append('=' * 64)
+ for i, (word, data) in enumerate(interesting_noise[:15], 1):
+ lines.append(
+ f' {i:>2}. {word:<22s} '
+ f'SW:{data["signal_weight"]:.3f} '
+ f'NW:{data["noise_weight"]:.3f} '
+ f'Rank:{data["rank"]}'
+ )
+ lines.append('')
+
+ # Thread SNR leaderboard
+ lines.append('=' * 64)
+ lines.append(' THREAD SNR LEADERBOARD (by normalized SNR)')
+ lines.append('=' * 64)
+ for i, (thread, metrics) in enumerate(thread_ranked[:20], 1):
+ lines.append(
+ f' {i:>2}. {thread:<35s} '
+ f'SNR:{metrics["snr_normalized"]:.4f} '
+ f'dB:{metrics["snr_db"]:>6.2f} '
+ f'Words:{metrics["total_words"]:>5,}'
+ )
+ lines.append('')
+
+ # Bottom threads (highest noise)
+ if len(thread_ranked) > 5:
+ lines.append('=' * 64)
+ lines.append(' BOTTOM 5 THREADS (lowest SNR — highest noise)')
+ lines.append('=' * 64)
+ for i, (thread, metrics) in enumerate(reversed(thread_ranked[-5:]), 1):
+ lines.append(
+ f' {i:>2}. {thread:<35s} '
+ f'SNR:{metrics["snr_normalized"]:.4f} '
+ f'dB:{metrics["snr_db"]:>6.2f} '
+ f'Words:{metrics["total_words"]:>5,}'
+ )
+ lines.append('')
+
+ # Compression opportunity
+ lines.append('=' * 64)
+ lines.append(' COMPRESSION ANALYSIS')
+ lines.append('=' * 64)
+ removable = ss['total_stop'] + sum(1 for _, d in noise_words
+ if d['rank'] == 'Infantry')
+ lines.append(f' Stop words (pure scaffolding): {ss["total_stop"]:,}')
+ lines.append(f' Est. removable words: {removable:,}')
+ lines.append(f' Compression potential: '
+ f'{removable/max(ss["total_words"],1)*100:.1f}% reducible')
+ lines.append(f' Post-compression signal density: '
+ f'{ss["total_signal"]/max(ss["total_words"]-removable,1):.4f}')
+ lines.append('')
+
+ # PPA Metrics (SCSEngine + CIVITAS equations)
+ if self.ppa:
+ lines.append('=' * 64)
+ lines.append(' SCS ENGINE METRICS (from PPA equations)')
+ lines.append('=' * 64)
+
+ lines.append(f' Session Resonance Score: {self.ppa.get("session_resonance", 0):.4f}')
+ lines.append(f' Scar Index: {self.ppa.get("scar_index", 0):.4f} '
+ f'[{self.ppa.get("scar_status", "?")}]')
+ lines.append(f' S²S Ratio: {self.ppa.get("s2s_ratio", 0):.4f} '
+ f'[{self.ppa.get("s2s_tier", "?")}]')
+ lines.append(f' Token Compression Ratio: {self.ppa.get("tcr", 0):.4f}')
+ lines.append(f' Semantic Compression Delta: {self.ppa.get("scd", 0):.4f}')
+ lines.append(f' Recursive Thematic Density: {self.ppa.get("rtd", 0):.4f}')
+ lines.append('')
+
+ # Scar Index interpretation
+ si = self.ppa.get('scar_index', 0)
+ lines.append(' Scar Index Ranges:')
+ lines.append(f' 0.00-0.20 HEALTHY {"<-- HERE" if si <= 0.20 else ""}')
+ lines.append(f' 0.20-0.50 WARNING {"<-- HERE" if 0.20 < si <= 0.50 else ""}')
+ lines.append(f' 0.50-0.90 CRITICAL {"<-- HERE" if 0.50 < si < 0.90 else ""}')
+ lines.append(f' 0.90-1.00 COLLAPSE {"<-- HERE" if si >= 0.90 else ""}')
+ lines.append('')
+
+ # Keter threads
+ keter = self.ppa.get('keter_threads', [])
+ if keter:
+ lines.append(f' KETER-LEVEL THREADS (resonance >= 0.94):')
+ for thread, res in keter:
+ lines.append(f' {thread:<35s} RES:{res["resonance"]:.4f}')
+ lines.append('')
+
+ # Top resonance threads
+ thread_res = self.ppa.get('resonance_scores', {})
+ if thread_res:
+ lines.append(' THREAD RESONANCE SCORES (top 10):')
+ sorted_res = sorted(thread_res.items(), key=lambda x: -x[1]['resonance'])
+ for thread, res in sorted_res[:10]:
+ keter_flag = ' [KETER]' if res['keter'] else ''
+ lines.append(
+ f' {thread:<35s} '
+ f'RES:{res["resonance"]:.4f} '
+ f'DRIFT:{res["drift_delta"]:+.4f}{keter_flag}'
+ )
+ lines.append('')
+
+ # Ghost tokens
+ ghosts = self.ppa.get('ghost_tokens', [])
+ if ghosts:
+ lines.append(f' GHOST TOKENS ({len(ghosts)} detected):')
+ lines.append(' "High-intent, low-coherence residues quarantined')
+ lines.append(' for reprocessing." -- SCSEngine [0095]')
+ for word, data in ghosts[:15]:
+ lines.append(
+ f' {word:<22s} '
+ f'NEC:{data["necessity_score"]:.3f} '
+ f'SW:{data["signal_weight"]:.3f} '
+ f'Rank:{data["rank"]}'
+ )
+ lines.append('')
+
+ lines.append('=' * 64)
+ lines.append(' "Words are not inherently signal or noise.')
+ lines.append(' Their classification emerges only in-context')
+ lines.append(' during live system use."')
+ lines.append(' -- SIGSYSTEM Foundation Spec')
+ lines.append('=' * 64)
+
+ report = '\n'.join(lines)
+ path = os.path.join(self.output_dir, 'sigsystem_summary.txt')
+ with open(path, 'w', encoding='utf-8') as f:
+ f.write(report)
+ print(f" sigsystem_summary.txt -- dashboard generated")
+ return path
+
+ def save_all(self):
+ """Generate all output files."""
+ paths = []
+ paths.append(self.save_word_scores())
+ paths.append(self.save_thread_snr())
+ paths.append(self.save_summary())
+ return paths
+
+
+# ---------------------------------------------------------------------------
+# CLI & Main
+# ---------------------------------------------------------------------------
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='SIGSYSTEM — Signal Classification & Measurement Subsystem\n'
+ '"Every word is unresolved until the system collapses it."',
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog='Examples:\n'
+ ' python sigsystem.py --run-dir ../signal_army/runs/run_2026-03-02_06-03-50/\n'
+ ' python sigsystem.py --run-dir --output-dir ./runs/\n'
+ )
+ parser.add_argument('--run-dir', metavar='DIR', required=True,
+ help='Path to Signal Army run directory (must contain CSVs)')
+ parser.add_argument('--output-dir', metavar='DIR',
+ help='Output directory (default: ./runs/ with timestamp)')
+ parser.add_argument('-v', '--verbose', action='store_true',
+ help='Show detailed progress')
+
+ args = parser.parse_args()
+
+ if not os.path.isdir(args.run_dir):
+ parser.error(f'Run directory not found: {args.run_dir}')
+
+ # Output directory
+ run_stamp = datetime.now().strftime('%Y-%m-%d_%H-%M-%S')
+ if args.output_dir:
+ base_dir = args.output_dir
+ else:
+ base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'runs')
+ output_dir = os.path.join(base_dir, f'run_{run_stamp}')
+ os.makedirs(output_dir, exist_ok=True)
+
+ # Header
+ print(f'\nSIGSYSTEM — Signal Classification & Measurement v{VERSION}')
+ print('=' * 56)
+ print()
+
+ # ---- Stage 1: Input Evaluation ----
+ print('[1/5] Loading Signal Army data...')
+ loader = DataLoader(args.run_dir)
+ word_inventory = loader.load_word_inventory()
+ phrase_inventory = loader.load_phrase_inventory()
+ division_inventory = loader.load_division_inventory()
+ messages = loader.load_messages()
+
+ if not word_inventory:
+ print('ERROR: No word inventory found. Cannot proceed.', file=sys.stderr)
+ sys.exit(1)
+
+ print(f' {len(word_inventory):,} words loaded')
+ print(f' {len(division_inventory)} divisions loaded')
+ print(f' {len(messages):,} messages loaded')
+ print()
+
+ # ---- Stage 2: Contextual Assessment ----
+ print('[2/5] Running contextual assessment...')
+ assessor = ContextualAssessor(word_inventory, division_inventory)
+ contextual_scores = assessor.score_all()
+ avg_ctx = sum(contextual_scores.values()) / max(len(contextual_scores), 1)
+ print(f' Avg contextual score: {avg_ctx:.4f}')
+ print()
+
+ # ---- Stage 3: Structural Necessity Evaluation ----
+ print('[3/5] Evaluating compression necessity...')
+ evaluator = NecessityEvaluator(word_inventory, division_inventory)
+ necessity_scores = evaluator.score_all()
+ avg_nec = sum(necessity_scores.values()) / max(len(necessity_scores), 1)
+ print(f' Avg necessity score: {avg_nec:.4f}')
+ print()
+
+ # ---- Stage 5 (before 4): Longitudinal Tracking ----
+ print('[4/5] Computing signal decay rates...')
+ tracker = DecayTracker(word_inventory, messages)
+ decay_results = tracker.compute_all()
+ trajectories = Counter(t for _, t in decay_results.values())
+ for traj in ['RISING', 'STABLE', 'DECLINING', 'SPARSE']:
+ print(f' {traj:<12s} {trajectories.get(traj, 0):>5,} words')
+ print()
+
+ # ---- Stage 4: Classification ----
+ print('[5/5] Classifying signal vs noise...')
+ classifier = SignalClassifier(word_inventory, contextual_scores,
+ necessity_scores, decay_results)
+ classifications = classifier.classify_all()
+
+ signal_count = sum(1 for d in classifications.values() if d['classification'] == 'SIGNAL')
+ noise_count = sum(1 for d in classifications.values() if d['classification'] == 'NOISE')
+ print(f' SIGNAL: {signal_count:,} words')
+ print(f' NOISE: {noise_count:,} words')
+ print()
+
+ # ---- SNR Engine ----
+ print('Computing SNR metrics...')
+ engine = SNREngine(classifications, messages)
+ thread_snr = engine.analyze_by_thread()
+ session_snr = engine.analyze_session(thread_snr)
+
+ print(f' Session SNR (normalized): {session_snr["snr_normalized"]:.4f}')
+ print(f' Session SNR (dB): {session_snr["snr_db"]:.2f} dB')
+ print(f' Signal density: {session_snr["signal_density"]:.4f}')
+ print()
+
+ # ---- PPA Metrics Engine ----
+ print('Computing PPA metrics (SCSEngine + CIVITAS equations)...')
+ ppa_engine = PPAMetrics(classifications, thread_snr, session_snr, division_inventory)
+ ppa_metrics = ppa_engine.compute_all()
+
+ print(f' Resonance Score: {ppa_metrics["session_resonance"]:.4f}')
+ print(f' Scar Index: {ppa_metrics["scar_index"]:.4f} [{ppa_metrics["scar_status"]}]')
+ print(f' S²S Ratio: {ppa_metrics["s2s_ratio"]:.4f} [{ppa_metrics["s2s_tier"]}]')
+ print(f' Ghost Tokens: {len(ppa_metrics["ghost_tokens"])}')
+ keter_count = len(ppa_metrics['keter_threads'])
+ if keter_count:
+ print(f' Keter Threads: {keter_count}')
+ print()
+
+ # ---- Generate Reports ----
+ print('Generating reports...')
+ reporter = ReportGenerator(output_dir, classifications, thread_snr,
+ session_snr, word_inventory, decay_results,
+ ppa_metrics=ppa_metrics)
+ reporter.save_all()
+ print()
+
+ # Quick stats footer
+ print('=' * 56)
+ print('SIGSYSTEM RESULTS:')
+ print(f' Words classified: {len(classifications):,}')
+ print(f' Signal words: {signal_count:,}')
+ print(f' Noise words: {noise_count:,}')
+ print(f' Session SNR: {session_snr["snr_normalized"]:.4f} '
+ f'({session_snr["snr_db"]:.2f} dB)')
+ print(f' Resonance: {ppa_metrics["session_resonance"]:.4f}')
+ print(f' Scar Index: {ppa_metrics["scar_index"]:.4f} [{ppa_metrics["scar_status"]}]')
+ print(f' S²S Ratio: {ppa_metrics["s2s_ratio"]:.4f}')
+ print(f' Threads analyzed: {len(thread_snr)}')
+ print('=' * 56)
+ print(f'\nAll files written to: {output_dir}/')
+ print()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/moses-sampler/processors/sigtoken/SIGTOKEN_CONTEXT.md b/moses-sampler/processors/sigtoken/SIGTOKEN_CONTEXT.md
new file mode 100644
index 0000000..d56f65a
--- /dev/null
+++ b/moses-sampler/processors/sigtoken/SIGTOKEN_CONTEXT.md
@@ -0,0 +1,138 @@
+# SigToken System — Context & Lineage
+
+**Component:** C-0005
+**Version:** 1.0
+**Created:** 2026-03-06
+**Entity:** Ello Cello LLC
+**File:** `sigtoken_sys.py`
+
+---
+
+## What This Is
+
+SigToken is the third tool in the MOS²ES measurement stack, sitting between Signal Army and SIGSYSTEM.
+
+```
+Signal Army → SigToken → SIGSYSTEM
+(word inventory) (message-level (session SNR +
+(rank + frequency) commitment) decay tracking)
+```
+
+It implements **Component C-0005: Contextual Token SNR Classification** — originally specified October 1, 2025 in five files:
+
+| Source File | What It Specified |
+|-------------|------------------|
+| `classification_logic.md` | Core doctrine: signal/noise is not word identity, it is usage role |
+| `ppa_C0005.md` | PPA claim: dynamic token classification, upstream of leaderboard + vault |
+| `vault.meta.json` | Component manifest: C-0005, lineage to scs-engine, metrics SNR/SDR/CID |
+| `signal_use_case.md` | Example: "The MOS²ES system encodes lineage into every keystroke" |
+| `noise_use_case.md` | Example: "basically, like, you know..." — and the dual-weight insight: 'just' is noise in filler, signal in emphasis |
+
+---
+
+## The Core Insight (Oct 1 2025)
+
+From `classification_logic.md`:
+
+> "Words and tokens are not inherently signal or noise. Their classification depends entirely on contextual function, relational placement, frequency, redundancy or originality, and contribution to compression and resonance."
+
+From `noise_use_case.md` — the dual-weight example:
+
+> "Same token 'just' is noise here, but could be signal in emotional emphasis elsewhere."
+
+This is the same insight that became SIGSYSTEM's epigraph five months later:
+> *"Every word is unresolved until the system collapses it."*
+
+SigToken is the implementation of that collapse — at the message level.
+
+---
+
+## Three-Tier Token Taxonomy
+
+Derived from `signal_use_case.md` which identified three distinct categories:
+
+| Tier | Description | Example |
+|------|-------------|---------|
+| **Signal** | High semantic density, intent-bearing, load-carrying | `MOS²ES`, `encodes`, `lineage`, `compression` |
+| **Scaffolding** | Neutral structure — not signal, not noise | `the`, `into`, `every` |
+| **Noise** | Low contribution, filler, structurally removable | `basically`, `like`, `just` (in filler context) |
+
+Note: Scaffolding is its own tier — not noise. This matters for SNR calculation. Stop words are not penalized.
+
+---
+
+## What SigToken Measures That Signal Army Doesn't
+
+Signal Army ranks words by **frequency + cross-thread survivability**.
+SIGSYSTEM classifies words by **rank + contextual score + necessity + decay**.
+SigToken classifies **tokens by usage role in a specific message**.
+
+The key difference: SigToken can score the same word differently in two different messages. Signal Army and SIGSYSTEM assign one score per word across the whole corpus. SigToken assigns one score per token per position per message.
+
+---
+
+## Commitment Score
+
+The new metric introduced by SigToken.
+
+**Definition:** Fraction of a message's tokens that cannot be removed without collapsing the message's irreducible meaning kernel.
+
+- High commitment (≥ 0.50) = message is dense, nearly every token is load-bearing
+- Low commitment (< 0.20) = message is mostly removable, low kernel density
+- Average across corpus (first run, 7,823 messages): **0.1616**
+
+This is the bridge to the Conservation Law paper (`main.tex`):
+The paper defines Commitment C(S) as the minimal identity-preserving content invariant under loss-inducing transformations.
+The commitment score is the empirical measurement of that kernel.
+
+---
+
+## First Run Results (2026-03-06)
+
+Run against Signal Army corpus (7,823 messages, 185 conversations):
+
+| Metric | Value |
+|--------|-------|
+| Total tokens | 455,740 |
+| Signal tokens | 13,119 |
+| Noise tokens | 268,470 |
+| Scaffolding tokens | 174,151 |
+| SNR normalized | 0.0288 |
+| Avg commitment | 0.1616 |
+| High commitment messages (≥0.50) | 17 |
+| Low commitment messages (<0.20) | 6,962 |
+
+**Top thread:** "SNR calculation request" — SNR 0.3000, commitment 0.3606
+**Note:** Domain anchors seeded from ~30 hardcoded Officer-Class words. Full 438-Officer load will sharpen scores significantly.
+
+---
+
+## What's Next
+
+1. **Dynamic anchor loading** — pass `--word-inventory word_inventory.csv` to load all Officer-Class words as domain anchors at runtime instead of using the hardcoded list
+2. **Message-level commitment leaderboard** — rank individual messages not just threads
+3. **Bridge to SIGSYSTEM** — feed commitment scores upstream as an additional weight in SIGSYSTEM's composite signal score
+
+---
+
+## File Structure
+
+```
+sigtoken/
+├── SIGTOKEN_CONTEXT.md ← this file
+├── sigtoken_sys.py ← main engine (C-0005)
+└── runs/
+ └── run_YYYY-MM-DD_HH-MM-SS/
+ ├── sigtoken_summary.txt
+ └── sigtoken_summary.json
+```
+
+---
+
+## Lineage
+
+- **Origin spec:** `vault.meta.json` created 2025-10-01T16:41:58.324312Z
+- **Parent component:** scs-engine
+- **Related metrics:** SNR, SDR, CID
+- **Built:** 2026-03-06 (this session)
+- **Entity:** Ello Cello LLC | MO§ES™ trademark
diff --git a/moses-sampler/processors/sigtoken/sigtoken_recursive.py b/moses-sampler/processors/sigtoken/sigtoken_recursive.py
new file mode 100644
index 0000000..001b730
--- /dev/null
+++ b/moses-sampler/processors/sigtoken/sigtoken_recursive.py
@@ -0,0 +1,667 @@
+#!/usr/bin/env python3
+"""
+SigToken Recursive Scorer — Thread-Aware Two-Pass Classification
+Component C-0005 | v2.1 | Ello Cello LLC
+
+The flat scorer (v1, v2) treats all messages as one undifferentiated blob.
+This scorer uses the thread structure the corpus already has.
+
+TWO-PASS APPROACH:
+ Pass 1 — Thread-local IDF
+ For each thread: compute which words are load-bearing WITHIN that thread.
+ A word that appears in every message of the thread is scaffolding for that thread.
+ A word that appears in 1-2 messages of a long thread is a signal event.
+ Result: per-thread word signal scores.
+
+ Pass 2 — Corpus validation
+ A word's final score = (thread-local signal) × (corpus-level thread penetration).
+ Words that score high locally AND appear across multiple high-SNR threads
+ are true domain anchors — promoted regardless of static DOMAIN_ANCHORS list.
+ Words that score high locally but only appear in one thread stay local.
+ Words in the static DOMAIN_ANCHORS list get a floor boost.
+
+WHY THIS IS DIFFERENT:
+ "compression" in a file-format thread = corpus-common, thread-local noise
+ "compression" in SCS architecture thread = thread-local signal, corpus anchor
+ The flat scorer can't distinguish these. The recursive scorer can.
+
+Usage:
+ python sigtoken_recursive.py --messages flattened_messages.csv
+ python sigtoken_recursive.py --messages flattened_messages.csv --word-inventory word_inventory.csv
+"""
+
+import argparse
+import csv
+import json
+import math
+import os
+import re
+from collections import Counter, defaultdict
+from datetime import datetime, timezone
+
+csv.field_size_limit(10 * 1024 * 1024)
+
+VERSION = "2.1-recursive"
+COMPONENT = "C-0005"
+
+
+# ---------------------------------------------------------------------------
+# Static taxonomy (seed layer — corpus validation refines from here)
+# ---------------------------------------------------------------------------
+
+SCAFFOLDING = frozenset({
+ 'the', 'a', 'an', 'and', 'or', 'but', 'so', 'if', 'of', 'in', 'on',
+ 'at', 'to', 'for', 'from', 'with', 'by', 'about', 'as', 'is', 'are',
+ 'was', 'were', 'be', 'been', 'being', 'am', 'have', 'has', 'had',
+ 'having', 'do', 'does', 'did', 'doing', 'done', 'will', 'would',
+ 'shall', 'should', 'can', 'could', 'may', 'might', 'must',
+ 'i', 'me', 'my', 'mine', 'myself', 'you', 'your', 'yours',
+ 'he', 'him', 'his', 'she', 'her', 'hers', 'it', 'its',
+ 'we', 'us', 'our', 'they', 'them', 'their',
+ 'who', 'whom', 'which', 'what', 'that', 'this', 'these', 'those',
+ 'into', 'through', 'between', 'after', 'before', 'during',
+ 'without', 'within', 'upon', 'above', 'below', 'under', 'over',
+ 'because', 'although', 'though', 'while', 'when', 'where', 'whether',
+ 'unless', 'since', 'than', 'also', 'too', 'then', 'now', 'here', 'there',
+ 'some', 'any', 'all', 'each', 'every', 'both', 'either', 'neither',
+ 'one', 'two', 'three', 'first', 'second', 'third',
+})
+
+FILLER_WORDS = frozenset({
+ 'basically', 'literally', 'actually', 'honestly', 'obviously',
+ 'clearly', 'simply', 'totally', 'absolutely', 'definitely',
+ 'certainly', 'probably', 'maybe', 'perhaps', 'really', 'very',
+ 'quite', 'rather', 'pretty', 'just', 'only', 'even', 'still',
+ 'already', 'always', 'often', 'sometimes', 'usually', 'generally',
+ 'lol', 'ok', 'okay', 'yeah', 'yep', 'nope', 'um', 'uh', 'ah',
+ 'oh', 'well', 'like', 'so', 'right', 'sure', 'anyway', 'kind',
+ 'sort', 'thing', 'things', 'stuff', 'bit', 'lot', 'way', 'ways',
+ 'gonna', 'wanna', 'gotta', 'etc', 'eg', 'ie',
+})
+
+# Static seed anchors — corpus validation can promote additional words to this level
+DOMAIN_ANCHORS_SEED = {
+ 'signal', 'noise', 'compression', 'token', 'system', 'sigsystem',
+ 'mos2es', 'moses', 'sigrank', 'lineage', 'vault',
+ 'mediator', 'resonance', 'entropy', 'snr', 'drift', 'decay',
+ 'commitment', 'kernel', 'fracto', 'abba', 'scs', 'ppa', 'keter',
+ 'integrity', 'classification', 'semantic', 'recursive', 'anchor',
+ 'ignition', 'collapse', 'threshold', 'quantify', 'measurement',
+ 'governance', 'sovereign', 'artifact', 'leaderboard', 'dual',
+ 'weight', 'density', 'trajectory', 'conservation', 'invariant',
+}
+
+
+# ---------------------------------------------------------------------------
+# Tokenizer
+# ---------------------------------------------------------------------------
+
+def tokenize(text):
+ text = text.lower()
+ text = re.sub(r'mo§es™?|moses™', 'moses', text)
+ text = re.sub(r'mos²es|mos2es', 'mos2es', text)
+ text = re.sub(r"[^\w\s'\-]", ' ', text)
+ tokens = text.split()
+ result = []
+ for i, t in enumerate(tokens):
+ t = t.strip("'-_")
+ if not t or t.isdigit() or (len(t) == 1 and t not in ('i', 'a')):
+ continue
+ result.append((i, t))
+ return result
+
+
+def parse_timestamp(ts_str):
+ if not ts_str:
+ return 0
+ try:
+ return float(ts_str)
+ except (ValueError, TypeError):
+ pass
+ try:
+ clean = re.sub(r'[+-]\d{2}:\d{2}$', '', str(ts_str)).rstrip('Z')
+ return datetime.fromisoformat(clean).replace(tzinfo=timezone.utc).timestamp()
+ except Exception:
+ return 0
+
+
+# ---------------------------------------------------------------------------
+# Pass 1: Thread-local TF-IDF
+# For each thread, compute which words carry information WITHIN that thread.
+# A word that appears in most messages of the thread = thread-scaffolding.
+# A word that appears rarely but in high-information positions = thread-signal.
+# ---------------------------------------------------------------------------
+
+class ThreadProfile:
+ """Vocabulary and signal profile for a single thread."""
+
+ def __init__(self, name):
+ self.name = name
+ self.messages = [] # list of tokenized message word lists
+ self.raw_texts = []
+ self.timestamps = []
+ self.roles = Counter()
+
+ def add_message(self, text, role='unknown', timestamp=0):
+ tokens = [t for _, t in tokenize(text)]
+ self.messages.append(tokens)
+ self.raw_texts.append(text)
+ self.roles[role] += 1
+ if timestamp:
+ self.timestamps.append(timestamp)
+
+ def compute_local_idf(self):
+ """
+ Within-thread IDF: words that appear in fewer messages relative
+ to thread length carry more local signal.
+ Returns dict: word -> local_idf_score (0.0 to 1.0)
+ """
+ n_messages = len(self.messages)
+ if n_messages == 0:
+ return {}
+
+ # Document frequency within thread
+ doc_freq = Counter()
+ for msg_tokens in self.messages:
+ for word in set(msg_tokens):
+ doc_freq[word] += 1
+
+ local_idf = {}
+ for word, df in doc_freq.items():
+ if word in SCAFFOLDING or word in FILLER_WORDS:
+ local_idf[word] = 0.0
+ continue
+ # Standard IDF — inverse document frequency within thread
+ idf = math.log((n_messages + 1) / (df + 1)) + 1
+ # Normalize to 0-1 range (max possible IDF = log(n+1) + 1)
+ max_idf = math.log(n_messages + 1) + 1
+ local_idf[word] = round(idf / max_idf, 4)
+
+ return local_idf
+
+ def snr_estimate(self, dynamic_anchors):
+ """Quick SNR estimate for this thread — used for corpus validation weighting."""
+ all_tokens = [t for msg in self.messages for t in msg]
+ if not all_tokens:
+ return 0.5
+ signal = sum(1 for t in all_tokens
+ if t in dynamic_anchors and t not in SCAFFOLDING and t not in FILLER_WORDS)
+ noise = sum(1 for t in all_tokens if t in FILLER_WORDS)
+ active = len([t for t in all_tokens if t not in SCAFFOLDING])
+ if active == 0:
+ return 0.5
+ return (signal - noise * 0.5) / active
+
+
+# ---------------------------------------------------------------------------
+# Pass 2: Corpus-level validation
+# Build a dynamic anchor set from bottom up — words that score high locally
+# across multiple threads are promoted to corpus anchors.
+# ---------------------------------------------------------------------------
+
+class CorpusValidator:
+ """
+ Builds a dynamic signal map across all threads.
+
+ For each word: computes a corpus_signal_score based on:
+ - How many threads it appears in with high local IDF
+ - Whether those threads have high estimated SNR
+ - Whether it appears in the static DOMAIN_ANCHORS_SEED
+
+ Words promoted to dynamic_anchors get the same floor as static seeds.
+ """
+
+ def __init__(self, thread_profiles, static_anchors):
+ self.threads = thread_profiles
+ self.static_anchors = static_anchors
+ self.dynamic_anchors = set(static_anchors)
+ self.word_corpus_scores = {}
+
+ def build(self, promotion_threshold=0.35, min_threads=3):
+ """
+ Build corpus signal map.
+ Words with corpus_score >= promotion_threshold in >= min_threads
+ are promoted to dynamic_anchors.
+ """
+ print(f" Corpus validation: {len(self.threads)} threads...")
+
+ # First pass: get rough SNR for each thread using static anchors
+ thread_snr = {}
+ for name, profile in self.threads.items():
+ thread_snr[name] = profile.snr_estimate(self.static_anchors)
+
+ # Collect local IDF scores per word across threads
+ word_weighted_appearances = defaultdict(list) # word -> [(local_idf, thread_snr)]
+ for name, profile in self.threads.items():
+ local_idf = profile.compute_local_idf()
+ t_snr = max(thread_snr[name], 0.01) # floor at 0.01
+ for word, idf_score in local_idf.items():
+ if idf_score > 0:
+ word_weighted_appearances[word].append((idf_score, t_snr))
+
+ # Compute corpus score for each word
+ promoted = 0
+ for word, appearances in word_weighted_appearances.items():
+ if word in SCAFFOLDING or word in FILLER_WORDS:
+ self.word_corpus_scores[word] = 0.0
+ continue
+
+ n_threads = len(appearances)
+ # Weighted average: local_idf weighted by thread SNR
+ weighted_sum = sum(idf * snr for idf, snr in appearances)
+ weight_total = sum(snr for _, snr in appearances)
+ avg_weighted_idf = weighted_sum / max(weight_total, 0.01)
+
+ # Thread penetration factor: more threads = more validated
+ # But use log scale — appearing in 100 threads vs 50 isn't 2x better
+ penetration = math.log(n_threads + 1) / math.log(len(self.threads) + 1)
+
+ corpus_score = avg_weighted_idf * 0.60 + penetration * 0.40
+
+ # Static anchor boost — seeds always get a floor
+ if word in self.static_anchors:
+ corpus_score = max(corpus_score, 0.65)
+
+ self.word_corpus_scores[word] = round(corpus_score, 4)
+
+ # Promotion: high corpus score across enough threads
+ if corpus_score >= promotion_threshold and n_threads >= min_threads:
+ self.dynamic_anchors.add(word)
+ promoted += 1
+
+ print(f" {promoted} words promoted to dynamic anchors "
+ f"({len(self.dynamic_anchors)} total including seeds).")
+ return self.dynamic_anchors
+
+ def get_word_score(self, word):
+ """Return corpus signal score for a word (0.0 to 1.0)."""
+ if word in SCAFFOLDING:
+ return 0.0
+ if word in FILLER_WORDS:
+ return 0.05
+ return self.word_corpus_scores.get(word, 0.20)
+
+
+# ---------------------------------------------------------------------------
+# Thread-aware message scorer
+# Uses both thread-local IDF and corpus validation scores
+# ---------------------------------------------------------------------------
+
+class RecursiveSigTokenScorer:
+ """
+ Scores tokens using thread-local context + corpus validation.
+ Each message is scored with knowledge of:
+ 1. Its thread's local vocabulary distribution
+ 2. The corpus-wide signal map built from all threads
+ """
+
+ def __init__(self, corpus_validator, thread_profiles):
+ self.validator = corpus_validator
+ self.thread_profiles = thread_profiles
+ # Pre-compute local IDF for each thread
+ self.thread_local_idf = {
+ name: profile.compute_local_idf()
+ for name, profile in thread_profiles.items()
+ }
+
+ def score_token(self, token, position, message_tokens, thread_name):
+ if token in SCAFFOLDING:
+ return (0.05, 0.95, 'scaffolding', 'neutral_structure')
+
+ # Get both local and corpus scores
+ local_idf = self.thread_local_idf.get(thread_name, {}).get(token, 0.20)
+ corpus_score = self.validator.get_word_score(token)
+ is_dynamic_anchor = token in self.validator.dynamic_anchors
+
+ if token in FILLER_WORDS:
+ # Filler can be elevated if surrounded by dynamic anchors
+ pos_idx = next((i for i, (p, t) in enumerate(message_tokens) if p == position), 0)
+ window = [t for _, t in message_tokens[max(0, pos_idx-2):pos_idx+3]]
+ anchor_neighbors = sum(1 for w in window if w in self.validator.dynamic_anchors)
+ if anchor_neighbors >= 2:
+ return (0.35, 0.65, 'noise', 'filler_emphasis')
+ return (0.08, 0.92, 'noise', 'filler_pure')
+
+ # Dynamic anchor: promoted through corpus validation OR static seed
+ if is_dynamic_anchor:
+ base = 0.75
+ # Local IDF boost: if this word is also rare in its own thread, it's
+ # doing real work right here, not just a general domain term
+ local_boost = local_idf * 0.15
+ # Cluster bonus: nearby anchors
+ pos_idx = next((i for i, (p, t) in enumerate(message_tokens) if p == position), 0)
+ window = [t for _, t in message_tokens[max(0, pos_idx-2):pos_idx+3]]
+ cluster = sum(1 for w in window if w in self.validator.dynamic_anchors and w != token)
+ cluster_bonus = min(cluster * 0.02, 0.08)
+ sw = min(base + local_boost + cluster_bonus, 0.95)
+ nw = round(1.0 - sw, 4)
+ return (sw, nw, 'signal', 'dynamic_anchor')
+
+ # General word: composite of local IDF + corpus score + position
+ total_tokens = len(message_tokens)
+ normalized_pos = position / max(total_tokens, 1)
+ position_score = 0.60 - normalized_pos * 0.20
+
+ sw = round(
+ local_idf * 0.45 +
+ corpus_score * 0.35 +
+ position_score * 0.20,
+ 4
+ )
+ sw = min(max(sw, 0.08), 0.75)
+ nw = round(1.0 - sw, 4)
+
+ tier = 'signal' if sw >= 0.38 else 'noise'
+ return (sw, nw, tier, 'recursive_local_corpus')
+
+ def score_message(self, text, thread_name):
+ tokens = tokenize(text)
+ if not tokens:
+ return None
+
+ scored = []
+ for pos, token in tokens:
+ sw, nw, tier, reason = self.score_token(token, pos, tokens, thread_name)
+ scored.append({
+ 'token': token, 'position': pos,
+ 'SW': sw, 'NW': nw, 'tier': tier, 'reason': reason,
+ })
+
+ signal_tokens = [s for s in scored if s['tier'] == 'signal']
+ noise_tokens = [s for s in scored if s['tier'] == 'noise']
+ scaffolding_tokens = [s for s in scored if s['tier'] == 'scaffolding']
+
+ signal_count = len(signal_tokens)
+ noise_count = len(noise_tokens)
+ active = signal_count + noise_count
+
+ snr_ratio = signal_count / max(noise_count, 1)
+ snr_db = 10 * math.log10(snr_ratio) if snr_ratio > 0 else -99.0
+ snr_normalized = signal_count / max(active, 1)
+
+ # Commitment — same length-irreducibility formula as v2
+ non_scaffold = [s for s in scored if s['tier'] != 'scaffolding']
+ if non_scaffold:
+ signal_only = [s for s in non_scaffold if s['tier'] == 'signal']
+ avg_signal_sw = (
+ sum(s['SW'] for s in signal_only) / len(signal_only)
+ if signal_only else 0.0
+ )
+ signal_purity = signal_count / max(active, 1)
+
+ if active <= 3:
+ length_factor = 1.0
+ elif active <= 8:
+ length_factor = 0.90
+ elif active <= 20:
+ length_factor = 0.75
+ elif active <= 50:
+ length_factor = 0.60
+ else:
+ length_factor = max(0.40, 0.60 - (active - 50) * 0.001)
+
+ commitment = round(
+ signal_purity * 0.50 +
+ avg_signal_sw * 0.30 +
+ length_factor * 0.20,
+ 4
+ )
+ else:
+ commitment = 0.0
+
+ return {
+ 'text': text[:120] + '...' if len(text) > 120 else text,
+ 'total_tokens': len(scored),
+ 'signal_count': signal_count,
+ 'noise_count': noise_count,
+ 'scaffolding_count': len(scaffolding_tokens),
+ 'snr_normalized': round(snr_normalized, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'commitment_score': commitment,
+ 'tokens': scored,
+ }
+
+
+# ---------------------------------------------------------------------------
+# Full corpus processor
+# ---------------------------------------------------------------------------
+
+class RecursiveSessionProcessor:
+
+ def __init__(self, messages):
+ self.messages = messages
+ # Build thread profiles
+ print(" Building thread profiles (Pass 1)...")
+ self.thread_profiles = defaultdict(lambda: ThreadProfile(''))
+ for msg in messages:
+ thread = msg.get('thread', 'unknown')
+ if thread not in self.thread_profiles:
+ self.thread_profiles[thread] = ThreadProfile(thread)
+ self.thread_profiles[thread].add_message(
+ msg.get('content', ''),
+ role=msg.get('role', 'unknown'),
+ timestamp=parse_timestamp(msg.get('create_time', ''))
+ )
+ print(f" {len(self.thread_profiles)} threads profiled.")
+
+ def build_corpus(self, static_anchors):
+ # Pass 2: corpus validation
+ print(" Running corpus validation (Pass 2)...")
+ self.validator = CorpusValidator(self.thread_profiles, static_anchors)
+ self.dynamic_anchors = self.validator.build()
+ self.scorer = RecursiveSigTokenScorer(self.validator, self.thread_profiles)
+ return self.dynamic_anchors
+
+ def process(self):
+ results = []
+ for msg in self.messages:
+ content = msg.get('content', '')
+ if not content or len(content.strip()) < 3:
+ continue
+ thread = msg.get('thread', 'unknown')
+ scored = self.scorer.score_message(content, thread)
+ if scored:
+ scored['role'] = msg.get('role', 'unknown')
+ scored['thread'] = thread
+ scored['create_time'] = msg.get('create_time', '')
+ results.append(scored)
+ return [r for r in results if r]
+
+ def summarize(self, results):
+ if not results:
+ return {}
+
+ total_tokens = sum(r['total_tokens'] for r in results)
+ total_signal = sum(r['signal_count'] for r in results)
+ total_noise = sum(r['noise_count'] for r in results)
+ total_scaffold = sum(r['scaffolding_count'] for r in results)
+ active = total_signal + total_noise
+
+ snr_ratio = total_signal / max(total_noise, 1)
+ snr_db = 10 * math.log10(snr_ratio) if snr_ratio > 0 else -99.0
+ snr_normalized = total_signal / max(active, 1)
+
+ commitments = [r['commitment_score'] for r in results]
+ avg_commitment = sum(commitments) / len(commitments)
+ high_commitment = [r for r in results if r['commitment_score'] >= 0.50]
+ low_commitment = [r for r in results if r['commitment_score'] < 0.20]
+
+ thread_data = defaultdict(lambda: {
+ 'signal': 0, 'noise': 0, 'messages': 0, 'commitment': [], 'times': []
+ })
+ for r in results:
+ t = r['thread']
+ thread_data[t]['signal'] += r['signal_count']
+ thread_data[t]['noise'] += r['noise_count']
+ thread_data[t]['messages'] += 1
+ thread_data[t]['commitment'].append(r['commitment_score'])
+ if r.get('create_time'):
+ thread_data[t]['times'].append(r['create_time'])
+
+ thread_snr = []
+ for thread, data in thread_data.items():
+ active_t = data['signal'] + data['noise']
+ ratio = data['signal'] / max(data['noise'], 1)
+ db = 10 * math.log10(ratio) if ratio > 0 else -99.0
+ norm = data['signal'] / max(active_t, 1)
+ avg_commit = sum(data['commitment']) / len(data['commitment'])
+ thread_snr.append({
+ 'thread': thread,
+ 'snr_normalized': round(norm, 4),
+ 'snr_db': round(db, 2),
+ 'avg_commitment': round(avg_commit, 4),
+ 'messages': data['messages'],
+ })
+ thread_snr.sort(key=lambda x: x['snr_normalized'], reverse=True)
+
+ return {
+ 'total_messages': len(results),
+ 'total_tokens': total_tokens,
+ 'total_signal': total_signal,
+ 'total_noise': total_noise,
+ 'total_scaffolding': total_scaffold,
+ 'snr_normalized': round(snr_normalized, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'avg_commitment': round(avg_commitment, 4),
+ 'high_commitment_messages': len(high_commitment),
+ 'low_commitment_messages': len(low_commitment),
+ 'thread_leaderboard': thread_snr,
+ 'dynamic_anchors_count': len(self.dynamic_anchors),
+ }
+
+
+# ---------------------------------------------------------------------------
+# Output + CSV + CLI (same pattern as v2)
+# ---------------------------------------------------------------------------
+
+def render_summary(summary, output_dir=None):
+ lines = []
+ lines.append("=" * 64)
+ lines.append(" SIGTOKEN RECURSIVE — THREAD-AWARE TWO-PASS SCORING")
+ lines.append(f" Component {COMPONENT} | v{VERSION} | Ello Cello LLC")
+ lines.append("")
+ lines.append(" Pass 1: Thread-local IDF — each word scored within its thread")
+ lines.append(" Pass 2: Corpus validation — promoted anchors from bottom up")
+ lines.append("=" * 64)
+ lines.append(f" Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" SESSION METRICS")
+ lines.append("=" * 64)
+ lines.append(f" Total Messages Scored: {summary['total_messages']:,}")
+ lines.append(f" Total Tokens: {summary['total_tokens']:,}")
+ lines.append(f" Signal Tokens: {summary['total_signal']:,}")
+ lines.append(f" Noise Tokens: {summary['total_noise']:,}")
+ lines.append(f" Scaffolding Tokens: {summary['total_scaffolding']:,}")
+ lines.append(f" Dynamic Anchors Built: {summary['dynamic_anchors_count']:,}")
+ lines.append("")
+ lines.append(f" SNR (Normalized 0-1): {summary['snr_normalized']:.4f}")
+ lines.append(f" SNR (Ratio S/N): {summary['snr_ratio']:.4f}")
+ lines.append(f" SNR (dB): {summary['snr_db']:.2f} dB")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" COMMITMENT ANALYSIS")
+ lines.append("=" * 64)
+ lines.append(f" Avg Message Commitment: {summary['avg_commitment']:.4f}")
+ lines.append(f" High Commitment (≥0.50): {summary['high_commitment_messages']:,} messages")
+ lines.append(f" Low Commitment (<0.20): {summary['low_commitment_messages']:,} messages")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" THREAD LEADERBOARD (by SNR, top 20)")
+ lines.append("=" * 64)
+ for i, t in enumerate(summary['thread_leaderboard'][:20], 1):
+ lines.append(
+ f" {i:>2}. {t['thread'][:38]:<38} "
+ f"SNR:{t['snr_normalized']:.4f} "
+ f"CMT:{t['avg_commitment']:.4f} "
+ f"msgs:{t['messages']}"
+ )
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" BOTTOM 5 THREADS (highest noise)")
+ lines.append("=" * 64)
+ for t in summary['thread_leaderboard'][-5:]:
+ lines.append(
+ f" {t['thread'][:38]:<38} "
+ f"SNR:{t['snr_normalized']:.4f} "
+ f"CMT:{t['avg_commitment']:.4f} "
+ f"msgs:{t['messages']}"
+ )
+ lines.append("=" * 64)
+
+ output = '\n'.join(lines)
+ print(output)
+
+ if output_dir:
+ os.makedirs(output_dir, exist_ok=True)
+ ts = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H-%M-%S')
+ run_dir = os.path.join(output_dir, f'run_{ts}')
+ os.makedirs(run_dir, exist_ok=True)
+ with open(os.path.join(run_dir, 'sigtoken_recursive_summary.txt'), 'w') as f:
+ f.write(output)
+ with open(os.path.join(run_dir, 'sigtoken_recursive_summary.json'), 'w') as f:
+ json.dump(summary, f, indent=2)
+ print(f"\n Output saved to: {run_dir}")
+ return run_dir
+ return None
+
+
+def load_messages_csv(path):
+ messages = []
+ with open(path, 'r', encoding='utf-8', errors='replace') as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ messages.append({
+ 'content': row.get('message_text', row.get('content', '')),
+ 'role': row.get('role', 'unknown'),
+ 'thread': row.get('conversation_title', row.get('title', row.get('thread', 'unknown'))),
+ 'create_time': row.get('timestamp', row.get('create_time', '')),
+ })
+ return messages
+
+
+def load_officer_anchors(path):
+ anchors = set()
+ if not path or not os.path.isfile(path):
+ return anchors
+ with open(path, 'r', encoding='utf-8', errors='replace') as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ if row.get('Rank') == 'Officer-Class':
+ word = row.get('Word', '').strip().lower()
+ if word:
+ anchors.add(word)
+ print(f" {len(anchors)} Officer-Class anchors loaded.")
+ return anchors
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=f'SigToken Recursive v{VERSION} — Thread-aware two-pass scoring'
+ )
+ parser.add_argument('--messages', required=True)
+ parser.add_argument('--word-inventory', help='word_inventory.csv — Officer-Class anchors as seeds')
+ parser.add_argument('--output-dir', default='./runs')
+ args = parser.parse_args()
+
+ static_anchors = set(DOMAIN_ANCHORS_SEED)
+ if args.word_inventory:
+ static_anchors.update(load_officer_anchors(args.word_inventory))
+
+ print(f"Loading messages from {args.messages}...")
+ messages = load_messages_csv(args.messages)
+ print(f" {len(messages):,} messages loaded.")
+
+ processor = RecursiveSessionProcessor(messages)
+ processor.build_corpus(static_anchors)
+
+ print(" Scoring all messages...")
+ results = processor.process()
+ summary = processor.summarize(results)
+ render_summary(summary, args.output_dir)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/moses-sampler/processors/sigtoken/sigtoken_sys.py b/moses-sampler/processors/sigtoken/sigtoken_sys.py
new file mode 100644
index 0000000..ae9901c
--- /dev/null
+++ b/moses-sampler/processors/sigtoken/sigtoken_sys.py
@@ -0,0 +1,515 @@
+#!/usr/bin/env python3
+"""
+SigToken System — Contextual Token SNR Classification
+Component C-0005 | Ello Cello LLC
+
+"Words and tokens are not inherently signal or noise.
+ Their classification depends entirely on contextual function,
+ relational placement, frequency, and contribution to compression."
+ — classification_logic.md, Oct 1 2025
+
+Takes a message or corpus and classifies every token dynamically —
+not by identity, but by usage role in context.
+
+Output:
+ - Per-token dual weights (SW / NW)
+ - Message-level commitment score
+ - Session SNR
+ - Leaderboard-ready resonance scores
+
+Usage:
+ python sigtoken_sys.py --messages flattened_messages.csv
+ python sigtoken_sys.py --text "your message here"
+ python sigtoken_sys.py --messages flattened_messages.csv --output-dir ./runs/
+"""
+
+import argparse
+import csv
+import json
+import math
+import os
+import re
+import sys
+from collections import Counter, defaultdict
+from datetime import datetime, timezone
+
+csv.field_size_limit(10 * 1024 * 1024)
+
+VERSION = "1.0"
+COMPONENT = "C-0005"
+
+# ---------------------------------------------------------------------------
+# The three-tier taxonomy (signal_use_case.md, noise_use_case.md Oct 1 2025)
+# Signal — high semantic density, intent-bearing
+# Scaffolding — neutral support structure (the, into, every)
+# Noise — low contribution, filler, replaceable
+# ---------------------------------------------------------------------------
+
+SCAFFOLDING = frozenset({
+ 'the', 'a', 'an', 'and', 'or', 'but', 'so', 'if', 'of', 'in', 'on',
+ 'at', 'to', 'for', 'from', 'with', 'by', 'about', 'as', 'is', 'are',
+ 'was', 'were', 'be', 'been', 'being', 'am', 'have', 'has', 'had',
+ 'having', 'do', 'does', 'did', 'doing', 'done', 'will', 'would',
+ 'shall', 'should', 'can', 'could', 'may', 'might', 'must',
+ 'i', 'me', 'my', 'mine', 'myself', 'you', 'your', 'yours',
+ 'he', 'him', 'his', 'she', 'her', 'hers', 'it', 'its',
+ 'we', 'us', 'our', 'they', 'them', 'their',
+ 'who', 'whom', 'which', 'what', 'that', 'this', 'these', 'those',
+ 'into', 'through', 'between', 'after', 'before', 'during',
+ 'without', 'within', 'upon', 'above', 'below', 'under', 'over',
+ 'because', 'although', 'though', 'while', 'when', 'where', 'whether',
+ 'unless', 'since', 'than', 'also', 'too', 'then', 'now', 'here', 'there',
+ 'some', 'any', 'all', 'each', 'every', 'both', 'either', 'neither',
+ 'one', 'two', 'three', 'first', 'second', 'third',
+})
+
+# Noise: filler words — low semantic contribution in most contexts
+# NOTE: these carry DUAL WEIGHTS. 'just' is noise in filler but
+# signal in emotional emphasis. We score by context, not identity.
+FILLER_WORDS = frozenset({
+ 'basically', 'literally', 'actually', 'honestly', 'obviously',
+ 'clearly', 'simply', 'totally', 'absolutely', 'definitely',
+ 'certainly', 'probably', 'maybe', 'perhaps', 'really', 'very',
+ 'quite', 'rather', 'pretty', 'just', 'only', 'even', 'still',
+ 'already', 'always', 'often', 'sometimes', 'usually', 'generally',
+ 'lol', 'ok', 'okay', 'yeah', 'yep', 'nope', 'um', 'uh', 'ah',
+ 'oh', 'well', 'like', 'so', 'right', 'sure', 'anyway', 'kind',
+ 'sort', 'thing', 'things', 'stuff', 'bit', 'lot', 'way', 'ways',
+ 'gonna', 'wanna', 'gotta', 'etc', 'eg', 'ie',
+})
+
+# High-signal domain terms — when these appear they carry heavy SW
+# Seeded from Signal Army Officer-Class words in your corpus
+# NOTE: mutable set so --word-inventory can extend it at runtime
+DOMAIN_ANCHORS = {
+ 'signal', 'noise', 'compression', 'token', 'system', 'sigsystem',
+ 'mos2es', 'moses', 'sigrank', 'compression', 'lineage', 'vault',
+ 'mediator', 'resonance', 'entropy', 'snr', 'drift', 'decay',
+ 'commitment', 'kernel', 'fracto', 'abba', 'scs', 'ppa', 'keter',
+ 'integrity', 'classification', 'semantic', 'recursive', 'anchor',
+ 'ignition', 'collapse', 'threshold', 'quantify', 'measurement',
+ 'governance', 'sovereign', 'artifact', 'leaderboard', 'dual',
+ 'weight', 'density', 'trajectory', 'conservation', 'invariant',
+}
+
+
+# ---------------------------------------------------------------------------
+# Tokenizer
+# ---------------------------------------------------------------------------
+
+def tokenize(text):
+ """Tokenize preserving positional context for dual-weight scoring."""
+ text = text.lower()
+ text = re.sub(r'mo§es™?|moses™', 'moses', text)
+ text = re.sub(r'mos²es|mos2es', 'mos2es', text)
+ text = re.sub(r"[^\w\s'\-]", ' ', text)
+ tokens = text.split()
+ result = []
+ for i, t in enumerate(tokens):
+ t = t.strip("'-_")
+ if not t or t.isdigit() or (len(t) == 1 and t not in ('i', 'a')):
+ continue
+ result.append((i, t)) # (position, token)
+ return result
+
+
+# ---------------------------------------------------------------------------
+# Core: Contextual Dual-Weight Scorer
+# C-0005 — classification by usage role, not identity
+# ---------------------------------------------------------------------------
+
+class SigTokenScorer:
+ """
+ Classifies tokens by contextual function.
+
+ The key insight (Oct 1 2025, classification_logic.md):
+ Signal/noise is not a property of the word.
+ It is a property of the word IN THIS POSITION IN THIS MESSAGE.
+
+ Dual weights are held simultaneously — SW + NW = 1.0
+ Classification resolves at message level when context collapses them.
+ """
+
+ def __init__(self, corpus_frequencies=None):
+ # Corpus-level frequency data for relative scoring
+ self.corpus_freq = corpus_frequencies or {}
+ self.max_freq = max(self.corpus_freq.values()) if self.corpus_freq else 1
+
+ def score_token(self, token, position, message_tokens, message_text=""):
+ """
+ Score a single token in context. Returns (SW, NW, tier, reason).
+
+ Tier: 'signal' | 'scaffolding' | 'noise'
+ """
+ # Scaffolding — neutral structure, not signal or noise
+ if token in SCAFFOLDING:
+ return (0.05, 0.05, 'scaffolding', 'neutral_structure')
+
+ # Domain anchor — high signal in this corpus by definition
+ if token in DOMAIN_ANCHORS:
+ base_sw = 0.80
+ # Boost if it appears in a dense cluster of other anchors
+ anchor_neighbors = sum(
+ 1 for _, t in message_tokens
+ if t in DOMAIN_ANCHORS and t != token
+ )
+ cluster_bonus = min(anchor_neighbors * 0.02, 0.15)
+ sw = min(base_sw + cluster_bonus, 0.95)
+ nw = round(1.0 - sw, 4)
+ return (sw, nw, 'signal', 'domain_anchor')
+
+ # Filler words — dual weight depends on context
+ if token in FILLER_WORDS:
+ # Check if surrounded by high-signal content
+ # If yes: may be emphasis, not pure noise
+ # e.g. "just" in "just compression" vs "just kind of the thing"
+ window = [t for _, t in message_tokens]
+ pos_idx = next((i for i, (p, t) in enumerate(message_tokens) if t == token), 0)
+ nearby = window[max(0, pos_idx-2):pos_idx+3]
+ signal_neighbors = sum(1 for w in nearby if w in DOMAIN_ANCHORS)
+
+ if signal_neighbors >= 2:
+ # Emphasis context — elevated SW
+ return (0.35, 0.65, 'noise', 'filler_emphasis')
+ else:
+ # Pure filler
+ return (0.08, 0.92, 'noise', 'filler_pure')
+
+ # Frequency-based scoring for everything else
+ # High corpus frequency = well-established in this domain
+ # But also check: is it ONLY in this corpus (narrow) or widespread?
+ freq = self.corpus_freq.get(token, 0)
+ freq_normalized = freq / self.max_freq if self.max_freq > 0 else 0
+
+ # Position signal: early in message = likely load-bearing
+ total_tokens = len(message_tokens)
+ position_score = 1.0 - (position / max(total_tokens, 1)) * 0.3
+
+ # Uniqueness signal: rare tokens in a domain-dense message = high signal
+ message_word_set = set(t for _, t in message_tokens)
+ domain_density = len(message_word_set & DOMAIN_ANCHORS) / max(len(message_word_set), 1)
+
+ # Composite SW
+ sw = (
+ 0.40 * freq_normalized +
+ 0.25 * position_score +
+ 0.35 * domain_density
+ )
+ sw = round(min(max(sw, 0.10), 0.75), 4)
+ nw = round(1.0 - sw, 4)
+
+ tier = 'signal' if sw >= 0.35 else 'noise'
+ return (sw, nw, tier, 'frequency_context')
+
+ def score_message(self, text):
+ """
+ Score all tokens in a message. Returns message-level commitment data.
+
+ Commitment score = fraction of tokens that cannot be removed
+ without collapsing the message's irreducible meaning.
+ """
+ tokens = tokenize(text)
+ if not tokens:
+ return None
+
+ scored = []
+ for pos, token in tokens:
+ sw, nw, tier, reason = self.score_token(token, pos, tokens, text)
+ scored.append({
+ 'token': token,
+ 'position': pos,
+ 'SW': sw,
+ 'NW': nw,
+ 'tier': tier,
+ 'reason': reason,
+ })
+
+ # Message-level SNR
+ signal_tokens = [s for s in scored if s['tier'] == 'signal']
+ noise_tokens = [s for s in scored if s['tier'] == 'noise']
+ scaffolding_tokens = [s for s in scored if s['tier'] == 'scaffolding']
+
+ total = len(scored)
+ signal_count = len(signal_tokens)
+ noise_count = len(noise_tokens)
+
+ snr_ratio = signal_count / max(noise_count, 1)
+ snr_db = 10 * math.log10(snr_ratio) if snr_ratio > 0 else -99.0
+ snr_normalized = signal_count / max(total, 1)
+
+ # Commitment score:
+ # Average SW of non-scaffolding tokens weighted by necessity
+ # High commitment = message collapses cleanly to its kernel
+ non_scaffold = [s for s in scored if s['tier'] != 'scaffolding']
+ if non_scaffold:
+ avg_sw = sum(s['SW'] for s in non_scaffold) / len(non_scaffold)
+ # Penalize messages that are mostly noise
+ noise_penalty = noise_count / max(total, 1)
+ commitment = round(avg_sw * (1.0 - noise_penalty * 0.5), 4)
+ else:
+ commitment = 0.0
+
+ return {
+ 'text': text[:120] + '...' if len(text) > 120 else text,
+ 'total_tokens': total,
+ 'signal_count': signal_count,
+ 'noise_count': noise_count,
+ 'scaffolding_count': len(scaffolding_tokens),
+ 'snr_normalized': round(snr_normalized, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'commitment_score': commitment,
+ 'tokens': scored,
+ }
+
+
+# ---------------------------------------------------------------------------
+# Session-level processor
+# ---------------------------------------------------------------------------
+
+class SessionProcessor:
+ """Processes a full conversation corpus through SigToken."""
+
+ def __init__(self, messages):
+ # Build corpus frequency table first
+ all_text = ' '.join(m.get('content', '') for m in messages)
+ raw_tokens = tokenize(all_text)
+ freq = Counter(t for _, t in raw_tokens)
+ self.scorer = SigTokenScorer(corpus_frequencies=freq)
+ self.messages = messages
+
+ def process(self):
+ results = []
+ for msg in self.messages:
+ content = msg.get('content', '')
+ if not content or len(content.strip()) < 5:
+ continue
+ scored = self.scorer.score_message(content)
+ if scored:
+ scored['role'] = msg.get('role', 'unknown')
+ scored['thread'] = msg.get('title', msg.get('thread', 'unknown'))
+ results.append(scored)
+ return results
+
+ def summarize(self, results):
+ if not results:
+ return {}
+
+ total_tokens = sum(r['total_tokens'] for r in results)
+ total_signal = sum(r['signal_count'] for r in results)
+ total_noise = sum(r['noise_count'] for r in results)
+ total_scaffold = sum(r['scaffolding_count'] for r in results)
+
+ snr_ratio = total_signal / max(total_noise, 1)
+ snr_db = 10 * math.log10(snr_ratio) if snr_ratio > 0 else -99.0
+ snr_normalized = total_signal / max(total_tokens, 1)
+
+ # Commitment distribution
+ commitments = [r['commitment_score'] for r in results]
+ avg_commitment = sum(commitments) / len(commitments)
+ high_commitment = [r for r in results if r['commitment_score'] >= 0.50]
+ low_commitment = [r for r in results if r['commitment_score'] < 0.20]
+
+ # Thread-level leaderboard
+ thread_data = defaultdict(lambda: {'signal': 0, 'noise': 0, 'messages': 0, 'commitment': []})
+ for r in results:
+ t = r['thread']
+ thread_data[t]['signal'] += r['signal_count']
+ thread_data[t]['noise'] += r['noise_count']
+ thread_data[t]['messages'] += 1
+ thread_data[t]['commitment'].append(r['commitment_score'])
+
+ thread_snr = []
+ for thread, data in thread_data.items():
+ ratio = data['signal'] / max(data['noise'], 1)
+ db = 10 * math.log10(ratio) if ratio > 0 else -99.0
+ norm = data['signal'] / max(data['signal'] + data['noise'], 1)
+ avg_commit = sum(data['commitment']) / len(data['commitment'])
+ thread_snr.append({
+ 'thread': thread,
+ 'snr_normalized': round(norm, 4),
+ 'snr_db': round(db, 2),
+ 'avg_commitment': round(avg_commit, 4),
+ 'messages': data['messages'],
+ })
+
+ thread_snr.sort(key=lambda x: x['snr_normalized'], reverse=True)
+
+ return {
+ 'total_messages': len(results),
+ 'total_tokens': total_tokens,
+ 'total_signal': total_signal,
+ 'total_noise': total_noise,
+ 'total_scaffolding': total_scaffold,
+ 'snr_normalized': round(snr_normalized, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'avg_commitment': round(avg_commitment, 4),
+ 'high_commitment_messages': len(high_commitment),
+ 'low_commitment_messages': len(low_commitment),
+ 'thread_leaderboard': thread_snr,
+ }
+
+
+# ---------------------------------------------------------------------------
+# Output
+# ---------------------------------------------------------------------------
+
+def render_summary(summary, output_dir=None):
+ lines = []
+ lines.append("=" * 64)
+ lines.append(" SIGTOKEN SYSTEM — CONTEXTUAL TOKEN SNR CLASSIFICATION")
+ lines.append(f" Component {COMPONENT} | v{VERSION} | Ello Cello LLC")
+ lines.append("")
+ lines.append(" 'Words are not inherently signal or noise.")
+ lines.append(" Their classification depends entirely on")
+ lines.append(" contextual function and usage role.'")
+ lines.append("=" * 64)
+ lines.append(f" Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" SESSION METRICS")
+ lines.append("=" * 64)
+ lines.append(f" Total Messages Scored: {summary['total_messages']:,}")
+ lines.append(f" Total Tokens: {summary['total_tokens']:,}")
+ lines.append(f" Signal Tokens: {summary['total_signal']:,}")
+ lines.append(f" Noise Tokens: {summary['total_noise']:,}")
+ lines.append(f" Scaffolding Tokens: {summary['total_scaffolding']:,}")
+ lines.append("")
+ lines.append(f" SNR (Normalized 0-1): {summary['snr_normalized']:.4f}")
+ lines.append(f" SNR (Ratio S/N): {summary['snr_ratio']:.4f}")
+ lines.append(f" SNR (dB): {summary['snr_db']:.2f} dB")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" COMMITMENT ANALYSIS")
+ lines.append("=" * 64)
+ lines.append(f" Avg Message Commitment: {summary['avg_commitment']:.4f}")
+ lines.append(f" High Commitment (≥0.50): {summary['high_commitment_messages']:,} messages")
+ lines.append(f" Low Commitment (<0.20): {summary['low_commitment_messages']:,} messages")
+ lines.append("")
+ lines.append(" Commitment = fraction of a message that cannot be removed")
+ lines.append(" without collapsing its irreducible meaning kernel.")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" THREAD LEADERBOARD (by SNR)")
+ lines.append("=" * 64)
+ for i, t in enumerate(summary['thread_leaderboard'][:20], 1):
+ lines.append(
+ f" {i:>2}. {t['thread'][:40]:<40} "
+ f"SNR:{t['snr_normalized']:.4f} "
+ f"CMT:{t['avg_commitment']:.4f} "
+ f"dB:{t['snr_db']:>6.2f} "
+ f"msgs:{t['messages']}"
+ )
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" BOTTOM 5 THREADS (highest noise)")
+ lines.append("=" * 64)
+ for t in summary['thread_leaderboard'][-5:]:
+ lines.append(
+ f" {t['thread'][:40]:<40} "
+ f"SNR:{t['snr_normalized']:.4f} "
+ f"CMT:{t['avg_commitment']:.4f} "
+ f"dB:{t['snr_db']:>6.2f}"
+ )
+ lines.append("=" * 64)
+
+ output = '\n'.join(lines)
+ print(output)
+
+ if output_dir:
+ os.makedirs(output_dir, exist_ok=True)
+ ts = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H-%M-%S')
+ run_dir = os.path.join(output_dir, f'run_{ts}')
+ os.makedirs(run_dir, exist_ok=True)
+
+ with open(os.path.join(run_dir, 'sigtoken_summary.txt'), 'w') as f:
+ f.write(output)
+
+ with open(os.path.join(run_dir, 'sigtoken_summary.json'), 'w') as f:
+ json.dump(summary, f, indent=2)
+
+ print(f"\n Output saved to: {run_dir}")
+ return run_dir
+
+ return None
+
+
+# ---------------------------------------------------------------------------
+# CLI
+# ---------------------------------------------------------------------------
+
+def load_messages_csv(path):
+ messages = []
+ with open(path, 'r', encoding='utf-8', errors='replace') as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ msg = {
+ 'content': row.get('message_text', row.get('content', '')),
+ 'role': row.get('role', 'unknown'),
+ 'thread': row.get('conversation_title', row.get('title', row.get('thread', 'unknown'))),
+ }
+ messages.append(msg)
+ return messages
+
+
+def load_officer_anchors(word_inventory_path):
+ """Load Officer-Class words from Signal Army word_inventory.csv.
+ These become domain anchors — dynamically seeded, not hardcoded.
+ """
+ anchors = set()
+ if not word_inventory_path or not os.path.isfile(word_inventory_path):
+ return anchors
+ with open(word_inventory_path, 'r', encoding='utf-8', errors='replace') as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ if row.get('Rank') == 'Officer-Class':
+ word = row.get('Word', '').strip().lower()
+ if word:
+ anchors.add(word)
+ print(f" {len(anchors)} Officer-Class anchors loaded from word inventory.")
+ return anchors
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=f'SigToken System v{VERSION} — Component {COMPONENT}'
+ )
+ parser.add_argument('--messages', help='Path to flattened_messages.csv from Signal Army')
+ parser.add_argument('--text', help='Score a single message directly')
+ parser.add_argument('--word-inventory', help='Path to word_inventory.csv — loads Officer-Class words as domain anchors')
+ parser.add_argument('--output-dir', default='./runs', help='Output directory')
+ args = parser.parse_args()
+
+ # Load dynamic anchors if provided — extends (not replaces) hardcoded DOMAIN_ANCHORS
+ if args.word_inventory:
+ officer_anchors = load_officer_anchors(args.word_inventory)
+ DOMAIN_ANCHORS.update(officer_anchors) # type: ignore
+
+ if args.text:
+ scorer = SigTokenScorer()
+ result = scorer.score_message(args.text)
+ print(f"\nMessage: {args.text}")
+ print(f"Commitment Score: {result['commitment_score']}")
+ print(f"SNR (normalized): {result['snr_normalized']}")
+ print(f"SNR (dB): {result['snr_db']}")
+ print(f"\nToken breakdown:")
+ for t in result['tokens']:
+ print(f" {t['token']:<20} SW:{t['SW']:.3f} NW:{t['NW']:.3f} [{t['tier']}] ({t['reason']})")
+ return
+
+ if args.messages:
+ print(f"Loading messages from {args.messages}...")
+ messages = load_messages_csv(args.messages)
+ print(f" {len(messages):,} messages loaded.")
+ processor = SessionProcessor(messages)
+ results = processor.process()
+ summary = processor.summarize(results)
+ render_summary(summary, args.output_dir)
+ return
+
+ parser.print_help()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/moses-sampler/processors/sigtoken/sigtoken_v2.py b/moses-sampler/processors/sigtoken/sigtoken_v2.py
new file mode 100644
index 0000000..e8f7967
--- /dev/null
+++ b/moses-sampler/processors/sigtoken/sigtoken_v2.py
@@ -0,0 +1,534 @@
+#!/usr/bin/env python3
+"""
+SigToken System v2.0 — Revised Commitment Scoring
+Component C-0005 | Ello Cello LLC
+
+Changes from v1.0:
+ - Removed domain_density bleed from frequency scorer (was inflating all tokens
+ in domain-rich messages, deflating all tokens in casual messages)
+ - Commitment now penalizes length: short irreducible messages score higher
+ than long messages with equivalent signal density
+ - SNR ceiling fixed: scaffolding excluded from both numerator AND denominator
+ so the ratio reflects actual signal vs noise, not signal vs everything
+ - Frequency scoring simplified: position + corpus rarity only, no domain bleed
+
+Usage:
+ python sigtoken_v2.py --messages flattened_messages.csv
+ python sigtoken_v2.py --text "your message here"
+ python sigtoken_v2.py --messages flattened_messages.csv --word-inventory word_inventory.csv
+"""
+
+import argparse
+import csv
+import json
+import math
+import os
+import re
+import sys
+from collections import Counter, defaultdict
+from datetime import datetime, timezone
+
+csv.field_size_limit(10 * 1024 * 1024)
+
+VERSION = "2.0"
+COMPONENT = "C-0005"
+
+# ---------------------------------------------------------------------------
+# Three-tier taxonomy (unchanged from v1 — the taxonomy is correct)
+# ---------------------------------------------------------------------------
+
+SCAFFOLDING = frozenset({
+ 'the', 'a', 'an', 'and', 'or', 'but', 'so', 'if', 'of', 'in', 'on',
+ 'at', 'to', 'for', 'from', 'with', 'by', 'about', 'as', 'is', 'are',
+ 'was', 'were', 'be', 'been', 'being', 'am', 'have', 'has', 'had',
+ 'having', 'do', 'does', 'did', 'doing', 'done', 'will', 'would',
+ 'shall', 'should', 'can', 'could', 'may', 'might', 'must',
+ 'i', 'me', 'my', 'mine', 'myself', 'you', 'your', 'yours',
+ 'he', 'him', 'his', 'she', 'her', 'hers', 'it', 'its',
+ 'we', 'us', 'our', 'they', 'them', 'their',
+ 'who', 'whom', 'which', 'what', 'that', 'this', 'these', 'those',
+ 'into', 'through', 'between', 'after', 'before', 'during',
+ 'without', 'within', 'upon', 'above', 'below', 'under', 'over',
+ 'because', 'although', 'though', 'while', 'when', 'where', 'whether',
+ 'unless', 'since', 'than', 'also', 'too', 'then', 'now', 'here', 'there',
+ 'some', 'any', 'all', 'each', 'every', 'both', 'either', 'neither',
+ 'one', 'two', 'three', 'first', 'second', 'third',
+})
+
+FILLER_WORDS = frozenset({
+ 'basically', 'literally', 'actually', 'honestly', 'obviously',
+ 'clearly', 'simply', 'totally', 'absolutely', 'definitely',
+ 'certainly', 'probably', 'maybe', 'perhaps', 'really', 'very',
+ 'quite', 'rather', 'pretty', 'just', 'only', 'even', 'still',
+ 'already', 'always', 'often', 'sometimes', 'usually', 'generally',
+ 'lol', 'ok', 'okay', 'yeah', 'yep', 'nope', 'um', 'uh', 'ah',
+ 'oh', 'well', 'like', 'so', 'right', 'sure', 'anyway', 'kind',
+ 'sort', 'thing', 'things', 'stuff', 'bit', 'lot', 'way', 'ways',
+ 'gonna', 'wanna', 'gotta', 'etc', 'eg', 'ie',
+})
+
+DOMAIN_ANCHORS = {
+ 'signal', 'noise', 'compression', 'token', 'system', 'sigsystem',
+ 'mos2es', 'moses', 'sigrank', 'lineage', 'vault',
+ 'mediator', 'resonance', 'entropy', 'snr', 'drift', 'decay',
+ 'commitment', 'kernel', 'fracto', 'abba', 'scs', 'ppa', 'keter',
+ 'integrity', 'classification', 'semantic', 'recursive', 'anchor',
+ 'ignition', 'collapse', 'threshold', 'quantify', 'measurement',
+ 'governance', 'sovereign', 'artifact', 'leaderboard', 'dual',
+ 'weight', 'density', 'trajectory', 'conservation', 'invariant',
+}
+
+
+# ---------------------------------------------------------------------------
+# Tokenizer (unchanged)
+# ---------------------------------------------------------------------------
+
+def tokenize(text):
+ text = text.lower()
+ text = re.sub(r'mo§es™?|moses™', 'moses', text)
+ text = re.sub(r'mos²es|mos2es', 'mos2es', text)
+ text = re.sub(r"[^\w\s'\-]", ' ', text)
+ tokens = text.split()
+ result = []
+ for i, t in enumerate(tokens):
+ t = t.strip("'-_")
+ if not t or t.isdigit() or (len(t) == 1 and t not in ('i', 'a')):
+ continue
+ result.append((i, t))
+ return result
+
+
+# ---------------------------------------------------------------------------
+# Core: Revised Dual-Weight Scorer v2
+# Key changes:
+# - No domain_density bleed into unrelated tokens
+# - Filler emphasis check uses proximity only (not global domain density)
+# - Frequency scorer: position + corpus rarity, clean separation
+# ---------------------------------------------------------------------------
+
+class SigTokenScorer:
+ """
+ v2: Classifies tokens by contextual function without domain bleed.
+
+ Token scoring is LOCAL — each token scored by its own properties
+ and immediate neighbors only. Global message statistics do not
+ inflate or deflate unrelated tokens.
+ """
+
+ def __init__(self, corpus_frequencies=None, corpus_size=1):
+ self.corpus_freq = corpus_frequencies or {}
+ self.corpus_size = max(corpus_size, 1)
+ # IDF-style rarity: rare tokens get signal boost
+ # common tokens (appear in >10% of messages) treated as scaffolding-like
+ self.rarity_threshold = self.corpus_size * 0.10
+
+ def score_token(self, token, position, message_tokens):
+ """
+ Score a single token in local context. Returns (SW, NW, tier, reason).
+
+ v2 change: no global domain_density in the formula.
+ Each token stands on its own + immediate neighbors.
+ """
+ if token in SCAFFOLDING:
+ return (0.05, 0.95, 'scaffolding', 'neutral_structure')
+
+ if token in DOMAIN_ANCHORS:
+ base_sw = 0.80
+ # Only look at immediate neighbors (window of 4) for cluster bonus
+ pos_idx = next((i for i, (p, t) in enumerate(message_tokens) if p == position), 0)
+ window_tokens = [t for _, t in message_tokens[max(0, pos_idx-2):pos_idx+3]]
+ anchor_neighbors = sum(1 for w in window_tokens if w in DOMAIN_ANCHORS and w != token)
+ cluster_bonus = min(anchor_neighbors * 0.03, 0.15)
+ sw = min(base_sw + cluster_bonus, 0.95)
+ nw = round(1.0 - sw, 4)
+ return (sw, nw, 'signal', 'domain_anchor')
+
+ if token in FILLER_WORDS:
+ # Check immediate neighbors for signal context
+ pos_idx = next((i for i, (p, t) in enumerate(message_tokens) if p == position), 0)
+ window_tokens = [t for _, t in message_tokens[max(0, pos_idx-2):pos_idx+3]]
+ signal_neighbors = sum(1 for w in window_tokens if w in DOMAIN_ANCHORS)
+ if signal_neighbors >= 2:
+ return (0.35, 0.65, 'noise', 'filler_emphasis')
+ else:
+ return (0.08, 0.92, 'noise', 'filler_pure')
+
+ # Frequency-based scoring — clean version, no domain bleed
+ # Two factors only: corpus rarity + positional weight
+ freq = self.corpus_freq.get(token, 0)
+
+ # Rarity: words that appear rarely in corpus carry more information
+ # Common words (>10% of docs) treated like scaffolding
+ if freq > self.rarity_threshold:
+ rarity_score = 0.20 # very common — low signal
+ elif freq > self.rarity_threshold * 0.1:
+ rarity_score = 0.45 # moderately common
+ else:
+ rarity_score = 0.70 # rare — higher signal potential
+
+ # Position: earlier in message = more likely load-bearing
+ total_tokens = len(message_tokens)
+ normalized_pos = position / max(total_tokens, 1)
+ position_score = 0.60 - (normalized_pos * 0.20) # 0.60 at start, 0.40 at end
+
+ sw = round((rarity_score * 0.65 + position_score * 0.35), 4)
+ sw = min(max(sw, 0.10), 0.75)
+ nw = round(1.0 - sw, 4)
+
+ tier = 'signal' if sw >= 0.40 else 'noise'
+ return (sw, nw, tier, 'frequency_rarity')
+
+ def score_message(self, text):
+ tokens = tokenize(text)
+ if not tokens:
+ return None
+
+ scored = []
+ for pos, token in tokens:
+ sw, nw, tier, reason = self.score_token(token, pos, tokens)
+ scored.append({
+ 'token': token,
+ 'position': pos,
+ 'SW': sw,
+ 'NW': nw,
+ 'tier': tier,
+ 'reason': reason,
+ })
+
+ signal_tokens = [s for s in scored if s['tier'] == 'signal']
+ noise_tokens = [s for s in scored if s['tier'] == 'noise']
+ scaffolding_tokens = [s for s in scored if s['tier'] == 'scaffolding']
+
+ total = len(scored)
+ signal_count = len(signal_tokens)
+ noise_count = len(noise_tokens)
+
+ # v2 SNR fix: scaffolding excluded from BOTH numerator and denominator
+ # SNR now measures signal vs noise only — scaffolding is neutral, not counted
+ active_tokens = signal_count + noise_count # scaffolding excluded
+ snr_ratio = signal_count / max(noise_count, 1)
+ snr_db = 10 * math.log10(snr_ratio) if snr_ratio > 0 else -99.0
+ snr_normalized = signal_count / max(active_tokens, 1) # KEY CHANGE
+
+ # v2 Commitment fix: irreducibility score
+ # Short messages that are fully signal score higher than long messages
+ # with equal signal density. Length IS information when you can't compress further.
+ #
+ # Formula:
+ # base = avg SW of signal tokens (content quality)
+ # length_factor = diminishing returns on longer messages
+ # - a 5-token fully signal message scores higher than a 50-token one
+ # - models the irreducibility claim: can you remove any of this?
+ # noise_drag = penalty for noise fraction
+
+ non_scaffold = [s for s in scored if s['tier'] != 'scaffolding']
+ if non_scaffold:
+ signal_only = [s for s in non_scaffold if s['tier'] == 'signal']
+ avg_signal_sw = (
+ sum(s['SW'] for s in signal_only) / len(signal_only)
+ if signal_only else 0.0
+ )
+
+ # Signal purity: what fraction of active tokens are signal
+ signal_purity = signal_count / max(active_tokens, 1)
+
+ # Length irreducibility factor:
+ # Peaks around 3-8 tokens (maximally irreducible short messages)
+ # Decreases slowly for longer messages (more room for fat)
+ # "yes please" with 2 tokens gets a HIGH irreducibility score
+ if active_tokens <= 3:
+ length_factor = 1.0 # maximally irreducible
+ elif active_tokens <= 8:
+ length_factor = 0.90
+ elif active_tokens <= 20:
+ length_factor = 0.75
+ elif active_tokens <= 50:
+ length_factor = 0.60
+ else:
+ # Long messages: factor drops slowly — they CAN be irreducible
+ # but it takes more to prove it
+ length_factor = max(0.40, 0.60 - (active_tokens - 50) * 0.001)
+
+ commitment = round(
+ signal_purity * 0.50 +
+ avg_signal_sw * 0.30 +
+ length_factor * 0.20,
+ 4
+ )
+ else:
+ # Pure scaffolding message — no commitment measurable
+ commitment = 0.0
+
+ return {
+ 'text': text[:120] + '...' if len(text) > 120 else text,
+ 'total_tokens': total,
+ 'signal_count': signal_count,
+ 'noise_count': noise_count,
+ 'scaffolding_count': len(scaffolding_tokens),
+ 'snr_normalized': round(snr_normalized, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'commitment_score': commitment,
+ 'tokens': scored,
+ }
+
+
+# ---------------------------------------------------------------------------
+# Session processor (extended: captures create_time for timeline)
+# ---------------------------------------------------------------------------
+
+class SessionProcessor:
+
+ def __init__(self, messages):
+ all_text = ' '.join(m.get('content', '') for m in messages)
+ raw_tokens = tokenize(all_text)
+ freq = Counter(t for _, t in raw_tokens)
+ # corpus_size = number of messages for IDF-style rarity
+ self.scorer = SigTokenScorer(corpus_frequencies=freq, corpus_size=len(messages))
+ self.messages = messages
+
+ def process(self):
+ results = []
+ for msg in self.messages:
+ content = msg.get('content', '')
+ if not content or len(content.strip()) < 3:
+ continue
+ scored = self.scorer.score_message(content)
+ if scored:
+ scored['role'] = msg.get('role', 'unknown')
+ scored['thread'] = msg.get('title', msg.get('thread', 'unknown'))
+ scored['create_time'] = msg.get('create_time', '')
+ results.append(scored)
+ return results
+
+ def summarize(self, results):
+ if not results:
+ return {}
+
+ total_tokens = sum(r['total_tokens'] for r in results)
+ total_signal = sum(r['signal_count'] for r in results)
+ total_noise = sum(r['noise_count'] for r in results)
+ total_scaffold = sum(r['scaffolding_count'] for r in results)
+
+ # v2 SNR: exclude scaffolding from both sides
+ active = total_signal + total_noise
+ snr_ratio = total_signal / max(total_noise, 1)
+ snr_db = 10 * math.log10(snr_ratio) if snr_ratio > 0 else -99.0
+ snr_normalized = total_signal / max(active, 1)
+
+ commitments = [r['commitment_score'] for r in results]
+ avg_commitment = sum(commitments) / len(commitments)
+ high_commitment = [r for r in results if r['commitment_score'] >= 0.50]
+ low_commitment = [r for r in results if r['commitment_score'] < 0.20]
+
+ # Thread leaderboard
+ thread_data = defaultdict(lambda: {
+ 'signal': 0, 'noise': 0, 'messages': 0, 'commitment': [],
+ 'times': []
+ })
+ for r in results:
+ t = r['thread']
+ thread_data[t]['signal'] += r['signal_count']
+ thread_data[t]['noise'] += r['noise_count']
+ thread_data[t]['messages'] += 1
+ thread_data[t]['commitment'].append(r['commitment_score'])
+ if r.get('create_time'):
+ thread_data[t]['times'].append(r['create_time'])
+
+ thread_snr = []
+ for thread, data in thread_data.items():
+ active_t = data['signal'] + data['noise']
+ ratio = data['signal'] / max(data['noise'], 1)
+ db = 10 * math.log10(ratio) if ratio > 0 else -99.0
+ norm = data['signal'] / max(active_t, 1)
+ avg_commit = sum(data['commitment']) / len(data['commitment'])
+ earliest = min(data['times']) if data['times'] else ''
+ thread_snr.append({
+ 'thread': thread,
+ 'snr_normalized': round(norm, 4),
+ 'snr_db': round(db, 2),
+ 'avg_commitment': round(avg_commit, 4),
+ 'messages': data['messages'],
+ 'earliest': earliest,
+ })
+
+ thread_snr.sort(key=lambda x: x['snr_normalized'], reverse=True)
+
+ return {
+ 'total_messages': len(results),
+ 'total_tokens': total_tokens,
+ 'total_signal': total_signal,
+ 'total_noise': total_noise,
+ 'total_scaffolding': total_scaffold,
+ 'snr_normalized': round(snr_normalized, 4),
+ 'snr_ratio': round(snr_ratio, 4),
+ 'snr_db': round(snr_db, 2),
+ 'avg_commitment': round(avg_commitment, 4),
+ 'high_commitment_messages': len(high_commitment),
+ 'low_commitment_messages': len(low_commitment),
+ 'thread_leaderboard': thread_snr,
+ }
+
+
+# ---------------------------------------------------------------------------
+# Output
+# ---------------------------------------------------------------------------
+
+def render_summary(summary, output_dir=None):
+ lines = []
+ lines.append("=" * 64)
+ lines.append(" SIGTOKEN SYSTEM v2 — REVISED COMMITMENT SCORING")
+ lines.append(f" Component {COMPONENT} | v{VERSION} | Ello Cello LLC")
+ lines.append("")
+ lines.append(" 'Commitment is irreducibility, not vocabulary density.'")
+ lines.append(" SNR now excludes scaffolding from both S and N counts.")
+ lines.append(" Short irreducible messages score higher than long padded ones.")
+ lines.append("=" * 64)
+ lines.append(f" Generated: {datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S')} UTC")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" SESSION METRICS")
+ lines.append("=" * 64)
+ lines.append(f" Total Messages Scored: {summary['total_messages']:,}")
+ lines.append(f" Total Tokens: {summary['total_tokens']:,}")
+ lines.append(f" Signal Tokens: {summary['total_signal']:,}")
+ lines.append(f" Noise Tokens: {summary['total_noise']:,}")
+ lines.append(f" Scaffolding Tokens: {summary['total_scaffolding']:,}")
+ lines.append(f" Active (S+N only): {summary['total_signal'] + summary['total_noise']:,}")
+ lines.append("")
+ lines.append(f" SNR (Normalized 0-1): {summary['snr_normalized']:.4f}")
+ lines.append(f" SNR (Ratio S/N): {summary['snr_ratio']:.4f}")
+ lines.append(f" SNR (dB): {summary['snr_db']:.2f} dB")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" COMMITMENT ANALYSIS")
+ lines.append("=" * 64)
+ lines.append(f" Avg Message Commitment: {summary['avg_commitment']:.4f}")
+ lines.append(f" High Commitment (≥0.50): {summary['high_commitment_messages']:,} messages")
+ lines.append(f" Low Commitment (<0.20): {summary['low_commitment_messages']:,} messages")
+ lines.append("")
+ lines.append(" Commitment = signal purity × quality × length irreducibility")
+ lines.append(" Short messages that cannot be compressed score highest.")
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" THREAD LEADERBOARD (by SNR, top 20)")
+ lines.append("=" * 64)
+ for i, t in enumerate(summary['thread_leaderboard'][:20], 1):
+ lines.append(
+ f" {i:>2}. {t['thread'][:38]:<38} "
+ f"SNR:{t['snr_normalized']:.4f} "
+ f"CMT:{t['avg_commitment']:.4f} "
+ f"msgs:{t['messages']}"
+ )
+ lines.append("")
+ lines.append("=" * 64)
+ lines.append(" BOTTOM 5 THREADS (highest noise)")
+ lines.append("=" * 64)
+ for t in summary['thread_leaderboard'][-5:]:
+ lines.append(
+ f" {t['thread'][:38]:<38} "
+ f"SNR:{t['snr_normalized']:.4f} "
+ f"CMT:{t['avg_commitment']:.4f} "
+ f"msgs:{t['messages']}"
+ )
+ lines.append("=" * 64)
+
+ output = '\n'.join(lines)
+ print(output)
+
+ if output_dir:
+ os.makedirs(output_dir, exist_ok=True)
+ ts = datetime.now(timezone.utc).strftime('%Y-%m-%d_%H-%M-%S')
+ run_dir = os.path.join(output_dir, f'run_{ts}')
+ os.makedirs(run_dir, exist_ok=True)
+
+ with open(os.path.join(run_dir, 'sigtoken_v2_summary.txt'), 'w') as f:
+ f.write(output)
+ with open(os.path.join(run_dir, 'sigtoken_v2_summary.json'), 'w') as f:
+ json.dump(summary, f, indent=2)
+
+ print(f"\n Output saved to: {run_dir}")
+ return run_dir
+
+ return None
+
+
+# ---------------------------------------------------------------------------
+# CSV loader + anchor loader (same as v1)
+# ---------------------------------------------------------------------------
+
+def load_messages_csv(path):
+ messages = []
+ with open(path, 'r', encoding='utf-8', errors='replace') as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ msg = {
+ 'content': row.get('message_text', row.get('content', '')),
+ 'role': row.get('role', 'unknown'),
+ 'thread': row.get('conversation_title', row.get('title', row.get('thread', 'unknown'))),
+ 'create_time': row.get('create_time', ''),
+ }
+ messages.append(msg)
+ return messages
+
+
+def load_officer_anchors(word_inventory_path):
+ anchors = set()
+ if not word_inventory_path or not os.path.isfile(word_inventory_path):
+ return anchors
+ with open(word_inventory_path, 'r', encoding='utf-8', errors='replace') as f:
+ reader = csv.DictReader(f)
+ for row in reader:
+ if row.get('Rank') == 'Officer-Class':
+ word = row.get('Word', '').strip().lower()
+ if word:
+ anchors.add(word)
+ print(f" {len(anchors)} Officer-Class anchors loaded from word inventory.")
+ return anchors
+
+
+# ---------------------------------------------------------------------------
+# CLI
+# ---------------------------------------------------------------------------
+
+def main():
+ parser = argparse.ArgumentParser(
+ description=f'SigToken System v{VERSION} — Component {COMPONENT}'
+ )
+ parser.add_argument('--messages', help='Path to flattened_messages.csv from Signal Army')
+ parser.add_argument('--text', help='Score a single message directly')
+ parser.add_argument('--word-inventory', help='Path to word_inventory.csv — loads Officer-Class anchors')
+ parser.add_argument('--output-dir', default='./runs', help='Output directory')
+ args = parser.parse_args()
+
+ if args.word_inventory:
+ officer_anchors = load_officer_anchors(args.word_inventory)
+ DOMAIN_ANCHORS.update(officer_anchors)
+
+ if args.text:
+ scorer = SigTokenScorer()
+ result = scorer.score_message(args.text)
+ print(f"\nMessage: {args.text}")
+ print(f"Commitment Score: {result['commitment_score']}")
+ print(f"SNR (normalized): {result['snr_normalized']}")
+ print(f"SNR (dB): {result['snr_db']}")
+ print(f"\nToken breakdown:")
+ for t in result['tokens']:
+ print(f" {t['token']:<20} SW:{t['SW']:.3f} NW:{t['NW']:.3f} [{t['tier']}] ({t['reason']})")
+ return
+
+ if args.messages:
+ print(f"Loading messages from {args.messages}...")
+ messages = load_messages_csv(args.messages)
+ print(f" {len(messages):,} messages loaded.")
+ processor = SessionProcessor(messages)
+ results = processor.process()
+ summary = processor.summarize(results)
+ render_summary(summary, args.output_dir)
+ return
+
+ parser.print_help()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/moses-sampler/requirements.txt b/moses-sampler/requirements.txt
new file mode 100644
index 0000000..f6cb3d8
--- /dev/null
+++ b/moses-sampler/requirements.txt
@@ -0,0 +1,5 @@
+# moses-sampler — scaffold dependencies
+# Only what the Gradio scaffold needs to boot. Each processor under
+# processors/ carries its own dependencies; those are intentionally NOT
+# pulled in here yet (nothing is wired).
+gradio