[FIX] Tarbuckle tier 2: the mumble ticks on the clock and speaks from the soul
Material, register and occasion as AMENDMENT 6 separates them: the live session supplies the material, the filed soul supplies the register, the clock supplies the occasion. 73/20/7 drawn at each 20-minute tick, consumed whatever it says. The soul is READ from its artifact at run time, never pasted here — a copy would be a parallel version of a governed record. No soul, no voice; there is deliberately no fallback register, because a fallback voice is a second fool nobody derived. The utterance must have no ADJUDICATION PATH, not merely be unfalsifiable in principle. The prompt says so verbatim and a mechanical net sits under it: 3-9 words, one line, no advice, no questions, no "we", no vocabulary of lack, nothing with an address. A violation yields SILENCE, never a repaired line — rewriting the fool's words would make the executor its editor. Rejections are logged so the two-week rate report states the true rate rather than the drawn one. Generation is detached because a headless call measured 7-11 s and a status line cannot wait. A recursion guard rides along, and is honestly precautionary: headless claude was observed NOT to render a status line (zero invocations logged across an 11 s call), so the guard is one env check against a fork bomb, not a fix for something seen. 30/30 controls on the body, 18/18 on the generator, positive and negative throughout. D2 holds the proportions to the filed 73/20/7 over 60k draws; D5 proves the tick consumes on a silent draw, which is the determination that forbids a conserved draw. ⚠ One control failed against ITSELF: "this file contains no copy of the soul" searched for a phrase its own needle had placed in the file. Fixed by building the needle rather than writing it. Same class as the hand-typed link canary. The two-week deferral converted manual -> date 2026-09-08, as its own discriminator instructed, the day the body shipped. No manual-only deferrals remain. AMENDMENT 8 files the substrate findings, including the one clause of this item's own corrected text that does not survive contact with the schema. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01J6hZXNYSxEfZseBGTni4sf
This commit is contained in:
co-authored by
Claude Opus 5
parent
6f0ccde4c6
commit
3df5e4f4bb
Executable
+229
@@ -0,0 +1,229 @@
|
||||
#!/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
|
||||
)
|
||||
|
||||
|
||||
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:
|
||||
- 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) -> tuple[bool, str]:
|
||||
if not line or "\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"
|
||||
transcript = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
register = soul_register()
|
||||
if not register:
|
||||
return 1 # no soul, no voice. Deliberately no fallback.
|
||||
material = session_material(transcript)
|
||||
if not material.strip():
|
||||
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:
|
||||
return 1
|
||||
line = (r.stdout or "").strip().strip('"').strip()
|
||||
ok, why = acceptable(line)
|
||||
if not ok:
|
||||
try:
|
||||
with open(REJECTS, "a") as fh:
|
||||
fh.write(json.dumps({"t": time.strftime("%Y-%m-%dT%H:%M:%S%z"),
|
||||
"kind": kind, "why": why, "line": line[:200]}) + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
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:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
||||
def selftest() -> int:
|
||||
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.
|
||||
needle = "registrar rather than " + "a guardian"
|
||||
ck("A1n this file does not contain a copy of the soul",
|
||||
needle not in open(__file__, encoding="utf-8").read())
|
||||
|
||||
# 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])
|
||||
|
||||
# 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))
|
||||
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())
|
||||
Reference in New Issue
Block a user