Files
konstruct/.planning/phases/02-agent-features/02-06-SUMMARY.md
Adolfo Delorenzo 43cf7d4e63 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)
2026-03-23 19:16:56 -06:00

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
escalation
whatsapp
outbound-routing
pipeline-wiring
system-prompt
tdd
requires provides affects
02-agent-features/02-02 (tasks.py pipeline rewrite)
02-agent-features/02-04 (escalation handler)
02-agent-features/02-05 (WhatsApp outbound + multimodal)
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
packages/orchestrator/orchestrator/tasks.py
packages/orchestrator/orchestrator/agents/builder.py
tests/unit/test_pipeline_wiring.py
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
created modified
tests/unit/test_pipeline_wiring.py
packages/orchestrator/orchestrator/tasks.py
packages/orchestrator/orchestrator/agents/builder.py
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
duration completed_date tasks_completed files_modified
9m 53s 2026-03-24 2 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)