Skip to content

Vibe mode: prompt-to-pattern on the stage, shared by every page - #45

Merged
rossry merged 3 commits into
mainfrom
claude/luminary-vibe-e512rh
Sep 22, 2026
Merged

rossry merged 3 commits into
mainfrom
claude/luminary-vibe-e512rh

Conversation

@rossry

@rossry rossry commented Sep 21, 2026

Copy link
Copy Markdown
Owner

What

/vibe — the stage for a crowd. The sphere on a canvas, a text box, and a coding model:

  • Type what the sphere should do, press enter. The server hands the prompt to the model, validates the module it writes on the real lights, saves it as generation #N (numbered from 1 in order of asking), and hot-cuts the stage to it — the sphere and every open page switch together.
  • Optional name and author boxes; a model select (LUMINARY_VIBE_MODELS, default claude-sonnet-5,claude-opus-5, first is default).
  • Shared state. Everything lives server-side (GET /api/vibe); pages are thin adapters polling it every ~2 s, so five phones at the sphere agree on the thread, what's cooking, what's showing, and the menu.
  • Context. A prompt carries the pattern that was showing when it was typed — name and source — so "slower and more purple" means that one even if someone cut elsewhere while the model was busy. A "from scratch" box drops it. The thread shows "while #N was showing" under each prompt.
  • Always ships. The model may leave a ≤2-sentence note or question beside the result (shown in the thread), but ships its best try to every prompt. Validation (traceback, (n,3) shape, finite, stateless, frame ≤ 250 ms) gets one repair round; a still-failing generation is shown failed and the stage stays put.
  • Nothing deleted, nothing overwritten. var/vibe/vibe-0001.py… (registered as vibe-N, real patterns — queueable from the stage page too), rejected drafts kept beside them as _vibe-NNNN-attempt1.py / -failed.py (the registry skips underscores), var/vibe/log.json is the whole thread; queued/cooking entries are marked failed on restart rather than lost.
  • Menu (☰ hides it): named generations, all generations, then the repo's patterns split by folder (patterns, book-one, book-two, conifer) — click anything to cut to it, for comparison or as the base of the next prompt.

Backends

One contract, two implementations (luminary/vibe/coder.py, session.py; setup.py picks):

  • session (default when claude-agent-sdk imports and a claude CLI is on PATH): each prompt is its own Claude Code session in the checkout via the Agent SDK — read-only tools (Read/Glob/Grep), writes/Bash/web denied, and one MCP tool of ours, ship_pattern, which runs the core's validator on the spot and returns exactly what broke, so the session iterates inside its own turn. Its final words are the note.
  • api: one direct Messages API call (httpx, now a runtime dep) with ANTHROPIC_API_KEY.

LUMINARY_VIBE_BACKEND=session|api forces one; the page footer shows which is running. pip install -e '.[vibe]' brings the SDK.

Security

A generation is model-written code executed in-process — the upload model (spec §15.5.2). Vibe mounts only where upload is allowed or a stage key is configured, and then POST /api/vibe and POST /api/vibe/select take X-Stage-Key through the same stage_key_guard as the queue. serve --no-vibe leaves it off; /api/health reports "vibe". Documented in docs/deploy.md.

Engine changes (small, shared)

  • StageCore.cut(raw): hot-switch — insert after the current entry and jump, or just start it when holding. The vibe surface's one verb; no second render (the page decodes the stage's own WS /api/stage).
  • PatternRegistry: remembers each pattern's origin (origin_of, source_of; list() rows carry "file" relative to their root) — for the folder-grouped menu and for sending the shown pattern's source to the model.
  • stage_key_guard(key) factored out of register_stage, shared by both surfaces.

Verified

  • 19 new tests (tests/test_vibe.py): numbering from 1 and context capture at submit, cook → stamp → validate → save → registry → cut, repair round keeping the rejected draft, failed generations kept with numbering moving on, model failure as a failed generation (worker survives), validator error texts, folder-grouped menu, select, restart resilience, assign_identity cases, the session backend driven by a fake runner (ship-tool round trips, spoken-code fallback, repair prompt), HTTP routes + stage-key gating, create_app mount with the live worker, and the upload-style gating matrix. Full suite green; black + mypy on CI's lists (now including luminary/vibe/).
  • Headless-browser smoke against a real serve --stage-key k: the page connects, decodes the stream, lists 39 repo patterns by folder, cuts on click, and a real prompt ("slow purple tide from the crown") went through a live Claude Code session → validated → vibe-0001.py → stage cut to #1 Tide, with note, in ~37 s on claude-sonnet-5. No console errors; no horizontal overflow at 640/900/1280 px.

Docs

README (Web API rows + "Vibe mode" section), docs/deploy.md (security model + box setup), patterns/README.md (promoting a keeper), plan/spec/implementation-notes.md (component rows, runtime layout), .gitignore comment.

🤖 Generated with Claude Code

https://claude.ai/code/session_01N97HVk3sAr5jRGYodXcDoC


Generated by Claude Code

/vibe is the stage for a crowd: the sphere on a canvas, a text box, and
a coding model. A prompt goes to the model, the module it writes is
validated on the real lights, saved as generation #N (numbered from 1,
never overwritten — rejected drafts kept beside it under a leading
underscore, log.json the whole thread), and the stage hot-cuts to it.
Every open page polls the same server-side state. A prompt carries the
pattern showing when it was typed (name + source), so "slower and more
purple" means that one; "from scratch" drops it. The side menu lists
named generations, all of them, and the repo's patterns by folder.

Two backends behind one contract (always ship a best try, optional
short note): SessionCoder runs one Claude Code session per prompt
through the Agent SDK — read-only in the checkout, shipping via a
ship_pattern MCP tool that validates in-turn and returns what broke —
and Coder makes one direct Messages API call. The page's model select
picks from LUMINARY_VIBE_MODELS. Mounted only where upload is allowed
or a stage key is set (the upload security model); mutations take the
key. serve --no-vibe leaves it off.

Engine changes: StageCore.cut (insert-after-current + jump, or start
when holding), PatternRegistry origins (origin_of/source_of, list()
"file"), stage_key_guard shared by the stage and vibe routes. httpx
moves to runtime deps; a [vibe] extra brings the Agent SDK. CI's black
and mypy lists gain luminary/vibe/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N97HVk3sAr5jRGYodXcDoC
CI's mypy runs without the [vibe] extra, so claude_agent_sdk is an
unresolved import there and the @tool decorator became untyped. A mypy
override treats the SDK as Any when absent, and the ship tool is built
by applying tool(...) as a call, so the function stays typed in both
environments.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N97HVk3sAr5jRGYodXcDoC
The allow list (read-only tools + ship_pattern) still pre-approves and
the deny list (writes, shell, web) still blocks first, so the classifier
only ever sees a call outside both and nothing waits on a prompt; unlike
bypassPermissions, auto is not refused under root. The mode is a knob
(LUMINARY_VIBE_PERMISSION_MODE, default auto; "default" for
allowlist-only, needed with Haiku-class models, which auto mode does not
support). Verified live: a real session as root shipped a validated
pattern onto the stage in auto mode.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N97HVk3sAr5jRGYodXcDoC
@rossry
rossry merged commit c9ef962 into main Sep 22, 2026
1 check 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.

2 participants