register() (capabilities, * endpoints, auth, agent cards, MCP tools), see examples/integrate-your-plugin.php. * * Tiers * [STABLE] Public integration API, frozen at WP_Discovery spec 1.0. Build on these. * [EXTENSION] Supported output-shaping filters. Useful for deeper integrations; * signatures may evolve between releases — test against the version you target. * [INTERNAL] Advanced site-owner / internal-tuning knobs. Not a third-party * integration surface; listed here for completeness. * * @package Agentimus\Examples */ defined( 'ABSPATH' ) || exit; /* ===================================================================== * [STABLE] Registration & core extension points * ===================================================================== */ /** * Declare your plugin's resources, and optionally serve your own /.well-known * documents. The single integration every provider needs. */ add_action( 'wpdiscovery_register', function ( $registry ) { // See examples/integrate-your-plugin.php for the full resource schema. $registry->register( array( 'id' => 'acme', 'title' => 'Acme', 'type' => 'commerce', ) ); // Serve a document at /.well-known/ (callback | redirect | file). $registry->add_well_known( array( 'name' => 'acme.json', 'callback' => function () { return wp_json_encode( array( 'ok' => true ) ); }, ) ); } ); // `agentimus_register` is a product-aliased copy of the action above — hook either, not both. /** * Add selectable schema.org entity types to Settings → Identity. */ add_filter( 'agentimus_entity_types', function ( $types ) { $types[] = 'Restaurant'; return $types; } ); /** * Run after Agentimus regenerates its documents — e.g. purge your CDN / page cache. */ add_action( 'agentimus_cache_flushed', function () { // my_cdn_purge( array( '/llms.txt', '/llms-full.txt', '/.well-known/discovery.json' ) ); } ); /** * Run after the plugin finishes booting — a companion or Pro add-on registers its * own features here against the shared plugin instance. * * @param \Agentimus\Plugin $plugin The booted plugin instance. */ add_action( 'agentimus_booted', function ( $plugin ) { // my_addon_boot( $plugin ); } ); /* ===================================================================== * [EXTENSION] Discovery document & .well-known * ===================================================================== */ /** * The $schema URL of the discovery document. Return '' to omit it entirely. */ add_filter( 'agentimus_schema_url', fn( $url ) => $url ); /** * Kinds of resource this site knows, beyond the built-in vocabulary (content, * commerce, scheduling, courses, forms, crm, auth, search, media, messaging, * analytics, payments, directory, agent). * * Declare a kind here and it is published exactly as you wrote it. A word nobody * declared is MARKED rather than refused — "community" is published as * "x-community", with a warning on the Discovery Hub naming the change — so no * plugin ever loses its entry over one word. * * The filter ADDS: it is handed an empty array, and a built-in kind cannot be * removed, so no plugin can change what another plugin publishes. * * @param string[] $types Additional kinds. */ add_filter( 'agentimus_resource_types', function ( $types ) { $types[] = 'loyalty'; return $types; } ); /** * The whole assembled discovery.json envelope. Add x- keys for vendor * extensions — the unprefixed namespace is reserved for the spec. * * @param array $envelope The discovery envelope. * @param \Agentimus\Discovery\Registry $registry The collector. */ add_filter( 'agentimus_envelope', function ( $envelope, $registry ) { $envelope['x-acme'] = array( 'portal' => 'https://acme.example' ); return $envelope; }, 10, 2 ); /** * The `documents` map — add a standard document Agentimus can't auto-detect. * * @param array $docs name => URL. * @param \Agentimus\Discovery\Registry $registry The collector. */ add_filter( 'agentimus_documents', function ( $docs, $registry ) { $docs['acme_openapi'] = home_url( '/wp-json/acme/v1/openapi.json' ); return $docs; }, 10, 2 ); /** * Label a /.well-known name with the standard that governs it. */ add_filter( 'agentimus_well_known_specs', function ( $specs ) { $specs['acme.json'] = 'Acme Manifest'; return $specs; } ); /** * Route your own flat /.well-known name so it resolves on every host. */ add_filter( 'agentimus_well_known_routed', function ( $names ) { $names[] = 'acme.json'; return $names; } ); /** * Route an exact-match nested /.well-known// name. */ add_filter( 'agentimus_well_known_nested', function ( $names ) { $names[] = 'acme/card.json'; return $names; } ); /** * Add a surface your companion signer signs (Web Bot Auth / HTTP Message Signatures). */ add_filter( 'agentimus_signed_surfaces', function ( $names ) { $names[] = 'acme.json'; return $names; } ); /* ===================================================================== * [EXTENSION] MCP & agents * ===================================================================== */ /** * Annotate the advertised MCP descriptor served at /.well-known/mcp.json. * * @param array $mcp The MCP descriptor. * @param array[] $resources Collected resources. */ add_filter( 'agentimus_mcp', function ( $mcp, $resources ) { $mcp['x-acme'] = true; return $mcp; }, 10, 2 ); /** * Pin which server the MCP server card describes. Return '' to auto-pick the * server with the most tools. * * @param string $id Server id to pin ('' = auto). * @param array[] $servers All detected servers. */ add_filter( 'agentimus_mcp_card_server', fn( $id, $servers ) => $id, 10, 2 ); /** * Append entries to the Agent Skills index at /.well-known/agent-skills/index.json. * * @param array[] $skills Skill entries. * @param array[] $resources Collected resources. */ add_filter( 'agentimus_agent_skills', function ( $skills, $resources ) { $skills[] = array( 'id' => 'acme_do', 'name' => 'Do thing', 'description' => 'Perform the Acme action.', 'resource' => 'acme', ); return $skills; }, 10, 2 ); /* ===================================================================== * [EXTENSION] Content, llms.txt & markdown * ===================================================================== */ /** * Which post types are agent-visible — each gets its own section in llms.txt. * * @param string[] $types Selected post types. * @param string[] $available All public post types. */ add_filter( 'agentimus_post_types', function ( $types, $available ) { if ( in_array( 'acme_product', $available, true ) ) { $types[] = 'acme_product'; } return $types; }, 10, 2 ); /** * Attribute a post type's llms.txt section to your plugin. * * @param string $source Vendor label ('' = none). * @param string $post_type Post type slug. */ add_filter( 'agentimus_post_type_source', fn( $source, $post_type ) => 'acme_product' === $post_type ? 'Acme' : $source, 10, 2 ); /** * Supply rendered HTML for a post (e.g. page-builder content). Return null to * let Agentimus render it the normal way. * * @param string|null $html Pre-rendered HTML, or null. * @param \WP_Post $post The post. */ add_filter( 'agentimus_markdown_source', fn( $html, $post ) => $html, 10, 2 ); /** * Topic/category slugs to omit from the llms.txt Topics list AND from a post's * auto-derived "Topics for AI". The default already excludes 'uncategorized'. */ add_filter( 'agentimus_topic_exclude', function ( $slugs ) { $slugs[] = 'changelog'; return $slugs; } ); /** * Which taxonomies feed a post's auto-derived "Topics for AI". Core reads only the * WordPress built-ins (category, post_tag); declare your own so its terms are * offered as topics wherever your content type is agent-visible. The terms flow * through the SAME path as the core ones — the editor's derive toggle, the exclude * list above, de-duplication and the per-item cap — so vendors give their data * WITHOUT any vendor-specific code living in Agentimus. * * Example: a WooCommerce store surfacing product categories, tags and a brand * attribute as topics on its products. * * @param string[] $taxonomies Taxonomy slugs. * @param \WP_Post $post The post being described. */ add_filter( 'agentimus_derive_taxonomies', function ( $taxonomies, $post ) { if ( 'product' === $post->post_type ) { $taxonomies[] = 'product_cat'; $taxonomies[] = 'product_tag'; $taxonomies[] = 'pa_brand'; // a global product attribute } return $taxonomies; }, 10, 2 ); /** * Last word on a post's "Topics for AI": add or refine the final list directly. * Use this when your topics are NOT taxonomy terms (e.g. computed from post meta). * Whatever you return is re-normalised — trimmed, de-duplicated case-insensitively * and capped — so a filter can never overflow or corrupt a machine surface. * * @param string[] $topics Resolved topics (manual + derived). * @param \WP_Post $post The post. */ add_filter( 'agentimus_post_topics', function ( $topics, $post ) { return $topics; }, 10, 2 ); /** * Authoritative reference URLs for a topic — emitted as schema.org `sameAs` on * its `about` DefinedTerm so an assistant resolves the exact entity (e.g. the * planet Mercury, not the element). Agentimus never looks these up itself — no * front-end calls and no risky auto-matching — so you supply them, e.g. from a * small topic → Wikidata map. * * @param string[] $urls Reference URLs (default none). * @param string $topic The topic text. * @param \WP_Post $post The post. */ add_filter( 'agentimus_topic_links', function ( $urls, $topic ) { $map = array( 'WordPress' => 'https://www.wikidata.org/wiki/Q13166', 'PHP' => 'https://www.wikidata.org/wiki/Q59', ); if ( isset( $map[ $topic ] ) ) { $urls[] = $map[ $topic ]; } return $urls; }, 10, 2 ); /** * The suggestions offered (as you type) in the editor's "Topics for AI" box. * Defaults to topics already used on the site, its tags & categories, and your * declared Expertise — add your own controlled vocabulary here. * * @param string[] $pool Suggested topic strings. */ add_filter( 'agentimus_topic_suggestions', function ( $pool ) { $pool[] = 'llms.txt'; $pool[] = 'AI visibility'; return $pool; } ); /** * Per-item byte cap for the llms-full.txt full-text edition. */ add_filter( 'agentimus_llms_full_item_max_bytes', fn( $bytes ) => $bytes ); /** * Average item size used only to ESTIMATE the full-text edition size in the admin. */ add_filter( 'agentimus_llms_full_avg_item_bytes', fn( $bytes ) => 4096 ); /** * Size of the change feed window (/agentimus-changes.json) — how many * newest-modified items it can hold. Clamped to [1, 2000]. Agents pass * `?since=` to receive only items changed after that. * The feed reports additions/edits (`action:"created"|"updated"`) plus * removals (`action:"deleted"`), so a caching agent can also evict gone URLs. * * @param int $max Default 200. */ add_filter( 'agentimus_changes_max', fn( $max ) => 200 ); /** * How long a deletion stays in the change feed before it's pruned (days). * Clamped to [1, 3650]. * * @param int $days Default 90. */ add_filter( 'agentimus_tombstone_retain_days', fn( $days ) => 90 ); /** * Cede a surface to your own producer so Agentimus stops emitting it. * Surfaces: llms_txt | llms_full | changes | markdown | link_headers | robots. * * @param bool $yield Whether to yield. * @param string $surface The surface key. */ add_filter( 'agentimus_yield_surface', fn( $yield, $surface ) => 'robots' === $surface ? true : $yield, 10, 2 ); /* ===================================================================== * [EXTENSION] schema.org JSON-LD * ===================================================================== */ /** * Whether to emit the front-end JSON-LD. Return false to stand down for an SEO plugin. */ add_filter( 'agentimus_defer_schema', fn( $active ) => $active ); /** * Replace a single post's JSON-LD node (e.g. a Product or Service). * * @param array $node The node. * @param \WP_Post $post The post. */ add_filter( 'agentimus_schema_for_post', function ( $node, $post ) { if ( 'acme_product' === $post->post_type ) { $node['@type'] = 'Product'; } return $node; }, 10, 2 ); /** * Last-chance edit of the entire JSON-LD @graph before output. */ add_filter( 'agentimus_schema_graph', fn( $graph ) => $graph ); /** * Contribute extra question/answer pairs to the FAQPage schema. * * @param array[] $pairs FAQ pairs. * @param \WP_Post $post The post. */ add_filter( 'agentimus_faq_pairs', fn( $pairs, $post ) => $pairs, 10, 2 ); /* ===================================================================== * [EXTENSION] Sitemap * ===================================================================== */ /** * Override the detected sitemap URL (return the fallback unchanged to keep it). */ add_filter( 'agentimus_sitemap', fn( $url ) => $url ); /** * Cap the number of URLs in the generated sitemap. */ add_filter( 'agentimus_sitemap_max_urls', fn( $n ) => 2000 ); /* ===================================================================== * [EXTENSION] REST auto-discovery * ===================================================================== */ /** * Master switch for REST namespace auto-discovery. */ add_filter( 'agentimus_rest_discovery', fn( $on ) => $on ); /** * REST namespaces to publish in the discovery document. */ add_filter( 'agentimus_rest_namespaces', function ( $allowed ) { $allowed[] = 'acme/v1'; return $allowed; } ); /** * REST namespaces to exclude from discovery. */ add_filter( 'agentimus_rest_skip_namespaces', function ( $skip ) { $skip[] = 'acme-internal/v1'; return $skip; } ); /** * Include or exclude a single WP ability from discovery. * * @param bool $ok Whether to include it. * @param string $name Ability name. * @param mixed $ability The ability object. */ add_filter( 'agentimus_discoverable_ability', fn( $ok, $name, $ability ) => $ok, 10, 3 ); /* ===================================================================== * [EXTENSION] security.txt, readiness & signing * ===================================================================== */ /** * Whether Agentimus should generate a /.well-known/security.txt. */ add_filter( 'agentimus_serve_security_txt', fn( $on ) => $on ); /** * Edit the final security.txt body. */ add_filter( 'agentimus_security_txt', function ( $body ) { return rtrim( $body ) . "\nAcknowledgments: https://acme.example/security/thanks\n"; } ); /** * The security.txt Expires window, in days. */ add_filter( 'agentimus_security_txt_expires_days', fn( $days ) => 180 ); /** * Add or adjust the admin Discovery Hub readiness checks. * * @param array[] $checks Readiness checks. * @param \Agentimus\Settings $settings Settings. */ add_filter( 'agentimus_readiness_checks', function ( $checks, $settings ) { $checks[] = array( 'id' => 'acme_api_reachable', 'label' => 'Acme API reachable', 'pass' => true, ); return $checks; }, 10, 2 ); /** * Add, retune or drop the per-page "AI Readability" checks shown in the editor — * the per-post sibling of agentimus_readiness_checks (which is site-wide). You get * the computed rows PLUS the parsed page $stats, so you can re-grade against your * own thresholds without re-parsing the HTML. * * $stats keys: words, headings (int levels[]), paragraphs (int word-counts[]), * links, link_words, images, images_no_alt, has_excerpt (bool). * Row shape: { id, label, status: pass|warn|fail, detail }. * * @param array[] $checks Computed rows. * @param array $stats Parsed page stats. * @param \WP_Post $post The post. */ add_filter( 'agentimus_page_checks', function ( $checks, $stats, $post ) { // 1. Retune a threshold — require 150 words instead of the default 100. $checks = array_values( array_filter( $checks, fn( $c ) => 'words' !== $c['id'] ) ); $checks[] = $stats['words'] >= 150 ? array( 'id' => 'words', 'label' => 'Enough substance', 'status' => 'pass', 'detail' => $stats['words'] . ' words.' ) : array( 'id' => 'words', 'label' => 'Thin content', 'status' => 'warn', 'detail' => 'Aim for 150+ words.' ); // 2. Add your own check (e.g. require a featured image on image-heavy posts). if ( $stats['images'] > 0 && ! has_post_thumbnail( $post ) ) { $checks[] = array( 'id' => 'featured', 'label' => 'No featured image', 'status' => 'warn', 'detail' => 'Set one so cards and previews have an image.' ); } // 3. Drop a check you don't want surfaced. $checks = array_values( array_filter( $checks, fn( $c ) => 'link_density' !== $c['id'] ) ); return $checks; }, 10, 3 ); /** * Supply the Ed25519 signing secret key from a constant/vault instead of the DB. */ add_filter( 'agentimus_signing_secret_key', fn( $key ) => $key ); /* ===================================================================== * [INTERNAL] Guard (opt-in UA blocking) * Advanced site-owner knobs — not a third-party integration surface. * ===================================================================== */ /** * The Guard's final say on whether to 403 a request. * * @param bool $deny Whether to deny. * @param string $ua_lc Lower-cased user agent. */ add_filter( 'agentimus_deny_request', fn( $deny, $ua_lc ) => $deny, 10, 2 ); /** * Clients that must never be hard-blocked (search engines + your allow-list). */ add_filter( 'agentimus_block_allowlist', function ( $allowed ) { $allowed[] = 'acme-monitor'; return $allowed; } ); /** * Structured engine signatures used to match real crawlers at a token boundary. */ add_filter( 'agentimus_engine_signatures', function ( $sigs ) { $sigs[] = 'acme-crawler'; return $sigs; } ); /** * Generic user-agent tokens treated as low-signal. */ add_filter( 'agentimus_generic_ua_tokens', function ( $generic ) { $generic[] = 'acme-genericbot'; return $generic; } ); /* ===================================================================== * [INTERNAL] Classifier (labelling, not blocking) * ===================================================================== */ /** * User-agent → friendly label for the activity log. */ add_filter( 'agentimus_agent_map', function ( $map ) { $map['acme-research-agent'] = 'Acme Research Agent'; return $map; } ); /** * Platform markers that flag a spoofed/legacy-device "scanner". */ add_filter( 'agentimus_spoof_signatures', function ( $sigs ) { $sigs[] = 'mozilla/5.0 acme-spoof'; return $sigs; } ); /* ===================================================================== * [INTERNAL] Activity analytics & catalogs * ===================================================================== */ /** * Known-agent catalog (user-agent => label) for the activity log. */ add_filter( 'agentimus_known_agents', function ( $catalog ) { $catalog['acme-research-agent'] = 'Acme Research Agent'; return $catalog; } ); /** * Scanner user-agents offered as one-click block suggestions. */ add_filter( 'agentimus_known_scanners', function ( $known ) { $known[] = 'acme-scanner'; return $known; } ); /** * AI-trainer user-agents offered for robots.txt blocking. */ add_filter( 'agentimus_known_trainers', function ( $known ) { $known[] = 'acme-trainer'; return $known; } ); /** * Referrer host => friendly name for "Traffic from AI" attribution. */ add_filter( 'agentimus_ai_referral_sources', function ( $map ) { $map['acme.example'] = 'Acme'; return $map; } ); /** * Whether to skip recording hits from logged-in admins. */ add_filter( 'agentimus_activity_skip_self', fn( $skip ) => $skip ); /** * How long agent hits are retained, in days. */ add_filter( 'agentimus_activity_retention_days', fn( $days ) => $days ); /** * The "new agent" window for the activity-to-review panel, in seconds. */ add_filter( 'agentimus_new_agent_seconds', fn( $secs ) => $secs ); /** * Minimum hits to flag a burst. */ add_filter( 'agentimus_burst_min_hits', fn( $n ) => $n ); /** * Minimum hits to flag heavy usage. */ add_filter( 'agentimus_heavy_min_hits', fn( $n ) => $n ); /** * Maximum rows in the "activity to review" panel. */ add_filter( 'agentimus_threats_limit', fn( $n ) => $n ); /* ===================================================================== * [INTERNAL] Settings * ===================================================================== */ /** * The default settings array (seed your own companion defaults). */ add_filter( 'agentimus_default_settings', fn( $defaults ) => $defaults ); /** * The live, merged settings array at read time. */ add_filter( 'agentimus_settings', fn( $all ) => $all ); /** * Validate/coerce companion-added fields when settings are saved. * * @param array $clean Sanitized settings. * @param array $input Raw input. */ add_filter( 'agentimus_sanitize_settings', fn( $clean, $input ) => $clean, 10, 2 ); /** * Run when the owner resets settings (e.g. clear your own caches). */ add_action( 'agentimus_settings_reset', function () { // my_addon_clear_caches(); } );