← All projects
Voice conditioned audiovisual installation Active

Windchime

Audio language models retrieve real stems from a closed, artist authored corpus and render them as live coded sound, generative visuals and monome light.

✳ Thesis

Retrieval, not generation, is the honest interface between a voice and a sound library: answer with real material, in real time, and never go silent.

Shown at Gray Area, San Francisco; accepted to NeurIPS 2026 Creative AI, Sydney Audio language models (CLAP, CLaMP 3)faster-whisper ASRFAISSPythonFastAPIStrudelp5.jsThree.jsmonome
Windchime technical rider: installation diagram with labeled components
Technical rider
Gray Area NeurIPS CCRMA, Center for Computer Research in Music and Acoustics Stanford University

Paper coauthored with Celeste Betancur, CCRMA, Stanford University.

Windchime: a ring of suspended chime tubes rendered against a starfield

01 · Demo

See it running

02 · At a glance

Role
Built solo (product, engineering and research); the NeurIPS paper is coauthored
Timeframe
May 2026 to present
Audience
Untrained gallery visitors, once each; the unattended install's operator.
Collaborators
Celeste Betancur (CCRMA, Stanford University), coauthor of the NeurIPS 2026 paper
Stack
  • Audio language models (CLAP family, CLaMP 3)
  • Python
  • FastAPI
  • faster-whisper ASR
  • FAISS
  • Strudel live coding
  • p5.js
  • Three.js
  • monome (serialosc)
The problem

Audio language models act as retrieval curators over a closed, artist authored corpus; nothing is synthesized. The artist authors, the visitor initiates, the model orders access. That split must be legible within seconds, unattended, all day.

Constraints that shaped it 5
  • 6 to 8 hour unattended runs, self recovering
  • Audio never stops: failover planner, in place watchdog
  • Visitors get one try: the first prompt must land
  • monome Grid and Arc over serialosc, hotplug safe
  • Speech transcribed on device, never recorded

03 · Outcomes

What changed

  • 6 hour unattended soak: 879 synthetic visitors, zero wedges, zero crashes
  • Audio runaway cut from 2.8× full scale to 0.36 (three layer fix)
  • Corpus grown from 130 to 406 stems, eleven roles
  • Six deployable ALM configurations behind one interface
  • 135 visual scene families, 63 on the live rig
  • Runs fully offline, no venue internet

04 · Recognition

San Francisco, then Sydney

Gray Area

San Francisco, USA · Aug 5–7, 2026

Debuted at the Gray Area Cultural Incubator Showcase.

NeurIPS 2026 · Creative AI Track

Sydney, Australia · December 2026

Accepted artwork, under the track theme Agency.

“Windchime: an Audiovisual Installation with Audio-Language Model implementations”

It shows how agency splits three ways: the artist authors the corpus, the model shapes what is reachable, and the visitor’s speech drives each turn.

Read it on OpenReview

05 · Architecture

How it fits together

Voice microphone
ASR on device
Retrieval ALM + FAISS
Guarded planner fills a schema
Live coded audio Strudel
Generative visuals p5 / Three.js
monome LEDs
One direction of flow: a spoken phrase becomes sound, light and hardware state.

06 · The hardware

Tactile control, mirrored on screen

Digital twin: grid 128 (16×8, varibright) + arc 2 · ♪ LISTEN

grid ● hardware: 16×8 (128) · 2 quads · varibright 0–15 · tilt ✓ arc ● hardware: 2 enc × 64 LED (varibright) · push per enc

A monome Grid and Arc, remapped per scene over serialosc, steer visuals only; an onscreen twin mirrors every LED.

07 · Under the hood

Retrieval in embedding space

Drag to rotate · 3D projection of the joint text/audio embedding space synthetic points, not the private corpus
drums bass keys texture field vocals spoken prompt, embedded selected (one per role)
Contrastive pretraining puts text and audio in one space: L2 normalized vectors ranked by cosine similarity over a FAISS exact index. The installation's rail projects the live index (UMAP, PCA or t-SNE).
08 · The story

How it came together

The idea

Speech shapes music and visuals, answered from real recordings rather than fixed rules. It builds on Live Muse, a separate earlier installation shown at Mutaciones, Barcelona.

The pipeline

A visitor speaks; faster-whisper transcribes on device, recording nothing. The active ALM embeds the words; cosine similarity over a FAISS exact index and one per role selection pick one stem per role. A guarded planner fills a validated schema compiled to Strudel, never raw code, with a failover chain for offline sound.

Built for the room

Imagistic words retrieve more loosely than descriptive ones; that drift became a reward for play. A mic meter, computing cascade and LED flash make the machine legible; an audio watchdog, offline runtime, device guard and profanity filter carry it through hour six, proven by soak.

09 · Roadmap

Now, next, later

Now 4
Reliability

Close out the microphone fault

Guard shipped; open: the stop path deadlock, silent capture detection.

In progress confidence: medium
Corpus

Level and prune the corpus on evidence

Unlevelled stems, overanswering stems, an unwired exclusion list.

In progress confidence: high
Corpus

Grow and reembed the audio corpus

Content aware windowing; each rebuild appends an index epoch.

In progress confidence: high
Corpus

The coverage audit as a corpus design tool

Redescribe unreachable stems; extend saturated prompt categories.

In progress confidence: medium
Next 3
Reliability

Soak the audio path and watchdog

Real playback, stop and replace and forced faults.

Planned confidence: medium
Experience

Settle the exhibition posture for sound modes

Which preset to exhibit, and planner or bypass.

Planned confidence: medium
Research

Audit transcription with live speech

Live speech through faster-whisper, not synthetic text.

Planned confidence: medium
Later 3
Reliability

Monome hardware endurance pass

Full day Grid and Arc test (hotplug, marginal USB), after the encoder repair.

Planned confidence: low
Visuals

Per family visual tuning

Idle composition, exposure and framing, family by family.

Planned confidence: medium
Research

Ablate the one per role selection policy

Unsegmented retrieval separates model behaviour from policy.

Planned confidence: low
Shipped 1
Reliability

Ship the unattended installation

Kiosk lifecycle plus three layer audio safety.

Shipped
10 · Timeline

