- 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)
7.9 KiB
phase, plan, subsystem, tags, dependency_graph, tech_stack, key_files, decisions, metrics
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | |||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 02-agent-features | 06 | orchestrator |
|
|
|
|
|
|
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_humanwere never called (orphaned)- All responses went to
_update_slack_placeholderdirectly — WhatsApp replies silently lost handle_messagedidn't pop WhatsApp extras beforemodel_validate(unknown field errors)
Solution:
-
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_idto module level for testability. -
WhatsApp extra extraction in
handle_message: Now popsphone_number_idandbot_token(WhatsApp access_token) beforemodel_validate. Extractswa_idfrommsg.sender.user_idafter validation. Builds unifiedextrasdict. -
_process_messagesignature change: Now acceptsextras: dict | None = Noneinstead ofplaceholder_tsandchannel_idseparately. -
_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: ...}
- Slack:
-
Escalation pre-check: Before the pending tool confirmation block, checks Redis
escalation_status_key. Ifb"escalated", returns early with assistant-mode reply — no LLM call. -
All response delivery via
_send_response(): Replaced all three_update_slack_placeholdercalls in_process_messagewith_send_response(msg.channel, text, response_extras)._update_slack_placeholderremains the implementation detail inside_send_response. -
Escalation post-check: After
run_agent()returns, callscheck_escalation_rules()with_build_conversation_metadata()output. If a rule matches ANDagent.escalation_assigneeis set, callsescalate_to_human()and replacesresponse_textwith the confirmation message. -
_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_messageWhatsApp extra extraction - 3 tests:
_send_responsechannel routing - 2 tests:
_process_messageuses_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_metadatabilling keyword detection - 5 tests:
build_system_promptWhatsApp scoping - 4 tests:
build_messages_with_memorychannel 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_messagemake it impossible to patch atorchestrator.tasks.*— patching requires module-level binding - Fix: Moved all key imports to module level; kept only
Agent/ChannelConnectionORM 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_idfrommsg.sender.user_idinside_process_message, butextrasdict was already built inhandle_messagebefore_process_messageis called — need to set wa_id in handle_message after model_validate - Fix:
handle_messageextractswa_id = msg.sender.user_idaftermodel_validatewhenchannel == "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)