docs(02-06): complete escalation and WhatsApp routing re-wire plan summary
- Created 02-06-SUMMARY.md documenting escalation wiring, WhatsApp outbound routing, and tier-2 scoping - Updated STATE.md: advanced progress to 100%, recorded metrics and decisions - Updated ROADMAP.md: Phase 2 marked Complete (6/6 plans)
This commit is contained in:
146
.planning/phases/02-agent-features/02-06-SUMMARY.md
Normal file
146
.planning/phases/02-agent-features/02-06-SUMMARY.md
Normal file
@@ -0,0 +1,146 @@
|
||||
---
|
||||
phase: 02-agent-features
|
||||
plan: "06"
|
||||
subsystem: orchestrator
|
||||
tags: [escalation, whatsapp, outbound-routing, pipeline-wiring, system-prompt, tdd]
|
||||
dependency_graph:
|
||||
requires:
|
||||
- 02-agent-features/02-02 (tasks.py pipeline rewrite)
|
||||
- 02-agent-features/02-04 (escalation handler)
|
||||
- 02-agent-features/02-05 (WhatsApp outbound + multimodal)
|
||||
provides:
|
||||
- Escalation handler wired into _process_message pre/post-check
|
||||
- WhatsApp outbound routing wired end-to-end (handle_message -> _send_response)
|
||||
- Tier-2 WhatsApp business-function scoping in system prompt builder
|
||||
affects:
|
||||
- packages/orchestrator/orchestrator/tasks.py
|
||||
- packages/orchestrator/orchestrator/agents/builder.py
|
||||
- tests/unit/test_pipeline_wiring.py
|
||||
tech_stack:
|
||||
added: []
|
||||
patterns:
|
||||
- Module-level imports in tasks.py for testability (patchable at orchestrator.tasks.*)
|
||||
- _build_response_extras() helper for channel-aware extras assembly
|
||||
- _build_conversation_metadata() helper for billing keyword v1 detection
|
||||
- extras dict pattern: unified channel metadata dict passed through pipeline
|
||||
key_files:
|
||||
created:
|
||||
- tests/unit/test_pipeline_wiring.py
|
||||
modified:
|
||||
- packages/orchestrator/orchestrator/tasks.py
|
||||
- packages/orchestrator/orchestrator/agents/builder.py
|
||||
decisions:
|
||||
- "Module-level imports in tasks.py: moved key imports to module level so unit tests can patch at orchestrator.tasks.* rather than source modules"
|
||||
- "extras dict unified: Slack (placeholder_ts, channel_id, bot_token) and WhatsApp (phone_number_id, bot_token, wa_id) both carried in one dict through the pipeline"
|
||||
- "_build_response_extras(): Slack path injects DB-loaded slack_bot_token; WhatsApp path uses gateway-injected bot_token (no DB lookup needed)"
|
||||
- "Escalation pre-check before pending_tool_confirm check: already-escalated convos skip ALL processing including tool confirmations"
|
||||
- "wa_id extracted in handle_message from msg.sender.user_id after model_validate, injected into extras dict"
|
||||
metrics:
|
||||
duration: "9m 53s"
|
||||
completed_date: "2026-03-24"
|
||||
tasks_completed: 2
|
||||
files_modified: 3
|
||||
---
|
||||
|
||||
# Phase 2 Plan 06: Escalation and WhatsApp Routing Re-Wire Summary
|
||||
|
||||
Re-wired orphaned escalation handler and broken WhatsApp outbound routing into the orchestrator pipeline; added tier-2 WhatsApp business-function scoping to the system prompt builder.
|
||||
|
||||
## What Was Built
|
||||
|
||||
### Task 1: Escalation + Outbound Routing Re-wire in tasks.py
|
||||
|
||||
**Problem:** Plans 02-02 and 02-05 rewrote tasks.py but dropped two integrations:
|
||||
- `check_escalation_rules` / `escalate_to_human` were never called (orphaned)
|
||||
- All responses went to `_update_slack_placeholder` directly — WhatsApp replies silently lost
|
||||
- `handle_message` didn't pop WhatsApp extras before `model_validate` (unknown field errors)
|
||||
|
||||
**Solution:**
|
||||
|
||||
1. **Module-level imports:** Moved `aioredis`, `check_escalation_rules`, `escalate_to_human`, `build_messages_with_memory`, `run_agent`, `AuditLogger`, `embed_text`, `retrieve_relevant`, `append_message`, `get_recent_messages`, `get_tools_for_agent`, `settings`, `async_session_factory`, `engine`, `escalation_status_key`, `configure_rls_hook`, `current_tenant_id` to module level for testability.
|
||||
|
||||
2. **WhatsApp extra extraction in `handle_message`:** Now pops `phone_number_id` and `bot_token` (WhatsApp access_token) before `model_validate`. Extracts `wa_id` from `msg.sender.user_id` after validation. Builds unified `extras` dict.
|
||||
|
||||
3. **`_process_message` signature change:** Now accepts `extras: dict | None = None` instead of `placeholder_ts` and `channel_id` separately.
|
||||
|
||||
4. **`_build_response_extras()` helper:** Assembles channel-specific response extras:
|
||||
- Slack: `{bot_token: db_token, channel_id: ..., placeholder_ts: ...}`
|
||||
- WhatsApp: `{phone_number_id: ..., bot_token: gateway_token, wa_id: ...}`
|
||||
|
||||
5. **Escalation pre-check:** Before the pending tool confirmation block, checks Redis `escalation_status_key`. If `b"escalated"`, returns early with assistant-mode reply — no LLM call.
|
||||
|
||||
6. **All response delivery via `_send_response()`:** Replaced all three `_update_slack_placeholder` calls in `_process_message` with `_send_response(msg.channel, text, response_extras)`. `_update_slack_placeholder` remains the implementation detail inside `_send_response`.
|
||||
|
||||
7. **Escalation post-check:** After `run_agent()` returns, calls `check_escalation_rules()` with `_build_conversation_metadata()` output. If a rule matches AND `agent.escalation_assignee` is set, calls `escalate_to_human()` and replaces `response_text` with the confirmation message.
|
||||
|
||||
8. **`_build_conversation_metadata()` helper:** Scans recent messages + current text for billing keywords (`billing`, `invoice`, `charge`, `refund`, `payment`, `subscription`). Returns `{billing_dispute: bool, attempts: int}`.
|
||||
|
||||
### Task 2: Tier-2 WhatsApp Scoping in builder.py
|
||||
|
||||
Added `channel: str = ""` parameter to `build_system_prompt()`, `build_messages_with_memory()`, and `build_messages_with_media()`.
|
||||
|
||||
When `channel == "whatsapp"` and `agent.tool_assignments` is non-empty, `build_system_prompt()` appends:
|
||||
|
||||
```
|
||||
You are responding on WhatsApp. You only handle: {topics}.
|
||||
If the user asks about something outside these topics,
|
||||
politely redirect them to the allowed topics.
|
||||
```
|
||||
|
||||
`_process_message` now passes `str(msg.channel)` to `build_messages_with_memory()`.
|
||||
|
||||
## Test Results
|
||||
|
||||
26 new tests in `tests/unit/test_pipeline_wiring.py` — all passing:
|
||||
- 4 tests: `handle_message` WhatsApp extra extraction
|
||||
- 3 tests: `_send_response` channel routing
|
||||
- 2 tests: `_process_message` uses `_send_response` (not `_update_slack_placeholder`)
|
||||
- 1 test: escalation pre-check skips LLM
|
||||
- 3 tests: escalation post-check (rule match, assignee set/unset)
|
||||
- 4 tests: `_build_conversation_metadata` billing keyword detection
|
||||
- 5 tests: `build_system_prompt` WhatsApp scoping
|
||||
- 4 tests: `build_messages_with_memory` channel parameter
|
||||
|
||||
Existing tests: 66 passing (no regressions):
|
||||
- `test_escalation.py` (26 tests)
|
||||
- `test_whatsapp_scoping.py` (14 tests)
|
||||
- `test_whatsapp_normalize.py` (17 tests)
|
||||
- `test_whatsapp_verify.py` (9 tests)
|
||||
|
||||
## Verification
|
||||
|
||||
```
|
||||
check_escalation_rules wired at: tasks.py:71 (import), :504 (call)
|
||||
escalate_to_human wired at: tasks.py:71 (import), :514 (call)
|
||||
_send_response called at: tasks.py:355, :395, :438, :556
|
||||
_update_slack_placeholder: only inside _send_response (not directly in _process_message)
|
||||
"You only handle": builder.py:187
|
||||
```
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Auto-fixed Issues
|
||||
|
||||
**1. [Rule 2 - Architecture] Module-level imports for testability**
|
||||
- **Found during:** Task 1 GREEN phase
|
||||
- **Issue:** plan specified local imports matching existing pattern, but local imports in `_process_message` make it impossible to patch at `orchestrator.tasks.*` — patching requires module-level binding
|
||||
- **Fix:** Moved all key imports to module level; kept only `Agent`/`ChannelConnection` ORM models as local imports (to avoid circular import risks at module load time)
|
||||
- **Files modified:** `packages/orchestrator/orchestrator/tasks.py`
|
||||
- **Commit:** bd217a4
|
||||
|
||||
**2. [Rule 1 - Bug] wa_id set in extras dict from handle_message**
|
||||
- **Found during:** Task 1 test verification
|
||||
- **Issue:** Plan specified extracting `wa_id` from `msg.sender.user_id` inside `_process_message`, but `extras` dict was already built in `handle_message` before `_process_message` is called — need to set wa_id in handle_message after model_validate
|
||||
- **Fix:** `handle_message` extracts `wa_id = msg.sender.user_id` after `model_validate` when `channel == "whatsapp"` and injects into extras dict
|
||||
- **Files modified:** `packages/orchestrator/orchestrator/tasks.py`
|
||||
- **Commit:** bd217a4
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- FOUND: packages/orchestrator/orchestrator/tasks.py
|
||||
- FOUND: packages/orchestrator/orchestrator/agents/builder.py
|
||||
- FOUND: tests/unit/test_pipeline_wiring.py
|
||||
- FOUND: .planning/phases/02-agent-features/02-06-SUMMARY.md
|
||||
- FOUND commit: bd217a4 (feat implementation)
|
||||
- FOUND commit: 77c9cfc (test RED phase)
|
||||
- 92 tests pass (26 new + 66 existing regressions)
|
||||
Reference in New Issue
Block a user