Speed up the Welcome kickoff and show the team arriving (#2066)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
morgmart
2026-07-18 09:13:22 -07:00
committed by GitHub
parent 1bdd938491
commit 1fdb73e17b
17 changed files with 1069 additions and 90 deletions

View File

@@ -0,0 +1,106 @@
# Welcome Kickoff — Silent Failure Paths
Status: **open** — the *perception* gap is handled (see below); the silent
paths themselves are not. Documented for follow-up work.
Context: the Welcome-channel kickoff choreography
(`desktop/src/features/onboarding/welcomeKickoff.ts`) where Fizz posts an
opener, teammates introduce themselves in-thread, and Fizz posts a closer.
## The problem
Every fallback message in the kickoff assumes Fizz — the lead agent and
sender — is alive and able to post. When Fizz itself fails, or an early step
throws, **nothing is ever posted** and the user stares at an empty Welcome
channel with no explanation.
The client-side kickoff stage (the starter-team characters standing on the
Welcome composer banner) covers the *perception* gap, and that part has landed:
after `WELCOME_KICKOFF_STAGE_TIMEOUT_MS` (90s) with no message, the characters
play their exit and the banner drops back to its normal mention hint. A failed
kickoff degrades to an ordinary, usable empty channel rather than claiming a
team is still being set up.
What it does **not** do is explain anything — the silent paths below still need
real handling. Two things worth knowing before picking this up:
- The stage is driven purely by "is the timeline empty" plus that timer
(`useWelcomeKickoffStage.ts`). It never reads the real kickoff state, so it
cannot distinguish "Fizz crashed" from "the relay is slow" — the timeout is a
perception backstop, not a diagnosis. Surfacing a cause means plumbing one out
of `useWelcomeKickoff` (step 1 of the Sketch below).
- The empty channel it degrades to invites the user to `@`-mention Fizz — who,
in exactly these failure cases, is the thing that isn't working. So the quiet
timeout is honest but still a dead end.
## Message inventory (what the user CAN receive today)
All hard-coded client-side; only teammate intro replies are LLM-generated.
| # | Message | Trigger | Sender |
|---|---------|---------|--------|
| 1 | Provider fallback ("connect to an AI provider in Settings…") | Readiness check fails before kickoff | Fizz (marker: `provider-required.v1`) |
| 2 | Happy-path opener (mentions teammates, asks them to introduce themselves) | Team online | Fizz (marker: `opener.v1`) |
| 3 | Degraded opener ("I'm here with Honey and Bumble…") | Fizz online, zero teammates online within 60s | Fizz (opener + closer markers, self-contained) |
| 4 | Closer variants (clean / failed / slow teammate wording) | 3s beat after intros resolve, or 15s intro timeout | Fizz (marker: `closer.v1`) |
| 5 | Setup-mode nudge ("here's what you still need to configure") | Agent process spawns but its requirements check fails (e.g. missing API key) | The agent process itself (backend, buzz-acp setup-listener mode) |
## Silent paths (what the user CANNOT be told today)
1. **Fizz fails to start.** `startManagedAgent` for the lead rejects (harness
binary missing, spawn error). The kickoff effect logs
`Failed to start Welcome agent…` and returns — by design, the opener is
only sent by Fizz, so nobody speaks.
2. **Any step throws.** The entire kickoff runs in one `try/catch` that logs
`Failed to start the Welcome team kickoff.` and gives up. Causes seen in
practice:
- relay unreachable / websocket down
- `ensureWelcomeTeam` failure (team record creation)
- the send itself rejected — e.g. relay rate-limiting
("rate-limited: quota exceeded", observed 2026-07-17 with an agent
publishing in a tight retry loop)
3. **Closer-path failures.** The closer send failing is also caught-and-logged
only; the thread ends without the CTA. Lower stakes than 1–2 (an opener and
intros already happened) but still a dangling state.
Navigation away mid-kickoff also cancels silently, but that is intentional
(the kickoff resumes on next visit) — not a failure.
## Constraints for the fix
- **Fizz cannot be the messenger** for these paths: she is the thing that
failed. Any user-visible fallback must come from the client UI itself
(banner, intro-block state, or the kickoff stage's `timed-out` phase) — not a
channel message impersonating an agent.
- A relay-side or system-authored message is possible in principle
(kind-scoped system event) but heavier; the client already knows locally
that the kickoff threw, so a local UI state is the cheap, honest option.
- Whatever surfaces must be **idempotent across revisits** — same rule as the
opener markers: don't re-alarm the user every time they click Welcome.
- Distinguish *retryable* (relay hiccup, rate-limit) from *actionable*
(harness missing → point at Agents/Settings). The `Requirement` machinery
in `desktop/src-tauri/src/managed_agents/readiness.rs` already classifies
the actionable ones.
## Sketch (to validate later)
1. Surface a `kickoffError` phase from `useWelcomeKickoff` when the catch
block fires or the lead's start rejects, with a coarse cause
(`lead-start-failed` | `relay` | `unknown`).
2. The kickoff stage's `timed-out` phase renders that cause: quiet copy + a
pointer to Agents (for start failures) or a retry affordance (for relay
failures). Retry = re-run the effect (the coordinator already dedupes).
Note the phase currently exits immediately on timeout — giving it copy to
show means holding it on screen instead, and the stage is `aria-hidden`
decoration today, so anything it says needs to reach screen readers too.
3. Consider a bounded auto-retry (once, after a short delay) for the relay
class before showing anything.
4. Closer-path failure: on send rejection, retry once; otherwise leave the
thread as-is (intros already delivered the core experience).
## Related
- Rate-limiting incident: one Welcome agent produced a 42KB log of
"rate-limited: quota exceeded" retries within seconds
(2026-07-17, remote relay `onboarding.communities.buzz.xyz`). Worth a
separate look at buzz-acp publish backoff — a tight retry loop against a
quota makes every other send in the session fail too.