Skip to content

feat(manager): WhatsApp Cloud API channel - #149

Merged
Asaf-prog merged 14 commits into
codex/fix-140-cancelled-token-budgetfrom
codex/whatsapp-integration
Oct 22, 2026
Merged

Asaf-prog merged 14 commits into
codex/fix-140-cancelled-token-budgetfrom
codex/whatsapp-integration

Conversation

@Asaf-prog

Copy link
Copy Markdown
Collaborator

Adds an opt-in WhatsApp Cloud API channel for one organization-owned number, following the integration plan (WA-01 to WA-05).

Stacked on #148. The base is codex/fix-140-cancelled-token-budget, so this diff shows only the WhatsApp work. GitHub retargets it to main when #148 merges.

Commits

  1. test(manager): close SQL engines owned by repository tests. Fixes intermittent aiosqlite worker-thread exceptions in the full gate (task 0018).
  2. feat(manager): prepare conversation turns idempotently. An opt-in idempotency key claims a durable receipt with the user message, so a retried preparation recovers the same turn. Migration 0007, ADR 0006.
  3. feat(manager): add durable channel storage. Provider-independent inbox, sender bindings, execution claims, webhook receipts, ordered outbox and delivery events, worker ownership, and the channel identity port. Migrations 0008–0010.
  4. feat(manager): connect WhatsApp webhook, worker, and delivery. Authenticated webhook intake, a supervised turn worker with conservative recovery, provider delivery with text controls, and /health returning 503 when the worker has stopped. Adds the regex dependency (used by text rendering).
  5. docs. ADRs 0007–0010, tasks, and review records.

Rollout

Everything is off by default. Three flags turn it on in order, and settings validation rejects a flag without its prerequisites:

  • EXTRA_WHATSAPP_ENABLED: webhook intake.
  • EXTRA_WHATSAPP_WORKER_ENABLED: turn worker. Requires intake, an organization id, and an identity resolver plugin.
  • EXTRA_WHATSAPP_SENDER_ENABLED: outbound delivery. Requires the worker.

No real account or number has been activated yet. Customer Google/JWT account linking and pilot packaging remain planned.

Validation

  • make check passes on the final commit: 1423 tests, plus ruff and mypy.
  • Every intermediate commit was checked in a separate worktree for tests, ruff, and mypy: 1126, 1159, 1174, and 1423 tests pass.
  • Migrations 0007–0010 need to run on deploy.

🤖 Generated with Claude Code

Asaf-prog and others added 7 commits October 16, 2026 14:57
Repository-contract fixtures and SQL conversation-creation helpers created aiosqlite engines without disposing them, causing intermittent worker-thread exceptions. Each test now owns and disposes its engines.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An opt-in idempotency key claims a durable receipt together with the user message, so a retried preparation recovers the same turn instead of appending another. Adds saved-response recovery for channel workers and migration 0007.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Provider-independent inbox, sender bindings, execution claims, webhook receipts, ordered outbox and delivery events, worker ownership, and the channel identity port, with migrations 0008-0010.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Opt-in authenticated Cloud API intake, a supervised channel turn worker with conservative recovery, provider delivery with text controls, and health reporting for a stopped worker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Integration plan, ADRs 0007-0010, implementation tasks 0014-0017 and 0019-0020, review records, and index entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The worker logs one `channel status:` line per configured interval with queue
counts, the oldest unfinished and unsent ages, and unknown, failed, and blocked
reply counts. The read is bounded to five seconds so monitoring cannot stall
scheduling or a graceful drain. Every rejected webhook request logs a fixed
reason. No line contains message text, sender IDs, tokens, or provider IDs.

Adds EXTRA_WHATSAPP_STATUS_LOG_SECONDS (default 60).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The runbook covers preflight, deployment, staged enablement and pausing,
health and status logs, restart recovery, rollback, retention, and the
real-number smoke-test checklist. The example runs agent-manager on durable
storage with a tool-free assistant and an allowlist identity resolver; its port
is bound to loopback because the web conversation API is anonymous by default.

A fake-provider rehearsal proves that paused intake survives restarts, is
answered and sent exactly once, projects a delivered callback, and is not
resent after a further restart.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@Asaf-prog Asaf-prog left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. Summary

Reviewed webhook intake, identity, preparation, execution, delivery, controls, persistence and lifecycle. Synced the latest #148 correction and resolved tasks/README.md while preserving both task lists. One confirmed blocker remains.

2. Blocking issues

Sender-disabled composition bypasses suppression and controls; see the inline finding. In the documented worker-on/sender-off state, /stop becomes failed and a subsequent text invokes the engine and becomes answered. /new also cannot reset in this state.

3. Non-blocking issues

No additional confirmed finding in the inspected paths. Retention and operator reconciliation remain documented follow-ups.

4. Architecture concerns

Manager/provider boundaries and request-scoped identity are preserved; ADRs 0006-0010 document the added contracts. Control enforcement must run independently of outbound transport activation.

5. Security concerns

The reproduced gap permits processing after opt-out, including potential model/tool work. The reproduction used a fake engine and no provider sends. Outbound delivery still has its separate suppression guard.

6. Testing gaps

make check passed on this head: formatting, lint, mypy, 1,432 tests / 27 warnings, and generated-stub checks. A separate real-app TestClient + file-backed SQLite reproduction produced [('stop', 'failed'), ('text', 'answered')] and one engine invocation after /stop. Existing control tests instantiate ChannelControlService directly, missing its absence in sender-disabled composition. Add app-level coverage for suppression and reset with sender disabled, including transition back to live.

7. Suggested improvements

Keep suppression and state-changing controls active whenever the worker runs. Preserve startup without outbound credentials when sender is disabled.

8. Final recommendation

Request changes for the confirmed defect. This is a COMMENT review because the connected account is the PR author and cannot approve its own PR. main also requires independent approval. No main merge was performed.

organization_id=options.organization_id,
render=render_text,
)
controls = ChannelControlService(

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Keep suppression and controls active while sending is paused. ChannelControlService is constructed only inside extra_whatsapp_sender_enabled, so the supported worker-on/sender-off state passes controls=None to ChannelTurnProcessor. Reproduction through create_app/TestClient with file-backed SQLite: POST a signed /stop followed by a signed text; /stop becomes failed and the text becomes answered with one engine invocation. /new also fails to reset. Enforce suppression and state-changing controls whenever the worker runs, independently of transport credentials, and add composition-level regression coverage.

feat(manager): WhatsApp pilot packaging and operations (WA-06)
@Asaf-prog
Asaf-prog merged commit cb71fd1 into codex/fix-140-cancelled-token-budget Oct 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant