Agent Registration

Declarative agent registration uses the Agents API wp_agents_api_init action. Plugins and Data Machine declare agent roles once; Data Machine’s materializer reconciles the registered definitions against the datamachine_agents table on init.

Source: agents-api/inc/class-wp-agent.php, agents-api/inc/class-wp-agents-registry.php, agents-api/inc/register-agents.php, inc/Engine/Agents/AgentRegistry.php, inc/Engine/Agents/datamachine-register-agents.php

Why

Declarative registration lets extensions ship bundled agent roles such as a wiki generator, support triage agent, or content reviewer. User-created and lazily provisioned personal agents continue to use the imperative ability paths described below.

The registry mirrors the register_post_type() / register_taxonomy() pattern: plugins declare roles through Agents API, and Data Machine materializes the product-owned rows and files. The public surface is wp_register_agent(), wp_get_agent(), wp_get_agents(), wp_has_agent(), wp_unregister_agent(), WP_Agent, WP_Agents_Registry, and wp_agents_api_init. Data Machine registers the default site administrator agent through the same hook.

Declaring an agent

Inside your plugin:

php
add_action( 'wp_agents_api_init', function () {
    wp_register_agent( 'wiki-generator', array(
        'label'        => __( 'Wiki Generator', 'my-plugin' ),
        'description'  => __( 'Fetches sources, distills into wiki articles, cross-links.', 'my-plugin' ),
        'memory_seeds' => array(
            'SOUL.md'   => MY_PLUGIN_DIR . 'agents/wiki-generator/SOUL.md',
            'MEMORY.md' => MY_PLUGIN_DIR . 'agents/wiki-generator/MEMORY.md',
        ),
    ) );
} );

That’s it. On the next request where init fires, DM reconciles the registration:

  • If a row with agent_slug = 'wiki-generator' already exists in datamachine_agents, nothing happens. Mutable state is DB-owned; the registration never overwrites it.
  • If the row is missing, DM creates it (owner resolved via owner_resolver or falls back to the default admin user), bootstraps owner access, ensures the agent directory exists, and runs the scaffold ability for every registered agent-layer memory file.
  • For each memory_seeds entry whose target file does not yet exist on disk, the bundled file’s contents become the initial scaffold. Generic site-context defaults apply to any filename without a seed entry.

Registration arguments

wp_register_agent( string|WP_Agent $agent, array $args = array() )

KeyTypeDescription
labelstringDisplay name. Defaults to the slug when omitted.
descriptionstringShort description for admin UI / CLI listings.
memory_seedsarray<string,string>Map of filename => absolute path. Each entry surfaces the bundled file as scaffold content for that filename when the target file does not yet exist on disk. Works for any filename registered via MemoryFileRegistry::register()SOUL.md and MEMORY.md are common, but plugins can seed custom agent-layer files through the same primitive. See Memory seed resolution.
owner_resolvercallableReturns int user_id. Called once at row-creation time. Defaults to DirectoryManager::get_default_agent_user_id().
default_configarrayInitial agent_config persisted on creation. Subsequent config changes go through the DB — the registration never overrides user-edited config.
metaarrayOptional registry metadata for future consumers. Data Machine’s current materializer ignores it.

Slug semantics

Slugs are passed through sanitize_title(). Empty slugs are rejected. They must be unique across a site because the database column has a unique constraint on agent_slug. When two plugins register the same slug, the registration at the later hook priority wins.

WP_Agent is a prepared definition object, not a database row. It validates property types, normalizes the slug and memory seed filenames, and exposes getters (get_slug(), get_label(), get_description(), get_memory_seeds(), get_owner_resolver(), get_default_config(), get_meta()).

Invalid property types reject the definition with a _doing_it_wrong() notice when WordPress provides that function. Unknown properties are ignored with the same notice style so future registry fields do not accidentally become materializer inputs.

Registration Lifecycle

The Agents API registry initializes lazily on its first read or registration. Data Machine’s materializer reads it from init priority 15. Extensions register definitions during wp_agents_api_init; direct registration before materialization is also supported.

Reconciliation

Reconciliation runs on init at priority 15:

  • Priority 10: wp_abilities_api_init fires. Abilities register.
  • Priority 15: AgentRegistry::reconcile() fires the wp_agents_api_init action, collects registrations, creates missing DB rows, scaffolds agent-layer memory files.
  • Priority 20: existing datamachine_needs_scaffold transient check. No-op when the registry has already scaffolded.

The wp_agents_api_init action is also fired lazily by AgentRegistry::get_all() / get() / reconcile(), so callers can query the registry regardless of hook ordering. Extensions use the Agents API registration functions and wp_agents_api_init lifecycle.

Memory seed resolution

Registered memory_seeds entries flow into scaffold content via the existing datamachine_scaffold_content filter chain:

  1. Priority 5 — registry’s generator. For any filename being scaffolded, checks AgentRegistry::get($agent_slug)['memory_seeds'][$filename]. If a readable bundle path is registered, its contents become the scaffold content.
  2. Priority 10 — DM’s default generators (datamachine_scaffold_soul_content, datamachine_scaffold_memory_content, etc.). Produce generic site-context content using agent display name + site metadata.

