Skip to content

Add WP_Agent_Channel base class for messaging transports#99

Merged
chubes4 merged 2 commits intomainfrom
add/agent-channel
May 7, 2026
Merged

Add WP_Agent_Channel base class for messaging transports#99
chubes4 merged 2 commits intomainfrom
add/agent-channel

Conversation

@lezama
Copy link
Copy Markdown
Contributor

@lezama lezama commented May 7, 2026

Summary

Introduces AgentsAPI\AI\Channels\WP_Agent_Channel, a reusable abstract base for transports that connect external messaging surfaces (Telegram, Slack, WhatsApp, Email, …) to a chat ability registered through the WordPress Abilities API.

The base class owns the agent loop end-to-end:

validate → extract_message → run_agent → deliver_result → lifecycle hooks → session persistence

Concrete channels only fill in the transport-specific I/O — extract a user message from the webhook payload, send a reply, lifecycle hooks for typing indicators / reactions / stats. The default run_agent() calls the chat ability under the slug returned by the wp_agent_channel_chat_ability filter (default openclawp/chat); override to plug in a different runtime.

Why this matters

Mirrors a pattern that's been validated in production at WordPress.com for Telegram and Slack agents. Lifting the portable surface into agents-api means consumers (openclaWP, Data Machine, Intelligence, future plugins) can ship new channels by extending one class instead of reinventing the agent dispatch + session continuity + loop-prevention plumbing each time.

The first public consumer is OpenclaWP_Wacli_Channel (WhatsApp via openclaw/wacli) in lezama/openclawp; see the companion PR there.

Surface

  • Channel identity: get_external_id_provider(), get_external_id(), get_client_name() — used to scope sessions across redeploys and for tracing.
  • I/O: extract_message(), send_response(), send_error(), optional send_notification().
  • Async dispatch: get_job_action() — returning a hook name makes receive() schedule a single async event; returning '' runs synchronously inside the receive call.
  • Lifecycle hooks: validate(), on_processing_start(), on_processing_end(), on_complete().
  • Loop-prevention seam: validate() returning a WP_Error with code WP_Agent_Channel::SILENT_SKIP_CODE drops the message silently — no send_error(), no agent invocation. Use for own-bot echoes, allowlist misses, and well-formed-but-uninteresting webhook events.
  • Session persistence: option-based by default, keyed on md5(provider:external_id). Subclasses can override session_storage_key() to scope per-user, per-blog, or to a custom store.

Filter

add_filter( 'wp_agent_channel_chat_ability', function ( $slug, $agent_slug, $channel_class ) {
    // Route specific agents to a custom chat ability.
    return 'my-plugin/chat';
}, 10, 3 );

Test plan

  • tests/channels-smoke.php — 19 pure-PHP assertions covering happy path, multi-turn session continuity, distinct external_id isolation, empty-message short-circuit, agent-error routed to send_error, filter override, async dispatch via wp_schedule_single_event, and silent_skip.
  • tests/bootstrap-smoke.php updated to include Channels in the agent-native source-directory list (300 / 300 assertions pass).
  • No regressions: full smoke suite passes (28 files, 0 failures).

Implementer notes

  • The default run_agent() calls wp_get_ability($slug)->execute(['agent' => …, 'message' => …, 'session_id' => …]). Subclasses can override for a different runtime — direct wp_ai_client_prompt(), an external HTTP service, or a host-specific factory.
  • deliver_result() understands two result shapes: { reply: string, session_id?: string } (single-turn) and { messages: [ { role, content } ] } (multi-message). Override extract_replies() for exotic shapes.
  • Permission gating for unauthenticated webhook callers is left to consumers — typical pattern is a short-lived add_filter on the chat ability's permission callback, scoped to the request.

🤖 Generated with Claude Code

Introduces `AgentsAPI\AI\Channels\WP_Agent_Channel`, a reusable abstract
base for transports that connect external messaging surfaces (Telegram,
Slack, WhatsApp, Email, …) to a chat ability.

The base class owns the agent loop:

  validate → extract_message → run_agent → deliver_result →
  lifecycle hooks → session persistence

Concrete channels only fill in the transport-specific I/O. Default
`run_agent()` calls the chat ability under the slug returned by the
`wp_agent_channel_chat_ability` filter (default `openclawp/chat`); a
sentinel error code `silent_skip` lets `validate()` drop a message
without firing `send_error()` (loop prevention, allowlist misses).

Test coverage: tests/channels-smoke.php — 19 assertions covering the
happy path, multi-turn session continuity, distinct external_ids,
empty-message short-circuit, agent-error → send_error routing, the
filter override seam, async dispatch via wp_schedule_single_event,
and silent_skip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…branch

Addresses Homeboy lint + PHPStan findings on PR #99:

- Replace `?:` short ternaries with explicit ternaries (PHPCS rule).
- `unset( $data )` in default `validate()` / `run_agent()` stubs so the
  unused-parameter warning is silenced for base-class no-ops that
  subclasses override to actually use $data.
- Remove the `is_array( $result )` defensive branch in `deliver_result()`.
  After the `is_wp_error()` early return, `$result` is narrowed to array
  per the docblock, so PHPStan flagged the check as always-true. The
  type system is the one enforcing the invariant; the runtime check was
  redundant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants