Skip to content
New site — Virtual Media Folders now has its own home. Visit vmf.soderlind.no →

Jev Comment Triage

Auto-moderate WordPress comments with TypeSafe’s Jev model, via the AI Provider for Jev plugin. Each comment is scored for spam, scam/phishing, and toxicity, then routed — approved, flagged as spam, or held for review.

The Jev call runs asynchronously, off the comment-submit request, so commenting stays fast even though moderation calls a remote API.

  • WordPress 6.8+, PHP 8.3+
  • The AI Provider for Jev plugin, active and configured with an API key (declared as a dependency via Requires Plugins).
  1. On submit (pre_comment_approved) — untrusted comments are held as pending instantly, with no API call, while WordPress’s own would-be decision is remembered. Trusted users (moderate_comments), pingbacks and trackbacks, an unconfigured provider, and comments WordPress already caught with the blocklist (spam/trash) keep WordPress’s decision.
  2. On store (comment_post) — the comment is tagged with a pending marker, a per-minute drain event is ensured, and a throttled immediate nudge (spawn_cron) is fired.
  3. Background drain (jct_drain, every minute) — a bounded batch of pending, un-triaged comments is assessed in one Jev call each and routed, never publishing past the site’s own moderation policy:
    • spam/scam ≥ threshold → spam
    • borderline or toxic → held for review
    • clean → WordPress’s remembered decision (approved only if the site would have approved it anyway; otherwise held)
  4. Self-healing — on an API error a comment keeps its pending marker and is retried on the next tick; after 3 attempts it is left held for a human. Comments are never publicly visible before triage, and a lock prevents concurrent drains from double-processing.

Scores are stored as the _jev_triage comment meta and shown in a Jev column on the admin Comments screen.

The combined spam signal is max(is_spam, is_scam). A comment is marked spam when that exceeds the spam threshold, when is_scam alone exceeds the scam threshold, or when it is link-heavy (≥ comment_max_links) with any real spam signal. Borderline spam or toxic content is held rather than deleted, keeping false positives out of the spam bucket.

HookTypePurpose
jct_thresholdsfilterTune decision thresholds (spam 0.75, scam 0.65, hold 0.45, toxicity_hold 1.5, link_assist 0.50).
jct_batch_sizefilterComments processed per drain tick (default 20).
jct_include_author_detailsfilterWhether to send the author name, URL, and email to the AI service (default true). Return false for stricter privacy.
jct_triagedactionFires after a decision: do_action( 'jct_triaged', $comment_id, $assessment, $decision ).
// Be stricter, process more per tick, and stop sending author PII.
add_filter( 'jct_thresholds', fn( $t ) => [ ...$t, 'spam' => 0.65 ] );
add_filter( 'jct_batch_size', fn() => 50 );
add_filter( 'jct_include_author_details', '__return_false' );

Comment text (and, unless disabled via jct_include_author_details, the author name, email, and URL) is sent to the TypeSafe (Jev) service for assessment. The plugin registers a suggested-privacy-policy snippet under Settings → Privacy.

Against a hand-labelled set of tricky cases (polite affiliate spam, phishing, crypto, dating bait, a link-farm, plus genuine comments including one with a legitimate link), the tuned logic scored 10/10 with no false positives — the genuine comment carrying a link scored 0.34 spam and was correctly passed.

100 comments on a Local multisite subsite, jev-latest, sequential.

On-request insert latency (what the commenter waits for):

minmeanp95max
1.28 ms2.38 ms3.94 ms9.06 ms

Background drain (off the request path):

processedpasseswall timethroughputmean/comment
100170.7 s1.41 comments/s707 ms

Async vs. the original synchronous version

Section titled “Async vs. the original synchronous version”
MetricSyncAsync (this version)Change
Commenter-facing latency (mean)1237 ms2.4 ms~520× faster
Commenter-facing p951608 ms3.9 ms~410× faster
Where the Jev call runson submit (blocking)background drain
Throughput0.77/s1.41/s+83%

Moving the Jev call off the request cut comment submission from ~1.2 s to ~2 ms (~500×). The drain is also faster per comment than the old sync path (707 ms vs 1237 ms) because it does only the API call and status update, not the full comment-insert pipeline — so the entire 100-comment backlog cleared in a single drain pass.

WP-Cron note: the drain relies on WP-Cron. On low-traffic sites, add a real system cron calling wp-cron.php (and set DISABLE_WP_CRON) so the backlog is processed promptly.

GPL-2.0-or-later