Skip to content

self: exciting deck card glow - #35

Merged
RonTuretzky merged 14 commits into
mainfrom
room/dobbin-street-intersection-know
Aug 24, 2026
Merged

self: exciting deck card glow#35
RonTuretzky merged 14 commits into
mainfrom
room/dobbin-street-intersection-know

Conversation

@RonTuretzky

Copy link
Copy Markdown
Owner

spoken in the room

RonTuretzky and others added 14 commits August 23, 2026 21:35
… "just arrived"

Two things the room was doing wrong when you looked at it.

PICKING A CONSTELLATION used to POST a research quest at that thread's
freshest utterance and show nothing at all — a heavy invisible side effect
for a gesture that reads as "tell me about this". It opens a TOPIC CARD
now: the thread's span, how many turns, who mostly spoke, the lines in
spoken order, what it relates to and why, and — when the relate agent has
written one — a one-sentence abstract of the thread. Researching it is a
verb ON the card.

The card will not invent a recap. `summaries` is new in the relate tick's
reply, so an abstract is the model's or it does not exist; with none, the
card distinguishes "not summarized yet" from "no model has ever spoken
here" and shows the actual lines. Lines say which half they came from:
"said" (live text) or "recalled" (an <=80-char star gist kept after the
window dropped the turn, ellipsis and dimmer). Elided history is stated
outright — "12 turns · 3 earlier no longer kept" — instead of implying the
thread is complete. Paged, never scrolled: the wall has no wheel.

The detail rides a fetch (GET /api/research/sky/topic/:id), not the
snapshot — abstracts plus every line of 24 clouds on every SSE frame, for
a card that shows one at a time, is not a trade worth making. A merge
drops both abstracts rather than letting the absorbed thread's sentence
describe the union.

"⚘ PLANT IT…" ON ORDINARY STARTUP was a real bug, not noise. The arrival
offer fired on "a upid this wall has not seen", with the first snapshot
seeding the seen-set silently — so a wall that connects while the server
is still filling that first snapshot seeds an EMPTY set, and every
previously imported project announces itself as a fresh arrival. Same on
any reload, and on the second wall of a two-wall rig. Imports now carry
the server's arrival stamp and the offer keys off THAT (3 minutes), which
survives reloads and second walls; a server too old to stamp stays silent
rather than announcing wrongly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…othing

STEER AN EXISTING BRANCH. Every record window on the room cut a FRESH
room/<slug> branch off the current one, and the branch scope the wall sent
with a scoped window was dropped on the floor — so "steer this branch"
quietly grew a sibling of it. Now a scoped window stands the room ON that
branch first (the agent only ever commits where it stands), and the branch
card offers 🌱 Graft onto this branch on any local branch, not only the one
the room happens to be running. Climbing without speaking is still its own
verb; an unscoped window still cuts fresh, which stays the default.

THE REFUSAL IS THE POINT. If the room cannot reach the branch you asked
for — it was pruned between arming and speaking, or the tree has
uncommitted src/ work a checkout would drag along — the change is
dispatched NOWHERE. Growing it on whatever branch happens to be checked
out is worse than doing nothing, because nobody finds out until they read
the log. The verdict rides the snapshot as `selfLanding` and the post-Stop
receipt says which branch grew the change, or refuses out loud:
"couldn't graft onto tulips — no branch named room/tulips, it may have
been pruned". A refusal never renders as "✓ growing this change".

AND A BUG THE TESTS FOUND on the way. Every dirty-tree guard parsed
`git status --porcelain` with `line.slice(3)` over a blob the caller had
already trimmed — and trimming eats the leading space of the FIRST line's
two-column status ("` M src/ui/App.tsx`" → "`M src/ui/App.tsx`"), so
slice(3) returned "rc/ui/App.tsx" and failed its own src/ test. The first
modified file in the list was invisible: a single unstaged edit passed the
check, and /api/self/checkout would climb with it in tow. porcelainPath()
handles trimmed and untrimmed lines and every status pair; all four call
sites go through it.

Coverage: the graft decision and all six refusal paths are unit-tested
against a scripted git (self-graft.test.ts). The "nothing is dispatched on
refusal" branch in #drainSteerGrace is by construction (early return
before registry.steer) and NOT covered end to end — the self mirror has to
be a live registry record for that drain to run at all, which needs the
self-commission harness.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…and honest issue ages

You imported a repo, typed "just study it first", and the room built it
anyway. The description was only ever used as build framing, so a perfectly
clear instruction was silently dropped — and the tree it grew showed build
controls and not one fact about the project you could pick issues off.

AN IMPORT NOW DECLARES AN INTENT (project-intake.ts). Asking to read —
study, look at, understand, explore, audit, "don't build anything yet" —
means the room reads it and builds nothing. A repo pasted with NO
instruction is also studied: nobody asked for anything to be built, and a
codebase nobody has described is exactly where a read comes first.
Everything else is a build, because describing something IS asking for it.
The build side is deliberately not a verb list — "give the widget a dark
mode" is plainly work, and no enumeration survives how people actually
write. Asking to read is the marked case, and it has the short checkable
vocabulary.

A STUDIED PROJECT GETS A BRIEF, on the tree's new "📖 About this project"
row: what it appears to be, its stack and layout, the README's opening, and
the ask it came in with, verbatim. Every line is derived from the real
checkout — the digest's hedge ("appears to be…") is kept rather than
upgraded into a statement, a repo that would not clone says THAT instead of
rendering an empty card, and the wall only grows the row when a study
actually exists behind it. Building is one press from the brief; the study
is the first step, not a dead end. The git substrate still births for a
studied import, so its tree keeps branches and issue fruit — skipping that
would make the room worse at exactly what someone studying a project wants.

AND ISSUES STOP PRETENDING TO BE LIVE. Open issues hang on a tree as fruit
you can pick and start work on, which reads as "this is all live work" — but
a tracker nobody grooms leaves issues fixed or abandoned years ago. The
API's updated_at rides the contract now: the card says "last touched 14
months ago", warns that it may already be done or no longer wanted, and a
stale issue takes TWO presses to adopt. Unknown is its own state, never
rendered as fresh; nothing warns about issues that are plainly current.

Suite: 1310 pass / 0 fail. The import tests that asserted "a GitHub link
becomes a REAL building project" now ask for the build in words, which is
the new contract, and one new test locks the default: a bare link is
studied, the brief route serves it, and registry.builds stays empty.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Partial on "every tree gets the full tend package": the verbs an adopted
tree can ACTUALLY back are now the same verbs, in the same plant language,
as the room's own tree.

  • 🌱 Graft onto this branch (was "🎙 Steer this branch") — same rail as
    before (select {branch} → the steer applier's real commit), same words
    the room's own branches use.
  • ✓ Finalize · into the trunk — squash-merges the branch's open PR into
    the origin's main via the branch/:branch/merge route. Two-stage like
    every destructive verb here, with a 10s fall back to resting, and the
    server's refusal ("no PR is open for this branch") verbatim.

WHAT IS DELIBERATELY MISSING: prune. There is no delete-branch rail on the
adopted clone substrate, and the self tree's prune-everywhere runs through
temp worktrees and CAS ref updates that have no adopted equivalent yet. A
verb with no rail behind it is the one thing this surface must never grow —
so the adopted card shows what it can do and no more. Same for the radial
chip layout, which stays self-only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d on it

Reported twice now: "the whole tree history is now reverts", and then "I
still see a bunch of the revert titles on the list". Both were true, and the
second one was my fault.

The prune-everywhere works by reverting the pruned graft on every branch
that carried it — so thirteen branches all ended up with the SAME tip
subject, 'Revert "self: make each tree's dancing dog a purple chihuahua"'.
selfBranches() labelled each row by its tip, so the rail became thirteen
identical reverts and the garden's history was unreadable.

A room branch's identity is the SPOKEN graft it carries, so the label is now
its newest `self:` commit — reverts, merges and hand commits skipped. A
branch with no spoken graft keeps its tip subject, which is honest because
there is nothing better to call it, and a git failure leaves the tip rather
than blanking the row. On the live rail that turns thirteen identical
reverts into pink tulips, GPU server racks, the dancing porcupine, the
control dock's fold delay — 20 of 22 branches get a real name.

WHY IT CAME BACK: this fix was already written this session, and it landed
on room/mimic-central-park-mimic — a SIBLING branch cut from the same parent
as the branch the room was actually running. Never merged into main, never
an ancestor of HEAD, so the running room never had it. That is exactly the
hazard "graft onto an existing branch" now exists to prevent, and it is the
answer to the earlier question about whether these branches stack: they do
not, unless you make them.

Tests cover the live case (three branches, one shared revert tip, three
distinct names out), newest-graft-wins, the no-graft fallback, and the git
failure path.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…er, not itself

"I don't see the words appearing when I'm grafting on the dobbin street
branch." The room was hearing every one of them.

RecordSteerToggle echoes speech from a `transcript` prop that was OPTIONAL,
and BranchPopup never passed it — on either path. heardSince([], armPoint)
is [] for every armPoint shape, so the card sat pinned on "listening — say
the whole change" for the entire window while the server appended each FINAL
to the steer slice exactly as designed. Optionality was the hole that let
three call sites forget it silently, so `transcript` is now REQUIRED, with
null as the explicit "not wired" value: a forgotten wire is a compile error.
Slideshow's deck card had the same blank echo and is threaded too.

THE RECEIPT WAS WORSE THAN THE ECHO. It derived "did the room hear anything"
from that same dead client-side echo (caughtNothing = dispatched.length ===
0), so a graft the server really cut and dispatched was announced as "heard
nothing — no graft was made" — a confident false negative about work that
happened, which is the exact class of lie this surface exists to prevent.
Of the twelve (echo) x (landing) combinations, seven stated something false.

receiptState() is now pure, exported and ranked, and it asks the SERVER
first: a fresh landing (refused, with the reason verbatim / landed, with the
branch) outranks everything; then a build card's "sent"; then "cutting"
while the room is still answering; then "silent" ONLY when the card was
actually wired and still saw nothing; then "unanswered". Freshness is gated
on `steeringSince`, a new per-process stamp for when the window opened —
the server never clears #selfLanding, so an ungated card would stamp the
PREVIOUS change's branch name onto a graft still being cut. steeringSince
also fixes a second real case the diagnosis turned up: a card that MOUNTS
mid-window (the branch popup opened after the graft was armed) had no
watermark of its own and echoed nothing. The room stamps the window now;
the card no longer has to guess.

Gates: tsc clean, 1327 pass / 0 fail, build green — run independently by a
second agent, and a third traced the operator's exact path end to end and
failed to refute it. The SSR test added at projector.test.tsx:2693
reproduces the live symptom when the fix is reverted.

KNOWN AND NOT FIXED HERE (found by the completeness pass, worth its own
change): #selfLanding records only the BRANCH-CUT outcome. registry.steer is
fire-and-forget and SelfCommissioner.steer can answer {accepted:false,
reason:"busy"|"reloading"|"launch-failed"} without that ever reaching the
snapshot — so grafting twice in a row, where the second is refused as busy,
still prints "✓ graft taken". Same lie, inverted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…hards uprooted

Operator: "please revert all the cosmetic one off commits the cats dogs
computers specifically." These were joke props grafted in by voice during
demos, and each had grown TWO bodies — a field of them scattered through the
meadow, and a companion attached to every garden tree — plus frame-loop
animation and disposal. 338 lines out of RoomScene.tsx, 22 in.

Gone: the dancing cats, the dancing dogs (parented to the cat group, so they
came out as one unit), the GPU server-rack clusters at each tree's foot, the
crystal-mana shards, and the last tulip remnants.

KEPT, deliberately: the parallel ghost sky (verified byte-identical) — it
reads as scene-wide atmosphere rather than a one-off prop and was not asked
for. One word and it follows them out. Also kept: every test fixture that
merely QUOTES these commit subjects as branch-label data. Those are tests
about commit messages, not about props.

NOT a git revert. The commits are 15+ deep and the files have moved on;
95bc5a9 is the precedent, where the tulip excise "met its real-world limits"
and came out by hand. History keeps them; the garden does not.

Two things the removal turned up. `catBase` was SHARED with the horse-head
companion, which survives — deleting it would have broken the horse, so it
was renamed rather than removed. And `makeGpuRackCluster` allocated three
BoxGeometry objects per call while flagging only `ownMaterial`, so
disposeEntry freed the materials and leaked the geometry — three per garden
tree per structural rebuild. Uprooting it is a net leak fix.

Verified by three independent passes: a refuter that grepped every removed
identifier and audited disposal in BOTH directions (no dispose left for a
deleted object, none removed for a live one); an independent gate run (tsc
0, 1327 pass / 0 fail, build green, HEAD unmoved, diff confined to one
file); and a real-GPU Playwright check that built HEAD in a throwaway
worktree for a before/after baseline and opened the actual wall — 0
pageerrors, no mention of cat/dog/mana/rack/undefined. That last one also
recorded an honest environment gap: headless Chromium gates the whole
photoscan meadow behind a softwareGL check, so it had to run headed on the
Metal context for the meadow layer to be visible at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e rename)