What happened, in order

  1. Release Public debut at Gray Area

    Shown at the Gray Area Cultural Incubator Showcase in San Francisco, August 5–7, with a talk of under five minutes on how agency splits between artist, model and visitor.

    Customer value. The first public audience, in a working venue rather than a test room.

    • The venue hardening build: offline, guarded devices, public safe text
    • A presenter deck and script, practiced to under five minutes
    • The agency diagram: artist, model and visitor
  2. Release Venue hardening: offline, guarded devices, public safe text

    For a hostile venue.

    Customer value. Offline, on chosen devices, never showing visitor phrasing.

    • Vendored runtime
    • Offline default, supervised guard
    • Guard holds chosen input and output
    • Wedged mic detection, clean exit
    • Word boundary text filter
    • Runbook with no internet path
    • Frequency to loudness audit

    Risks

    • Device identifiers can shift on reenumeration
  3. Incident · Sev2 Open Nearly a quarter of the corpus plays unlevelled

    93 of 406 stems lack a usable loudness measurement.

    Impact. Too loud or too quiet, heard as bad mixing, not bad data.

    Detection. A full corpus audit, stem by stem.

    Root cause. A fixed window near each file's start misses later audio, which gets a neutral correction.

    Fix. Not yet fixed: prefer the existing full file measurement when the window finds nothing.

    Followups

    • Fall back to the full file measurement (open)

    Blameless note. The embedding side already fixed this bug class; the lesson did not travel.

  4. Release Operator tuning stage and corpus driven levelling

    With a live spectrum feed.

    Customer value. Balance the room in minutes; textures stop swamping percussion.

    • Install defaults
    • Nongenerated beds loudness levelled
    • Endpoint gain levels sequence beds
    • Presence trim for continuous stems

    Risks

    • Some stems lack a loudness measurement
  5. Incident · Sev2 Open The stem exclusion list was never wired up

    Editing the list changed nothing.

    Impact. Corpus judgements that assumed exclusions used the unfiltered library.

    Detection. A call site search during the corpus audit.

    Root cause. Unit tested in isolation and never called; passing tests is why nothing flagged them.

    Fix. Not yet fixed; wiring it into selection is scheduled.

    Followups

    • Wire in, verify with a real generation (open)

    Blameless note. A test proves a function works, never that anything calls it.

  6. Incident · Sev1 Monitoring The microphone goes deaf midsession

    Capture silently dies: two software faults.

    Impact. An unattended piece faces visitors it cannot hear.

    Detection. Operator reports, then a device versus stream diagnostic.

    Root cause. A cached device list lets a hotplug silently repoint capture at the internal mic; a lock order inversion can deadlock the stop path, hanging every mic endpoint.

    Fix. Reenumeration revives a dead input without a replug; a device guard holds the chosen one. The open deadlock clears on restart (per process audio context).

    Followups

    • Work around the stop path deadlock (open)
    • Test silent capture detection deliberately (open)

    Blameless note. Watchdogs guarded output, not capture; a health endpoint must never take the lock it reports on.

  7. Release Sound mode presets and monome depth

    Across grid and arc.

    Customer value. Sessions get an arc; every scene rewards hardware exploration.

    • Per prompt mode sequences, install default
    • Shared page and chord gestures
    • Depth on all install families
    • Per scene rotation LED feedback
    • Thinking bed before first audio

    Risks

    • Dead arc encoder switch blocks chords
  8. Incident · Sev3 Open A dead arc encoder switch, proven by paired markers

    Silent, exactly like a mapping bug.

    Impact. Its press gestures, including a two encoder page flip chord, are unreachable.

    Detection. Six paired marker attempts: 28 turns, zero key events.

    Root cause. Hardware: the code path treats both encoders identically.

    Fix. No software fix by design: gestures moved off it, and the hardware needs repair.

    Followups

    • Repair or replace the controller (open)

    Blameless note. Pair an unobservable null result with an observable action, so silence becomes evidence.

  9. Incident · Sev3 Resolved The tour's first line went silent after round one

    Later visitors' tours began midthought.

    Impact. The one moment meant to orient a stranger broke.

    Detection. Heard in rehearsal, weeks after round one was assumed correct.

    Root cause. During a fade, a gain still reads its starting level, so the guard thought narration audible and skipped the restore.

    Fix. Cancel pending automation and set the value outright; the race had two orderings, both fixed.

    Blameless note. It hid until automation ran, since settled values read correctly; partial fixes can disguise a second cause.

  10. Release LLM bypass, and a measured latency profile

    Retrieval straight to playback.

    Customer value. A fraction of a second, not several, with no model needed.

    • Planner dropdown option
    • Limiter and watchdog still apply
    • Teardown on stop
    • Latency by backend and mode

    Risks

    • Bypass plays denser than templates
    • Latency gap hinders blind comparison
  11. Release Reference art scene waves and a repeatable method

    Built by playbook, not improvisation.

    Customer value. Visitors rarely see a world twice.

    • Six waves: registry 69 to 135
    • Playbook: briefs, dispatch, gating, verification
    • Host rendering facts verified
    • Registry wide regression gates green

    Risks

    • Per family tuning now costs more
  12. Release Presentation mode and curation tools

    Show and curate from one build.

    Customer value. It shows cleanly; curation stays in the interface.

    • Fullscreen hides developer navigation
    • Click and hold stem audition
    • Curation lock
    • Wedge watchdog
    • Latent map labels model, tracks selection
    • Wider prompt pools
    • Attract repeat fixed
  13. Release Corpus to 406 stems, size aware audit

    One studio batch, up from 369.

    Customer value. More to reach, and an audit honest at any size.

    • Batch reembedded per configuration
    • Size read from the DB
    • Index epoch recorded
    • Notebook projection explorer

    Risks

    • All time counts favour older stems
  14. Release Corpus growth through repeatable studio batches

    Repeatable and auditable.

    Customer value. More varied, better labelled material.

    • Batches sorted by category
    • Idempotent reingest via skip sentinel
    • Reembed runbook, every backend
    • Index history per reindex

    Risks

    • Fair comparison means reembedding every backend
  15. Release Original Three.js scene waves

    Four waves, 69 families.

    Customer value. Place rooted originals: expressive clicks, LED acknowledgement, the whole grid alive.

    • O1 and O2: abstract, kinetic
    • O3 and O4: recording location cities
    • Authoring guide codifies monome rules
    • Regression gates green corpus wide
  16. Release Boot time visual runtime selector, with Three.js scenes

    Beside p5, one runtime per session.

    Customer value. A direct fidelity comparison; hardware and interaction unchanged.

    • Selector before renderer or audio init
    • URL pin for kiosk boots
    • Second host sharing params, LED flush, mount
    • p5 families ported to Three.js
    • Original 3D scenes
    • Per runtime dynamic imports

    Risks

    • Newest scenes need audition and tuning
    • Hardware pass pending; twin verified only
  17. Release Install mode feature wave

    Shipped in one day.

    Customer value. Orientation, and sound between visits.

    • Mic VU meter
    • Narrated tour, pregenerated clips
    • Hosted or local LLM planner, safe revert
    • Grid+Arc families, 38 to 43
    • Header status pills
    • Alternating attract beds
    • Sidechained tutorial bed
    • Idle bed after visits
  18. Incident · Sev2 Resolved A closed kiosk page could latch the audio gate

    Audio stranded in the wrong phase.

    Impact. No operator would clear it, defeating the self resetting lifecycle.

    Detection. The soak's abrupt page exits, which manual tests rarely reach.

    Root cause. Only a graceful showcase exit cleared the gate.

    Fix. Exit posts an idle phase, and a pagehide beacon (which survives teardown) hits a reset endpoint.

    Followups

    • Cover future kiosk autoboot paths (open)

    Blameless note. Long unattended runs hit abrupt page death; short manual tests miss it.

  19. Incident · Sev3 Resolved Ended sessions rang back to life

    Twice: a watchdog rescue, then gain masked voices.

    Impact. Trailing audio spoiled the quiet between visitors.

    Detection. Session cycling; the second cause surfaced a week later, in the install features pass.

    Root cause. Both assumed silence meant stopped: the watchdog rescued intentional silence, and stop() masked gain over voices that returned at volume reset.

    Fix. The watchdog honours an intentional silence flag; stop() purges the audio graph.

    Blameless note. Safety systems need "this silence is on purpose"; stops must make the state true, not inaudible.

  20. Release Self describing study sessions

    Each session records variant, model, sound mode and layer cap.

    Customer value. No reconstructing state from logs.

    • Condition capture in the eval app
    • Backend aware experimenter recap
    • Balanced, versioned audit prompt set
  21. Release Sound modes and a content aware reembed

    Repairs stems with a silent index window.

    Customer value. Wash to tight response; phrases reach content, not silence.

    • Retrieval and playback presets
    • Live preset toggle
    • Loudness sidecar precompute
    • Has audio flag
    • Clip gain
    • Best window
    • Compromised rows reembedded per backend
    • Append only epoch provenance

    Risks

    • Per clip gain not yet applied per sample
  22. Research · Experiment Content aware reembedding and index epoch provenance

    Repair an index whose fixed window hit silence; log each measurement's epoch.

    Insights

    • Silent openings embed as near nothing
    • Representative windows limit repair to compromised rows

    Published as process only. Results stay private while under review.

  23. Release Synthetic visitor soak of the install lifecycle

    Harness retired after one fix.

    Customer value. Reliability, proven on the exact gallery build.

    • Playwright synthetic visitors
    • Environment gated data isolation
    • Six hour health and memory telemetry
    • Negative radius bug fixed, reverified
    • Two low severity followups documented

    Risks

    • Live audio path excluded: the top gap
  24. Incident · Sev3 Resolved A negative radius draw call, found by the soak

    The soak's one real bug.

    Impact. One frame, under rapid cycling; no visitor saw it.

    Detection. Six logged across nearly 2,000 synthetic generations.

    Root cause. An elapsed time radius went briefly negative when two clocks disagreed by under a frame.

    Fix. A one line lower bound guard: six to zero on the next run.

    Followups

    • Document what it did NOT patch: memory creep, a rare dropped start (done)
    • Soak the audio and mic paths (open)

    Blameless note. One small bug in a clean soak is a success; memory stayed flat.

  25. Release Install mode: the unattended visitor lifecycle

    Armed to attract: the full kiosk lifecycle.

    Customer value. All day, no operator: oriented, a bounded turn, an automatic reset.

    • Staged states with explicit bounds
    • TTS narrated onboarding tour
    • Per visitor volume reset
    • Mic VU meter
    • Record gating
    • Idle attract board

    Risks

    • Slow multiday server memory creep
  26. Release Audio safety: limiter, clamps and a watchdog

    Three layers; a 1 Hz watchdog.

    Customer value. Speakers stay safe; the sound recovers itself.

    • Compressor into NaN proof ceiling
    • Voice ring out bounds
    • Effect and gain caps
    • Soft and deep watchdog rescue
    • Optional wild mode texture

    Risks

    • The soak harness skips the audio path
  27. Incident · Sev2 Resolved Audio runaway: louder, then silent

    Not feedback: unbounded voice accumulation.

    Impact. A loud buildup, then dead speakers until a reload.

    Detection. Manual testing, then deterministic replay.

    Root cause. Multiminute stems retriggered every cycle: about 870 source nodes by 18 minutes, and a render clock at a few percent of real time yet "running". The roar was hard clipping.

    Fix. NaN proof master limiter, ring out clamps, and a 1 Hz watchdog that rescues NaN, clock collapse or silence.

    Followups

    • Replay after the fix (done)
    • Optional wild mode keeps the texture (done)
    • Soak the audio path (open)

    Blameless note. A reasonable default met an unreasonable rate: bound resources under repetition.

  28. Release Exchangeable retrieval backends and an audit harness

    One interface, many models.

    Customer value. Stable for visitors while the ALM varies.

    • Model independent corpus DB
    • Offline audio embedding per configuration
    • Text only sidecar for heavy models
    • Runtime toggle, liveness probe, autorevert
    • Multilingual configuration
    • Offline distributional audit harness

    Risks

    • Research licensed model: offline audit only
    • Warm backends cost memory
  29. Research · Experiment Auditing ALMs as a controlled variable

    Hold the installation constant, vary only the ALM, and compare behaviour.

    Insights

    • One interface makes the model a toggle
    • Distributional metrics need no ground truth labels

    Published as process only. Results stay private while under review.

  30. Release Demo mode for operator run showcases

    With a closing recap.

    Customer value. Polished talks and visits; playback survives screen recording.

    • Branded overlay, logo reveal
    • Intro and outro flashes
    • Demo set picker
    • Section beds
    • Header mode and transition selectors
    • Operator controls
    • Presentation theme

    Risks

    • Manual flow; unattended lifecycle came later
  31. Release Visual engine alignment across the shared lineage

    Two days of porting the sibling engine.

    Customer value. One parameter contract, played by hand, audio or autoperformer.

    • Param model bridge, shared parameter vector
    • Layered target smoothing
    • Device adaptive monome twin
    • ~30 audio reactive families, 24 new
    • Off / Auto / Listen autoperformer
    • UMAP / PCA / t-SNE map
    • Sketch upload
    • Responsive canvas, mode theming
  32. Release One instrument design language across four apps

    Warm amber.

    Customer value. One instrument, not four developer tools; later modes build on it.

    • Tokens across all four surfaces
    • Survey theme matched
    • Submodule remote migration
    • Umbrella pin updates
  33. Incident · Sev3 Resolved Monome dead after another app used it

    serialosc kept the other app's prefix.

    Impact. Dead until a manual daemon restart.

    Detection. Switching to another monome app and back.

    Root cause. serialosc persists each device's prefix across app exits and daemon restarts, and the bridge's literal prefix matcher dropped every message.

    Fix. Suffix matching accepts any persisted prefix; a self healing regrab reasserts Windchime's.

    Blameless note. Shared hardware means shared daemon state; assume any prefix.

  34. Release Participant mode and onboarding flow

    The first build someone else could run.

    Customer value. Consent, orientation and mic recovery need no operator.

    • Dev / participant toggle
    • One click trials
    • Two stage consent, same origin
    • Stuck mic detection, recovery
    • Audio activity meter
    • Master volume
    • Transcript panel
    • Six prompt trials
  35. Incident · Sev2 Resolved Monome silent after an unplug

    A replug left it detached until a bridge restart.

    Impact. A bumped cable would drop it for the whole show.

    Detection. Hardware testing: no input or LEDs after reattaching.

    Root cause. The bridge armed device notifications once and deduplicated advertisements, so returning devices were never repointed.

    Fix. It rearms on every attach and detach, with a periodic repoll as backup: replugs reattach within seconds.

    Followups

    • Tolerate another app's prefix (done)
    • Recover audio input after USB reenumeration (done)

    Blameless note. First plugs always work; gallery hardware gets bumped.

  36. Incident · Sev2 Resolved Audio stayed suspended between trials

    Silence after the first stop.

    Impact. Blocked the first participant trials, with no error.

    Detection. Same day runs: transcript and visuals moved in a silent room.

    Root cause. Each stop suspended the AudioContext, resumed from an SSE handler without a user gesture; a cross origin unlock gap compounded it.

    Fix. The context now runs all session, the engine loads eagerly, and an unlock banner verifies the resume.

    Blameless note. Autoplay policy is part of the runtime contract; the durable fix needs no resume.

  37. Release Umbrella repo unifying four service branches

    The first umbrella.

    Customer value. One command runs it all on a laptop.

    • Four services as submodules
    • One launcher for all four
    • Dev UI shell
    • End to end smoke target
    • Control bus, livecode to animation

    Risks

    • Localhost only trust; needed an Origin allowlist
  38. Incident · Sev3 Resolved State changing endpoints accepted cross site requests

    Another open page could trigger generation.

    Impact. A malicious page could start the mic or spend model API budget; none observed.

    Detection. A security review during the umbrella unification.

    Root cause. Open cross origin settings block reading, not sending, a cross site POST; nothing checked Origin.

    Fix. Endpoints check Origin against the installation's own URLs; scripts and the mic runtime send none and pass.

    Followups

    • Sync the port list and Origin allowlist (open)
    • Token or authenticating proxy before nonloopback exposure (open)

    Blameless note. Proportionate for localhost, and documented for review.

  39. Release User study capture app, protocol to dashboard

    Built in one day.

    Customer value. Sessions capture themselves; no experimenter transcribes anything.

    • Protocol and consent as data
    • Standardized usability scale
    • Construct grouped instruments
    • Automatic session and event logs
    • Survey UI
    • Review dashboard
    • Same day sibling integration
    • Remote trial stop
  40. Research · Usability Instruments for a moderated proof of concept study

    Standardized UX questionnaire, construct grouped items, per trial ratings, semistructured interview; counterbalanced, distribution first.

    Insights

    • Validated and construct grouped items cover both facets
    • Per trial ratings catch what one form blurs

    Published as process only. Results stay private while under review.

  41. Incident · Sev3 Resolved Retrieval failed on every request after the first

    One database connection reused across threads.

    Impact. Bringup stalled after one query, blocking browser tests.

    Detection. The second generate raised a driver thread ownership error.

    Root cause. The connection enforces same thread use by default, and one engine was shared across worker threads.

    Fix. The same thread check is disabled, safe because the engine is read only after construction.

    Followups

    • Confirm the shared engine stays read only (done)
    • Keep retrieval in process (done)

    Blameless note. Match connection settings to the threading model.

11 · Decisions

Decision records

Accepted A bounded turn per visitor, not an endless loop

Context. Unattended, the piece must orient a stranger in seconds; free play never ends or resets.

Decision. A staged turn: explicit start, a set number of spoken prompts, a recap, then automatic reset.

Why. An arc visitors feel, a clean next start, a testable lifecycle.

Consequences. Install mode's spine and the soak's target: four states to harden (attract, onboarding, live, recap).

Accepted Make the ALM an exchangeable variable

Context. Which ALM matches best is a research question; a fair comparison needs an identical installation.

Decision. One embedding backend interface over a model independent corpus database; audio embedded offline per configuration; heavy models in a text only sidecar.

Why. Only a constant installation lets a difference be attributed to the model, not the plumbing.

Consequences. Six deployable ALM configurations and an offline audit harness; the corpus format stays model neutral.

Accepted An onscreen digital twin of the monome

Context. Work cannot depend on attached, correctly seated Grid and Arc hardware.

Decision. A virtual monome renders the byte identical LED frame sent to the device, and records or replays input traces.

Why. Development and regression without hardware, a live operator view, provable sync.

Consequences. Headless CI regression; the twin stands in for hardware and joined the onboarding tour.

Accepted A schema guard: the model never emits raw code

Context. Generated audio code near live speakers: one bad output plays noise or silence.

Decision. The model fills a validated schema from curated template families; a compiler renders it, so raw code never plays.

Why. Unattended, inspectable safety beats expressiveness: a schema is a contract.

Consequences. Bounded, reviewable output that enabled three tier failover, at the cost of hand authored template families.

Accepted Three tier failover for the code planner

Context. One hosted model is a single point of failure that could strand a visitor midturn.

Decision. Hosted LLM with prompt caching, then a local model in JSON mode, then a deterministic template; every plan validated before compiling.

Why. More local tiers still answer offline, and none can skip validation.

Consequences. It survives outages and logs each turn's tier; an operator selector exposes the chain.

Accepted Retrieval as an in process library, not a service

Context. Every utterance needs retrieval, via a service or an in process library.

Decision. Built once in process, shared read only across worker threads; only dependency conflicting heavy backends run in a minimal text only sidecar.

Why. No network hop or extra supervised process on the visitor hot path.

Consequences. Thread safety work surfaced a real bringup bug; normally one process runs, plus the active heavy backend's sidecar.