Registered agents with a memory_seeds entry for a filename win at priority 5. Filenames without a seed entry fall through to priority 10. Agents created imperatively via AgentAbilities::createAgent() (with no registry entry) likewise fall through for every filename.

The scaffold ability never overwrites existing files. Once a seeded file exists on disk, its content is user-editable and plugin updates don’t rewrite it. To reseed from an updated bundled version, delete the file and run the scaffold ability again.

Seeds apply to any filename registered via MemoryFileRegistry::register(). SOUL.md and MEMORY.md ship registered by default, so they work out of the box. Custom agent-layer files need a one-line MemoryFileRegistry::register() call somewhere in the plugin’s bootstrap before a memory_seeds entry can be surfaced for them.

Reconciliation outcomes

AgentRegistry::reconcile() returns a summary for logging / testing:

php
[
    'created'  => [ 'wiki-generator' ],  // newly inserted into datamachine_agents
    'existing' => [ 'chubes' ],          // row already present, skipped
    'skipped'  => [],                    // owner resolution failed or DB insert failed
]

The datamachine_registered_agent_reconciled action fires for each newly-materialized agent:

php
do_action( 'datamachine_registered_agent_reconciled', int $agent_id, string $slug, array $definition );

DM core dogfood

Data Machine registers its default site administrator agent through the same hook:

php
add_action( 'wp_agents_api_init', function () {
    $default_user_id = DirectoryManager::get_default_agent_user_id();
    $user            = get_user_by( 'id', $default_user_id );

    wp_register_agent(
        sanitize_title( $user->user_login ),
        array(
            'label'          => $user->display_name,
            'description'    => 'Default site administrator agent.',
            'owner_resolver' => fn() => $default_user_id,
        )
    );
}, 10 );

Same API. Same hook priority as any plugin. On existing installs this is a no-op (the per-user agent already exists); on fresh installs the registry is the primary creation path for the default agent.

When to register vs create imperatively

ScenarioPattern
A role bundled with a plugin, same on every installRegister via wp_agents_api_init
A user-created agent with install-specific name, owner, configCreate imperatively via AgentAbilities::createAgent()
Lazy provisioning of a per-user agent on first chat turnUse datamachine_resolve_or_create_agent_id($user_id)

Registered agents and imperatively-created agents coexist cleanly — they’re all just rows in datamachine_agents keyed by slug. The registry is an additive declarative path, not a replacement for the imperative API.

Overriding a registered agent

Two override paths. Pick based on what you’re trying to change.

1. Override registration intent (fresh installs only)

Hook at a higher priority and re-register with the same slug:

php
add_action( 'wp_agents_api_init', function () {
    wp_register_agent( 'wiki-generator', array(
        'label'        => __( 'Custom Wiki Generator', 'my-override' ),
        'memory_seeds' => array(
            'SOUL.md' => __DIR__ . '/custom-wiki-soul.md',
        ),
    ) );
}, 20 ); // Higher than the original plugin's priority 10.

Last registration wins at the registry level. Because reconciliation is create-if-missing and the scaffold ability never overwrites existing files, an override only affects fresh creation:

StateOverride applies?
Agent row doesn’t exist yet✅ Yes — your registration creates the row with your label + scaffolds from your memory_seeds
Agent row exists, seeded file doesn’t✅ Partially — label/description are ignored (DB-owned), but the next scaffold cycle picks up your memory_seeds for any still-missing files
Agent row exists and seeded files exist❌ No — registration changes don’t propagate to existing DB rows, and scaffold never overwrites existing files

To reseed SOUL.md on an existing install, delete the file and let the scaffold ability regenerate it. To change agent_name or agent_config, go through the DB (wp datamachine pipeline update, admin UI, or direct Agents::update_agent() call) — those are DB-owned, user-editable fields.

2. Suppress a default registration entirely

Every DM core registration is a named function — callers can remove it cleanly:

php
remove_action(
    'wp_agents_api_init',
    'datamachine_register_default_admin_agent',
    10
);

This prevents the registration from contributing to the registry at all. Useful for deployments that want full control over which agents exist on their site.

Plugins that bundle their own default registrations should follow the same convention — use a named function, document the handle in their README so site operators can suppress them.

3. Change SOUL.md content on an existing agent

Neither path 1 nor path 2 touches SOUL.md content once it exists on disk. To replace content for an already-materialized agent, the clean options are:

  • Delete and reseed — remove the existing SOUL.md file, let the scaffold ability regenerate on the next read path (scaffold is idempotent + never overwrites extant files, so deletion is the trigger).
  • Hook datamachine_scaffold_content directly — for conditional overrides based on agent context (e.g. Intelligence’s intelligence_kit agent_config flag already does this at priority 20).

Registry-level overrides are the right tool for declaring defaults; content-level overrides are the right tool for active SOUL.md substitution.

  • docs/core-system/multi-agent-architecture.md — agents table schema, access control, filesystem layout
  • docs/core-filters.md — the wp_agents_api_init action, datamachine_registered_agent_reconciled action, datamachine_scaffold_content filter
  • inc/Abilities/File/ScaffoldAbilities.php — scaffold ability that honors registered memory_seeds content
  • inc/migrations/scaffolding.php — default datamachine_scaffold_content generators