Pulled the smithers migration (PR #36) plus the e2e catch-up. One conflict,
in src/server/composition.ts, and it was a pure duplicate: main carries
13d1839 ("a branch is named by what it grew"), which is the same relabel I
re-implemented locally in ee0a545 after finding the original stranded on an
unmerged sibling branch. Resolved by keeping ours — identical logic, and the
comment here also records WHY it had to be written twice. main's only change
to that file was that block, so nothing was dropped.

Correcting my own earlier report while I am here: I said the relabel was "in
main: NO". That was read off a stale local ref before any fetch — it IS on
origin/main, and has been. What was true, and what caused the symptom, is
that it was never an ancestor of the branch the room runs.

`bun install` pulled smthrs@0.35.0 (421 packages). tsc clean, 1327 pass /
0 fail, build green.

NOT DONE BY THIS COMMIT — the deploy note on 0213be3: the room's gateway
mode talks to the machine's GLOBAL smithers CLI, still 0.23. A 0.35 client
against a 0.23 gateway spans the Effect 4 protocol churn and is untested, so
the global CLI has to move in lockstep before the next live run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… ghost sky

The operator asked whether the vestigial cosmetic-only changes were actually
cleaned up. They were not — two one-off grafts of the same family survived the
last pass:

  • fa39481 "self: add a low-poly horse head companion at each garden tree
    foot" — the dancing cat's direct sibling, same voice-graft, same spot. It
    survived only because the operator's list named cats/dogs/computers and I
    scoped to the named set. That was my miss.
  • 615795d "self: a parallel ghost sky bleeds through the garden" — the
    one-off I flagged and kept last time.

Both out: 62 lines from RoomScene.tsx. The butterflies STAY — different
provenance entirely (4d81361, the pastoral daylight redesign), deliberate
environment design rather than a demo graft.

VERIFIED, because a typecheck cannot see a three.js scene. Headed on a real
Metal GPU (softwareGL=false, so the photoscan meadow the props lived in
actually built — headless gates that whole layer off and would have proved
nothing). Scene mounts at 84 draw calls, zero pageerrors, and three full
dispose/rebuild cycles with the context still alive. An A/B against HEAD
built in a throwaway worktree shows the ground PIXEL-IDENTICAL (residual
0.09/255) and the change purely subtractive (0.01/255 new) — the residual
image is literally a picture of the deleted ghost sky, clipped at the horizon
and at every tree silhouette.

The removal also fixes a latent DOUBLE-DISPOSE: the ghost sky borrowed the
real dome's SphereGeometry, so the env teardown's generic traverse called
dispose() on the same geometry twice. It now disposes once. skyTexture was
only ever borrowed by the ghost and is still created and freed exactly once.

Two leftovers the audit flagged, cleared here rather than left:
  • GEO.crystal is gone — the horse's ears were its last consumer, and a
    shared geometry with no meshes is allocated and disposed every scene
    build while being invisible to tsc and lint.
  • e2e/garden-picking's K4 seed ("the self tree has no companions") is
    RETIRED rather than blocked. No tree has companions now, so the asymmetry
    cannot occur; a suite advertising a bug that can no longer happen is
    lying in the other direction.

tsc clean, 1327 pass / 0 fail, build green. (cloneRepo/repoDigest time out
under parallel load and pass in isolation — the known flake.)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Found across two machines running the same room. One operator's Smithers
gateway was healthy with real accumulated state; the other's port had nothing
bound on it at all — and BOTH rooms reported only tts/sink degraded. The room
pointed at a dead port and called itself well, while every spawn would have
failed.

degradation-notice.ts only ever emitted a smithers leg when the selection was
"memory". Setting VIBERSYN_SMITHERS_GATEWAY_URL flipped it to "gateway" and
nothing asked again. That is right for every OTHER leg — asr/tts/sink/decider/
summarizer are backends chosen at boot that cannot vanish — but the gateway is
a separate process on a port, on a machine that may not even be this one.

THE SHAPE MATTERS, and this follows the reviewing agent's design note rather
than my first instinct. buildDegradationNotice is pure and synchronous, and
putting an `if` with I/O inside it would have broken that for one leg. So the
probe lives at the health-endpoint layer (gateway-probe.ts) and its RESULT is
passed in as data — the notice stays pure, and every leg's reasoning stays in
one place. /api/health is async now and rebuilds through degradationNow().

The probe is bounded (1.5s — a status page must never hang on a dead port) and
cached (10s, with in-flight coalescing, so a burst of health hits costs ONE
round-trip and a status page does not become a load generator). A gateway that
dies mid-session surfaces within the cache window.

PROTOCOL, free in the same round-trip: the gateway advertises {"protocol":N}
and src/seam/smithers-client.ts never read it — zero hits for protocol or
apiVersion. A mismatch was destined to surface as a mid-build failure nobody
could trace; it is a boot-time notice now.

Unmeasured stays unmeasured: reachable === null claims nothing in either
direction, and calling buildDegradationNotice with no liveness argument
behaves exactly as before.

Proven against both real conditions on this machine, not just fixtures:
  DEAD  :7399 -> {"reachable":false,...}  leg: "Smithers gateway at
        http://127.0.0.1:7399 is not answering (Unable to connect...) —
        every spawn will fail"
  ALIVE :7331 -> {"reachable":true,"protocol":1}  leg: none, healthy
Plus 16 unit tests covering timeout, refused, non-gateway responder, cache
lapse, coalescing, protocol mismatch, and memory-mode isolation.

tsc clean, 1343 pass / 0 fail, build green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@RonTuretzky
RonTuretzky merged commit b37cbc7 into main Aug 24, 2026
2 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant