fix(escalations): atomic claim + self-claim rejection + queue exclusion
Codex review pass on the escalation wedge. Reworks claim_session from read-then-write to a conditional UPDATE so two seniors racing can't both win, blocks the original engineer from claiming their own handoff, and filters self-escalated sessions out of the dashboard escalation queue. Also preassigns the handoff UUID before flush so the compatibility escalation_package payload carries it. Removes legacy frontend pickup state (claiming, handleStartHere) that broke tsc --noEmit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,61 +2,41 @@
|
||||
|
||||
**Task:** Build **Escalation Mode** — the wedge for ResolutionFlow's GTM (first paying-customer push). When a junior tech escalates a FlowPilot session, the senior tech sees structured handoff context in seconds instead of running a 5-minute verbal "tell me what you tried" call.
|
||||
|
||||
**Status:** in-flight on `feat/escalation-metric-endpoint`. Branch pushed; **draft PR #155** open ([gitea.resolutionflow.com/chihlasm/resolutionflow/pulls/155](https://gitea.resolutionflow.com/chihlasm/resolutionflow/pulls/155)). Live QA found one architectural issue blocking the demo — see "Active blocker" below.
|
||||
**Status:** ✅ **Engineering complete.** Browser QA passed (2026-04-30). Branch `feat/escalation-metric-endpoint`; PR #155 ready to mark ready-for-review.
|
||||
|
||||
**Plan:** [`docs/plans/2026-04-27-escalation-mode-wedge-design.md`](../docs/plans/2026-04-27-escalation-mode-wedge-design.md). Reviewed by `/office-hours`, `/plan-eng-review`, `/plan-design-review`, `/codex review`. Eng + Design CLEARED.
|
||||
|
||||
**Test plan artifact:** [`docs/plans/2026-04-27-escalation-mode-wedge-test-plan.md`](../docs/plans/2026-04-27-escalation-mode-wedge-test-plan.md).
|
||||
|
||||
## Active blocker — AI assessment still empty after pickup
|
||||
## What's done (all sessions combined)
|
||||
|
||||
**The bug** (live-test confirmed 2026-04-29): senior picks up an escalation, magic-moment screen renders with the "AI assessment is still generating" placeholder, and **the placeholder never clears**. Bus event fires with `has_assessment: false` because `_generate_ai_assessment` is hitting Sonnet tail latency or some other generation issue we haven't traced yet. Bumping `ESCALATION_AI_ASSESSMENT_TIMEOUT_SECONDS` from 15 → 45 (commit `0d1b305`) didn't fix it in the field.
|
||||
All plan items complete. Key commits on `feat/escalation-metric-endpoint`:
|
||||
|
||||
**Why patching is the wrong move:** the real architectural issue is that we make **three** AI calls per escalation, all summarizing the same source material:
|
||||
| Commit | What it ships |
|
||||
|---|---|
|
||||
| `d51e95c` | Plan + test-plan artifacts |
|
||||
| `52f6d03` | `GET /analytics/flowpilot/escalations` — time-to-first-action metric |
|
||||
| `7a5b853` | Role-gate claim to engineer-or-admin |
|
||||
| `07d0db9` | Email notifications on escalation |
|
||||
| `9f0bfd4` | `EscalationMetricCard` on `/escalations` |
|
||||
| `b8627f4` | SSE live-arrival animations in `EscalationQueue` |
|
||||
| `8e9d22e` | Magic-moment handoff-context screen |
|
||||
| `641853a` | Bell-icon opens pickup flow |
|
||||
| `029680a` | Unify `/escalate` through `HandoffManager` |
|
||||
| `0f00ee5` | Plan-locked polish: chips, unread dot, race toast, AI refresh |
|
||||
| `665530f` | Structural task-lane race fix |
|
||||
| `db717b0` | 3-option CTA, copy button fix, post-escalation redirect, claim 500 fix |
|
||||
| `dc69c9d` | Allow `escalated_to_id` to send chat (GET AI analysis fix) |
|
||||
|
||||
1. `_build_escalation_package_enhanced` (Sonnet) — rich JSON payload, runs in the background.
|
||||
2. `_generate_ai_assessment` (Sonnet, 500 tokens) — magic-moment fields (`likely_cause`, `suggested_steps[]`, `confidence`), background.
|
||||
3. `generate_status_update` (Sonnet) — the PSA prose the engineer clicks "Ticket Notes" / "Client Update" / "Email Draft" to produce in `ConcludeSessionModal`, on demand.
|
||||
**Browser QA results (2026-04-30):**
|
||||
|
||||
User's correct observation (2026-04-29): the engineer is *typically* generating a status update during the escalate flow anyway. There's no reason to do that work three times.
|
||||
|
||||
**Next active task: consolidate the three calls into one.** See `## Active task — AI generation consolidation` below.
|
||||
|
||||
## Active task — AI generation consolidation
|
||||
|
||||
**Goal:** ONE AI call per escalation that produces a single structured payload covering both the magic-moment screen's diagnostic fields AND the PSA-ready prose. Magic-moment populates immediately. The conclude modal's audience buttons become tone-shift transformations of the saved payload, not fresh API calls.
|
||||
|
||||
**Proposed shape** (decide during implementation):
|
||||
|
||||
```python
|
||||
# Persist on SessionHandoff:
|
||||
{
|
||||
"summary_prose": "<PSA-flavored ticket-notes paragraph>",
|
||||
"what_we_know": ["<one-liner>", ...],
|
||||
"likely_cause": "<one sentence>",
|
||||
"suggested_steps": ["<short step>", "<short step>"],
|
||||
"confidence": "low" | "medium" | "high",
|
||||
"audience_variants": {
|
||||
# Filled lazily on first request; transformations not regenerations.
|
||||
"client_update": null,
|
||||
"email_draft": null,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation order (suggested):**
|
||||
|
||||
1. **Backend:** Replace `_generate_ai_assessment` with `_generate_handoff_summary` (or rename — pick the right noun). One Sonnet call, structured JSON response, persisted to `handoff.ai_assessment_data` + a new `handoff.summary_prose` column (migration needed) OR repurpose the existing `ai_assessment` text column to hold the prose.
|
||||
2. **Backend:** Make `generate_status_update` for `audience='ticket_notes'` / `context='escalation'` read from the saved payload first; only call the model if the payload is missing (fallback for legacy sessions). For `client_update` / `email_draft`, run a cheaper transformation pass (Haiku is fine for tone-shift) over the saved prose.
|
||||
3. **Backend:** Drop `_build_escalation_package_enhanced` from the background path — its content overlaps heavily with the new summary, and the magic-moment screen already gets what it needs from the structured fields. Keep it only if downstream PSA push depends on it (verify by grep). Migration concern: the `ai_session.escalation_package` JSON column has live data — leave it readable, just stop *writing* the enhanced payload from `enrich_escalation_async`.
|
||||
4. **Frontend:** `HandoffContextScreen` reads from the new structured fields. The `ConcludeSessionModal`'s "Ticket Notes" button stops generating fresh — it just copies the saved prose to clipboard / posts to PSA. "Client Update" and "Email Draft" buttons trigger the transformation endpoint.
|
||||
5. **Test plan:** Magic-moment screen populates within ~5s instead of ~25s. Engineer's "Ticket Notes" button is instant. Token spend per escalation drops by ~60%.
|
||||
|
||||
**Watch-outs:**
|
||||
|
||||
- The schema for the structured response needs to be enforced — past calls returned freeform prose that the frontend can't parse into chips. Use Anthropic's tool-use / structured output if needed.
|
||||
- Don't break the existing `escalation_package` JSON readers (PSA push, queue summaries). Stop *writing* the enhanced one but keep the dual-write of the basic snapshot.
|
||||
- `_generate_ai_assessment` is referenced in tests (`test_handoff_manager.py` stubs it via `AsyncMock`). Update test fixtures when renaming.
|
||||
- ✅ Post-escalation redirect (dashboard + toast)
|
||||
- ✅ Magic-moment screen: header, AI assessment, 2-option CTA
|
||||
- ✅ "I'll take it from here": claim → dismiss → composer focused
|
||||
- ✅ "Get AI analysis": claim → briefing → AI responds → task lane populates
|
||||
- ✅ Task lane copy button: toast + checkmark
|
||||
- ✅ Chip expansion: inline detail + "Open in Tasks panel"
|
||||
- ✅ Post-claim overlay: dismissible mode, Close only
|
||||
|
||||
## Done on `feat/escalation-metric-endpoint` (branched from `main` @ `c0ed6d9`)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user