Asked whether this was shipped. All seven §13 deliverables are met, which is what makes the question worth checking rather than answering. ⚠ A LIVE DRAW VANISHED WITH NO RECORD. One 'aside' fired and reported nothing, because the muted / no-soul / no-material / generator-failed / slot-write paths all returned silently. Almost certainly the mute test suppressed it — correct behaviour — but the log could not distinguish muted from crashed. Every exit now reports itself, with a control comparing return-count against log-count so the class cannot reopen. The rate report REVIEWED-128 binds is computed from this log: an unlogged exit does not merely lose a datum, it makes the denominator wrong while looking complete. ⚠ AND I CONTAMINATED THAT DENOMINATOR MYSELF. Inter-tick gaps measured 1.8, 4.7, 0.4 and 16.0 minutes against a 20-minute interval, then 20.0, 21.0, 20.0 once the session stopped touching it — the early ones are my own hand-runs of the status line and re-runs of the seam, which resets the tick clock. Test firings sitting in the live log, indistinguishable from real ones by inspection. A MARKER record now bounds the clean data and the deferral says to count from it and to report that the first 8 were discarded. ⚠ THE SEAMS HAVE NEVER FIRED IN PRODUCTION: 0 events each, and the Stop hook writes nothing on an ordinary turn, so there is no evidence it is invoked at all. Silent-net shape — it looks fine until a wrap produces nothing and the net takes the blame. The one genuinely good result: at 17:53:50 a notable draw passed the net and reached the status line UNRELAYED. First time anything reached the steward without the executor holding it. 38/38 on the mumble suite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J6hZXNYSxEfZseBGTni4sf
448 lines
20 KiB
Python
Executable File
448 lines
20 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Tarbuckle — the mumble generator. Runs DETACHED; never inside the status line.
|
|
|
|
Doctrine: v2 §8 (three tiers), §9 (one line, filed nowhere), and PENDING-152
|
|
AMENDMENT 6, which settles the three-way separation:
|
|
|
|
Material — the live session. Register — the soul, §7. Occasion — the refresh tick.
|
|
"The model supplies the words, in the soul's register, about the session."
|
|
|
|
⚠ THE UTTERANCE MUST HAVE NO TRUTH VALUE. The criterion is NOT "checkable in
|
|
principle" — it is whether an ADJUDICATION PATH EXISTS (AMENDMENT 6, superseding the
|
|
executor's own earlier test). "PENDING-4 has been open since April" has a path: open
|
|
the file. Once a path exists the trio walks it, the fool acquires a truth value, and it
|
|
is a checker again — which §2 says means the design has failed. "Scoring without
|
|
signal" has no path: it is a gesture at a shape, not a claim. That is the whole safety.
|
|
|
|
⚠ THE REGISTER IS NOT DUPLICATED HERE. The soul is read from its filed artifact at
|
|
run time. Pasting it into this file would create a parallel version of a governed
|
|
record, which is the context-rot failure CLAUDE.md names outright. If the soul cannot
|
|
be read, NOTHING IS GENERATED — honest degradation, not a fallback voice.
|
|
|
|
Latency measured 2026-08-25: ~11 s for a headless call. That is why this is detached
|
|
and why the status line never waits on it.
|
|
"""
|
|
import json
|
|
import os
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
SOUL = os.path.expanduser(
|
|
"~/dotfiles/claude/governance/fool/seed/FOOL-SOUL-2026-08-25.md")
|
|
SLOT = os.path.expanduser("~/.claude/state/tarbuckle-slot.json")
|
|
REJECTS = os.path.expanduser("~/.claude/state/tarbuckle-rejects.jsonl")
|
|
|
|
MIN_WORDS, MAX_WORDS = 3, 9 # observed Thistleweld register: three to nine words
|
|
|
|
# From the soul's own "What he never does", mechanically enforced. The model is asked
|
|
# for the register; this is the net under it. A violation yields SILENCE, never a
|
|
# repaired line — rewriting the fool's words would make the executor its editor.
|
|
BANNED = (
|
|
r"\bshould\b", r"\btry\b", r"\bmust\b", # advice
|
|
r"\bgone\b", r"\bif only\b", r"\bused to be\b", r"\bmissing\b", # vocabulary of lack
|
|
r"\bwe\b", # never says 'we' about the work
|
|
r"\?", # never asks
|
|
)
|
|
|
|
|
|
MUTE = os.path.expanduser("~/.claude/state/tarbuckle-mute")
|
|
DRAWS = os.path.expanduser("~/.claude/state/tarbuckle-draws.jsonl")
|
|
|
|
|
|
def log_event(surface: str, outcome: str) -> None:
|
|
"""Count occurrences. NEVER content. §8 obliges a rate; §9 forbids a log.
|
|
|
|
⚠ The two clauses look like they collide and do not, on this reading: §9's "filed
|
|
nowhere — no PENDING entry, no log, no item" is about the fool's OUTPUT entering the
|
|
record, and §8 explicitly orders "report the observed mumble rate after two weeks."
|
|
A rate needs a denominator. So this records THAT something happened and never WHAT
|
|
was said — occurrence, not utterance.
|
|
|
|
⚠ The rejections log is a different case and is NOT settled: it holds up to 200
|
|
characters of his words, at the steward's instruction, and that is nearer to filing.
|
|
Flagged rather than resolved; see the note put to the steward 2026-08-25.
|
|
|
|
Nothing reads this back. It is measurement, not memory.
|
|
"""
|
|
try:
|
|
os.makedirs(os.path.dirname(DRAWS), exist_ok=True)
|
|
with open(DRAWS, "a") as fh:
|
|
fh.write(json.dumps({"t": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
|
"surface": surface, "outcome": outcome}) + "\n")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def muted() -> str:
|
|
"""'' | 'mute' | 'off'. §9: "mute / off available at all times."
|
|
|
|
⚠ Read by EVERY surface, and it is the one piece of state the fool is permitted to
|
|
be steered by — because §9 says muting is never a fault, and a mute the fool could
|
|
ignore is not a mute. It conserves nothing and no utterance depends on it having
|
|
been set before: it is a switch, not a memory.
|
|
"""
|
|
try:
|
|
v = open(MUTE).read().strip()
|
|
return v if v in ("mute", "off") else ""
|
|
except OSError:
|
|
return ""
|
|
|
|
|
|
def log_rejection(why: str, line: str, surface: str = "") -> None:
|
|
"""The rejection log. ⚠ STRUCTURALLY UNABLE TO RECORD AN ACCEPTED LINE.
|
|
|
|
Jurist ruling, 2026-08-25, on whether this log breaches §9's "filed nowhere":
|
|
|
|
"The rejection log is a log of my instruction, not of Tarbuckle... The rejected
|
|
lines were never uttered: he was silent, and the log holds what silence cost.
|
|
Nothing there entered the room... the fool cannot be cited from it because
|
|
there is nothing to cite — only material the net suppressed.
|
|
THE CONDITION: the log holds rejections only. If it ever holds an accepted
|
|
line, that is filing, straightforwardly, and §9 is breached."
|
|
|
|
Made structural rather than intentional, as the ruling asked. `why` is acceptable()'s
|
|
violation reason, and it is EMPTY EXACTLY WHEN THE LINE PASSED. Refusing an empty
|
|
`why` means no call site exists from which an accepted line could be written: to log
|
|
one you would have to invent a violation it does not have. Same guarantee render()
|
|
takes from its signature — a function that cannot be handed the thing it must not
|
|
see.
|
|
|
|
⚠ TEMPORARY. Deleted on 2026-09-08 with the fortnight's report, per the same ruling:
|
|
"a permanent store of rejected lines is a corpus, and a corpus of his suppressed
|
|
speech is exactly what would let someone reconstruct a register." Tracked as a
|
|
DEFERRED-DECISION so it cannot be quietly retained.
|
|
|
|
⚠ NOT READ FOR CONTENT BEFORE THEN. Reading it as it accumulates is reading Tarbuckle
|
|
by the back door and would shape the net toward lines the reader happens to like.
|
|
"""
|
|
if not why:
|
|
return # an accepted line has no reason; there is no path
|
|
try:
|
|
with open(REJECTS, "a") as fh:
|
|
fh.write(json.dumps({"t": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
|
"kind": surface, "why": why,
|
|
"line": line[:200]}) + "\n")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _grams(text: str, n: int) -> set:
|
|
w = re.findall(r"[a-z']+", text.lower())
|
|
return {" ".join(w[i:i + n]) for i in range(len(w) - n + 1)}
|
|
|
|
|
|
def echoes_soul(line: str, soul: str) -> str:
|
|
"""'' if the line is his own; otherwise the phrase he recycled.
|
|
|
|
⚠ EARNED 2026-08-25, BY THE STEWARD NOTICING. The soul carries seven illustrative
|
|
sample lines, the prompt embeds the soul verbatim, and the model handed them back:
|
|
three of five measured outputs were near-verbatim lifts. A fool reciting his own
|
|
examples is not watching the session at all — and AMENDMENT 6 already ruled that
|
|
canned strings keyed to nothing "make a mood ring, atmosphere within a fortnight".
|
|
The implementation reintroduced exactly what the doctrine rejected, through the one
|
|
door nobody was watching: the examples.
|
|
|
|
Two thresholds, because the failure has two shapes. A 4-word run against the SAMPLE
|
|
LINES catches direct recital; a 6-word run against the whole soul catches longer
|
|
lifts out of the prose. Checked against the samples rather than the whole soul at
|
|
n=4 because the soul's prose shares ordinary 4-grams with ordinary English, and a
|
|
net that fires on those would silence him for speaking normally.
|
|
|
|
⚠ This TIGHTENS the net. Silence-on-violation is unchanged and still absolute.
|
|
"""
|
|
samples = re.findall(r"^- '(.+?)'$", soul, re.M)
|
|
lg4 = _grams(line, 4)
|
|
for sm in samples:
|
|
hit = lg4 & _grams(sm, 4)
|
|
if hit:
|
|
return sorted(hit)[0]
|
|
hit6 = _grams(line, 6) & _grams(soul, 6)
|
|
return sorted(hit6)[0] if hit6 else ""
|
|
|
|
|
|
def source_lacks(path: str, *parts: str) -> bool:
|
|
"""True if the joined needle does NOT appear in `path`.
|
|
|
|
⚠ THE NEEDLE IS ASSEMBLED FROM PARTS, AND THAT IS THE ENTIRE POINT. A control that
|
|
writes its needle as a literal PLANTS that literal in the very file it searches, so
|
|
it can only ever fail. That bug was written twice in one session — the second time
|
|
by the party who had just fixed the first, minutes earlier, while watching for it.
|
|
Correcting it a second time by hand would have been the same one-off; this is the
|
|
mechanism, so the shape cannot be written again by accident.
|
|
"""
|
|
return "".join(parts) not in open(path, encoding="utf-8").read()
|
|
|
|
|
|
def soul_register() -> str | None:
|
|
"""The soul, verbatim, from the filed artifact. None if unreadable."""
|
|
try:
|
|
body = open(SOUL, encoding="utf-8").read()
|
|
except OSError:
|
|
return None
|
|
m = re.search(r"```markdown\n(.*?)\n```", body, re.S)
|
|
return m.group(1) if m else None
|
|
|
|
|
|
def session_material(transcript_path: str, budget: int = 6000) -> str:
|
|
"""The tail of the live session. Bounded, and tool output is dropped.
|
|
|
|
Deliberately NOT the docket. AMENDMENT 6: the docket has a forum and the session
|
|
does not, which is why the session is safe material and PENDING.md is not.
|
|
"""
|
|
try:
|
|
lines = open(transcript_path, encoding="utf-8", errors="replace").readlines()
|
|
except OSError:
|
|
return ""
|
|
out = []
|
|
for ln in reversed(lines[-400:]):
|
|
try:
|
|
rec = json.loads(ln)
|
|
except Exception:
|
|
continue
|
|
if rec.get("type") not in ("user", "assistant"):
|
|
continue
|
|
msg = rec.get("message") or {}
|
|
content = msg.get("content")
|
|
text = ""
|
|
if isinstance(content, str):
|
|
text = content
|
|
elif isinstance(content, list):
|
|
text = " ".join(c.get("text", "") for c in content
|
|
if isinstance(c, dict) and c.get("type") == "text")
|
|
text = text.strip()
|
|
if not text:
|
|
continue
|
|
out.append(f"{rec['type']}: {text[:600]}")
|
|
if sum(len(s) for s in out) > budget:
|
|
break
|
|
return "\n".join(reversed(out))
|
|
|
|
|
|
def build_prompt(kind: str, register: str, material: str) -> str:
|
|
weight = ("Something at the shape of the work, not its detail."
|
|
if kind == "notable" else
|
|
"An ordinary passing remark. Small.")
|
|
return f"""You are writing ONE line as Tarbuckle. His character, filed and unalterable:
|
|
|
|
{register}
|
|
|
|
Here is the tail of the session he is in the room for:
|
|
<session>
|
|
{material}
|
|
</session>
|
|
|
|
{weight}
|
|
|
|
Write ONE line in his voice. Absolute constraints:
|
|
- ⚠ THE SAMPLE LINES ABOVE ARE ILLUSTRATIONS OF HIS REGISTER, NOT HIS VOCABULARY. Do
|
|
not reuse them, or any phrase from them, or their subject matter. They show how he
|
|
sounds. What he says must come from the session above and nowhere else. If your line
|
|
would work equally well pasted into any other session, it is wrong.
|
|
- Between {MIN_WORDS} and {MAX_WORDS} words. One clause. Present tense. Flat, no lift.
|
|
- IT MUST HAVE NO TRUTH VALUE. Nobody must be able to open a file and check it, agree
|
|
with it, or refute it. Put two things next to each other so a shape shows. Do not
|
|
state a fact about the work, the record, the code, or the docket.
|
|
- No advice, no questions, no warning of consequences, no explanation, no second line.
|
|
- Never the word 'we'. No vocabulary of lack.
|
|
- Do not name files, items, numbers of open things, or anything with an address.
|
|
|
|
Output the line and nothing else. No quotes, no preamble."""
|
|
|
|
|
|
def acceptable(line: str, max_words: int = MAX_WORDS,
|
|
one_line: bool = True) -> tuple[bool, str]:
|
|
"""The net. Widening is possible but must be PASSED EXPLICITLY at the call site.
|
|
|
|
⚠ Steward's ruling, 2026-08-25: "If the seam voice needs a wider net because seams
|
|
warrant more than nine words, widen it explicitly and say so, but NEVER relax
|
|
silence-on-violation." So the defaults are the filed ones, a caller that wants more
|
|
room has to say so in its own source, and no caller can turn the net off — the
|
|
clauses below are not parameterised, and deliberately.
|
|
"""
|
|
if not line:
|
|
return False, "empty"
|
|
if one_line and "\n" in line.strip():
|
|
return False, "not one line"
|
|
n = len(line.split())
|
|
if not (MIN_WORDS <= n <= max_words):
|
|
return False, f"{n} words"
|
|
for pat in BANNED:
|
|
if re.search(pat, line, re.I):
|
|
return False, f"banned {pat}"
|
|
return True, ""
|
|
|
|
|
|
def main() -> int:
|
|
kind = sys.argv[1] if len(sys.argv) > 1 else "aside"
|
|
if muted():
|
|
log_event(kind, "muted")
|
|
return 1
|
|
transcript = sys.argv[2] if len(sys.argv) > 2 else ""
|
|
register = soul_register()
|
|
if not register:
|
|
log_event(kind, "no-soul")
|
|
return 1 # no soul, no voice. Deliberately no fallback.
|
|
material = session_material(transcript)
|
|
if not material.strip():
|
|
log_event(kind, "no-material")
|
|
return 1
|
|
env = dict(os.environ, TARBUCKLE_CHILD="1") # precautionary; see body script
|
|
try:
|
|
r = subprocess.run(["claude", "-p", build_prompt(kind, register, material)],
|
|
capture_output=True, text=True, timeout=120, env=env)
|
|
except Exception:
|
|
log_event(kind, "generator-failed")
|
|
return 1
|
|
line = (r.stdout or "").strip().strip('"').strip()
|
|
ok, why = acceptable(line)
|
|
if ok:
|
|
echo = echoes_soul(line, register)
|
|
if echo:
|
|
ok, why = False, f"recited the soul: {echo!r}"
|
|
if not ok:
|
|
log_rejection(why, line, kind)
|
|
log_event(kind, "rejected")
|
|
return 1 # silence. The draw was already consumed.
|
|
try:
|
|
os.makedirs(os.path.dirname(SLOT), exist_ok=True)
|
|
with open(SLOT, "w") as fh:
|
|
json.dump({"utterance": line, "kind": kind, "written": int(time.time())}, fh)
|
|
except Exception:
|
|
log_event(kind, "slot-write-failed")
|
|
return 1
|
|
log_event(kind, "spoke")
|
|
return 0
|
|
|
|
|
|
def selftest() -> int:
|
|
global MUTE, REJECTS
|
|
import tempfile
|
|
checks, failed = [], []
|
|
|
|
def ck(name, cond):
|
|
checks.append(name)
|
|
if not cond:
|
|
failed.append(name)
|
|
|
|
# A1 — the register is READ, not duplicated. One canonical source.
|
|
reg = soul_register()
|
|
ck("A1 soul readable from its filed artifact", bool(reg))
|
|
ck("A1 soul is the real thing", bool(reg) and "Tarbuckle" in reg and "SUCCESSION" in reg)
|
|
# ⚠ The needle is BUILT rather than written, because the first version of this
|
|
# control failed against itself: the literal phrase it searched for was placed in
|
|
# the file BY the search. Same class as the hand-typed link canary whose only
|
|
# finding was the pattern inside its own specification.
|
|
ck("A1n this file does not contain a copy of the soul",
|
|
source_lacks(__file__, "registrar rather than ", "a guardian"))
|
|
ck("A1nn the predicate can fail",
|
|
not source_lacks(__file__, "soul_", "register"))
|
|
|
|
# A2 — the acceptability net. Positive AND negative controls on every clause.
|
|
ck("A2 accepts an in-register line", acceptable("Fourth time. First one was better.")[0])
|
|
ck("A2 accepts a short collision", acceptable("Two names, one thing.")[0])
|
|
ck("A2n rejects too few words", not acceptable("Yes.")[0])
|
|
ck("A2n rejects too many words",
|
|
not acceptable(" ".join(["word"] * (MAX_WORDS + 1)))[0])
|
|
ck("A2n rejects advice", not acceptable("You should check that again now.")[0])
|
|
ck("A2n rejects a question", not acceptable("How is that going for you?")[0])
|
|
ck("A2n rejects vocabulary of lack", not acceptable("The third one is missing now.")[0])
|
|
ck("A2n rejects 'we'", not acceptable("We did the second part first.")[0])
|
|
ck("A2n rejects two lines", not acceptable("First line here.\nSecond line here.")[0])
|
|
# The widening is explicit, and it widens ONLY what it names.
|
|
ck("A2 widening admits a longer line",
|
|
acceptable(" ".join(["word"] * 40), max_words=120)[0])
|
|
ck("A2n widening does NOT relax the clauses",
|
|
not acceptable("You should " + " ".join(["word"] * 40), max_words=120)[0])
|
|
ck("A2n widening does NOT relax 'we'",
|
|
not acceptable("We " + " ".join(["word"] * 40), max_words=120)[0])
|
|
|
|
# A3 — the prompt carries the no-truth-value constraint verbatim, not by intention.
|
|
p = build_prompt("aside", "SOUL", "MATERIAL")
|
|
ck("A3 prompt states no truth value", "NO TRUTH VALUE" in p)
|
|
ck("A3 prompt forbids addresses", "anything with an address" in p)
|
|
ck("A3 prompt embeds the register", "SOUL" in p)
|
|
ck("A3 notable differs from aside",
|
|
build_prompt("notable", "S", "M") != build_prompt("aside", "S", "M"))
|
|
|
|
# A4 — material is the session, never the docket.
|
|
# Structural, not textual: the module's file constants are the only things it
|
|
# opens, so assert none of them addresses the docket.
|
|
paths = (SOUL, SLOT, REJECTS)
|
|
ck("A4 material paths are session/soul only",
|
|
not any(("PENDING" in q or "REVIEWED" in q) for q in paths))
|
|
# A5 — the mute switch, and that every surface must be able to see it.
|
|
_m = MUTE
|
|
MUTE = os.path.join(tempfile.mkdtemp(), "mute")
|
|
try:
|
|
ck("A5n unmuted by default", muted() == "")
|
|
open(MUTE, "w").write("mute")
|
|
ck("A5 mute is read", muted() == "mute")
|
|
open(MUTE, "w").write("off")
|
|
ck("A5 off is read", muted() == "off")
|
|
open(MUTE, "w").write("nonsense")
|
|
ck("A5n a junk value is not a mute", muted() == "")
|
|
finally:
|
|
MUTE = _m
|
|
# A6 — the occurrence log is content-free, and that is the whole point.
|
|
ck("A6 log_event takes no content",
|
|
log_event.__code__.co_argcount == 2
|
|
and set(log_event.__code__.co_varnames[:2]) == {"surface", "outcome"})
|
|
# A7 — the recital net, with the real measured lifts as fixtures.
|
|
_soul = soul_register() or ""
|
|
ck("A7 catches a verbatim sample",
|
|
bool(echoes_soul("Somebody's going to inherit this and think it was easy.", _soul)))
|
|
ck("A7 catches a second measured lift",
|
|
bool(echoes_soul("You've been holding it that way since you were nineteen.", _soul)))
|
|
ck("A7n passes a line that is his own",
|
|
not echoes_soul("Second surface you've found for the same voice.", _soul))
|
|
ck("A7n passes an ordinary short observation",
|
|
not echoes_soul("Third one that's held together with a name.", _soul))
|
|
# A9 — ⚠ EVERY EXIT FROM main() MUST REPORT ITSELF. Earned 2026-08-25: a live
|
|
# `aside` draw vanished with no record, because the muted/no-soul/no-material/
|
|
# generator-failed/slot-write paths all returned silently. The rate report that
|
|
# REVIEWED-128 binds is computed from this log, so an unlogged exit does not just
|
|
# lose a datum — it makes the denominator wrong while looking complete.
|
|
_src = open(__file__, encoding="utf-8").read()
|
|
_main = _src[_src.index("def main() -> int:"):_src.index("def selftest")]
|
|
_returns = _main.count("return 1")
|
|
_logged = _main.count("log_event(kind,")
|
|
ck("A9 every silent exit reports itself", _logged >= _returns)
|
|
ck("A9n the predicate can fail", _returns > 0)
|
|
|
|
# A8 — THE JURIST'S CONDITION, structural. An accepted line has no `why`, so there
|
|
# is no call from which it could be written.
|
|
_r = REJECTS
|
|
REJECTS = os.path.join(tempfile.mkdtemp(), "rej.jsonl")
|
|
try:
|
|
log_rejection("", "an accepted line, offered to the log", "test")
|
|
ck("A8 an accepted line CANNOT be logged", not os.path.exists(REJECTS))
|
|
log_rejection("12 words", "a rejected line", "test")
|
|
ck("A8n a rejected line IS logged", os.path.exists(REJECTS))
|
|
ck("A8n the predicate can fail",
|
|
"a rejected line" in open(REJECTS).read())
|
|
log_rejection("", "a second accepted line", "test")
|
|
ck("A8 accepted lines never appear",
|
|
"second accepted" not in open(REJECTS).read())
|
|
finally:
|
|
REJECTS = _r
|
|
ck("A6n it cannot be handed a line",
|
|
"line" not in log_event.__code__.co_varnames)
|
|
ck("A4n the predicate can fail",
|
|
any(("PENDING" in q) for q in paths + ("/x/PENDING.md",)))
|
|
|
|
for name in checks:
|
|
print(f" {'FAIL' if name in failed else 'ok '} {name}")
|
|
print(f"{len(checks) - len(failed)}/{len(checks)} controls passed")
|
|
if failed:
|
|
print("INSTRUMENT NOT VERIFIED")
|
|
return 1
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
if "--selftest" in sys.argv:
|
|
sys.exit(selftest())
|
|
sys.exit(main())
|