-
Notifications
You must be signed in to change notification settings - Fork 41
example agents
Status: Experimental. The whole module sits behind the
agents extended option (OpenStation Preferences → Features → Extended options,
admin-only). While the flag is off none of these hooks or routes
exist — the one thing that stays is the WP Explorer Agents section,
which is always listed and renders read-only, with a way into the
Features tab for admins.
An agent is a login-blocked wp_users row whose definition
(description, system prompt, ability allowlist, triggers, model
override, rate limit) lives as user meta on that row. Full contract:
Hooks Reference — AI Agents.
Alongside the definition an agent carries two identity fields, which is what makes a roster of them readable rather than a list of settings:
| Field | What it is |
|---|---|
vibes |
One short line of voice, capped at 120 characters. Appended to the instructions before a run, and after them so a workflow beats a personality. |
face |
A partial Mio look. Rendered to an SVG on disk and served as the agent's avatar everywhere get_avatar() runs, including the wp-admin Users list and comment attribution. See Mio is a species. |
faceSeed |
The seed the face was rolled from. Provenance, not the face itself. |
Both travel through createAgent / updateAgent like any other field.
Abilities and triggers both go in the create call. There is no need
for a second request to attach either.
Agents pick their tools from the WordPress Abilities API — register an ability and it appears in every agent's Tools picker automatically:
add_action( 'wp_abilities_api_init', function () {
wp_register_ability(
'my-plugin/count-drafts',
array(
'label' => __( 'Count drafts', 'my-plugin' ),
'description' => 'Count the current draft posts.',
'category' => 'my-plugin',
'input_schema' => array( 'type' => 'object', 'properties' => array() ),
'output_schema' => array(
'type' => 'object',
'properties' => array( 'drafts' => array( 'type' => 'integer' ) ),
),
'execute_callback' => function () {
return array( 'drafts' => (int) wp_count_posts()->draft );
},
// Evaluated against the AGENT user during a run.
'permission_callback' => function () {
return current_user_can( 'edit_posts' );
},
// Truthful annotation — drives the read-only badge in the
// picker (and offers the ability to the AI Copilot too).
'meta' => array(
'annotations' => array( 'readonly' => true ),
),
)
);
} );The agent runs the tool as itself: the permission_callback sees
the agent's role, so an agent whose role lacks edit_posts cannot
call this even when it is on the allowlist.
$agents = openstation_agent_get_agents();
if ( $agents ) {
$result = openstation_agent_invoke(
$agents[0]->ID,
'Summarize the last comment on the site.',
array( 'source' => 'my-plugin/cron' )
);
if ( ! is_wp_error( $result ) ) {
// $result = array( 'text' => ..., 'toolCalls' => [...], 'turns' => N )
}
}Every successful run fires openstation_agent_completed with the
same result plus your context array.
User meta has no revisions — these actions are the audit trail:
add_action( 'openstation_agent_updated', function ( $agent_id, $changed, $actor_id ) {
foreach ( $changed as $field => $delta ) {
my_plugin_audit_log(
sprintf(
'Agent #%d %s changed by #%d: %s -> %s',
$agent_id,
$field,
$actor_id,
wp_json_encode( $delta['from'] ),
wp_json_encode( $delta['to'] )
)
);
}
}, 10, 3 );openstation_agent_created and openstation_agent_deleted complete
the set.
Trigger configuration is stored per-agent now; intakes beyond chat,
Send to and Drag & drop arrive in later phases. Declaring a kind gives
it a card in the Triggers pane and the create flow's Summon step: an
On switch, or the entity-kind checkboxes when its config_schema has
an entityKinds property. Set 'wired' => false to keep it out of the
UI until your intake exists; stored rows survive either way.
add_filter( 'openstation_agent_trigger_kinds', function ( $kinds ) {
$kinds[] = array(
'slug' => 'my-plugin-webhook',
'label' => __( 'My webhook', 'my-plugin' ),
'description' => __( 'Run when my-plugin receives a webhook.', 'my-plugin' ),
'icon' => 'dashicons-rest-api',
'config_schema' => array(
'type' => 'object',
'properties' => array(
'event' => array( 'type' => 'string' ),
),
),
);
return $kinds;
} );Read it back with openstation_agent_get_triggers( $agent_id ) and
wire your own intake to openstation_agent_invoke().
wp.os.whenReady( () => {
const store = wp.os.createSharedStore(
'desktop-mode/agents-chat',
() => ( { activeAgent: null, transcripts: {} } ),
);
store.state.activeAgent = {
id: 12,
name: 'Audit Agent',
description: 'Audits drafts.',
avatarUrl: '',
};
store.notify();
wp.os.openWindow( 'desktop-mode-agent-run', { source: 'my-plugin' } );
} );A transcript row may carry an attachment — the entity the message is
about. The chat renders it as a card instead of the message text, and
clicking the card opens that object's admin screen in its own window.
The drag-drop and "Send to" intakes both set it; a plugin pushing its
own row can too:
store.state.transcripts[ 12 ].push( {
role: 'user',
// The prose is what the RUNNER reads — keep it explicit.
text: 'Review the post "Hello world" (id 188).',
at: Date.now(),
// The card is what the USER sees.
attachment: { kind: 'post', id: 188, title: 'Hello world' },
} );
store.notify();kind is one of post, page, media, user, comment. The
attachment is persisted with the conversation, so a reopened
transcript still shows the card.
// Tighten who may invoke agents (default: edit_posts).
add_filter( 'openstation_agents_user_can_invoke', function () {
return current_user_can( 'manage_options' );
} );
// Platform-wide default rate limit (default: 60 runs/hour/agent).
add_filter( 'openstation_agent_default_rate_limit', fn () => 10 );
// Redact tool output before it re-enters the model context.
add_filter( 'openstation_agent_tool_result', function ( $output, $slug ) {
if ( is_array( $output ) ) {
unset( $output['user_email'] );
}
return $output;
}, 10, 2 );This wiki is generated from the docs/ directory — edits made here are overwritten by the next sync.
To change a page, open a pull request against docs/.
Guides
- Development guide
- Releasing openstation
- Agents security model
- API Index
- Architecture
- Bridge protocol — wiring overview
- <os-*> component reference
- Native Desktop Host — Experimental
- Desktop themes
- Dock customization — two registries, one mental model
- The event-driven framework
- Files on the Desktop
- Folder sharing
- Getting Started
- Hooks Reference
- Icons
- JavaScript Reference
- The Living Tree — algorithm definition
- Mio
- Native Windows & Framework Interop
- Plugin compatibility layer
- Progressive Web App (PWA)
- Station Home
- Using openstation from your own plugin
Migration notes
- Migration: built-in activity channels move to the os/ namespace
- Migration: window, wallpaper and widget bundles load on demand
- Migration — the navigation model
- Migration: a native window's tabs move to the window chrome
All examples
- AI Agents — extend and invoke from a plugin
- wp.os.ai.ask() — programmatic AI Copilot
- Tune the AI model config
- Custom arrange-menu action
- Open a child window its owner can't cover
- Style a specific admin page inside the iframe
- Code Blue — register your plugin's log file
- Open a file in the Code editor (deep-link from any window)
- Connect to a window — title-bar button + iframe pub/sub
- Content changes — live-refresh every window listing your type
- Custom window chrome (Experimental)
- Register a custom unfocused-window effect
- Example: render a data table
- Real file storage — react to uploads, gate policy, share from PHP
- React to a window being set free onto the real desktop
- Cross-window devtools — instrumentation primitives
- Add a dock item with a badge
- Decorate the dock without forking the renderer
- Replace the dock rail entirely
- Retune the Drafts widget's AI writing assistant
- Gate OpenStation by role
- Iframe-initiated window opens
- Build a feed reader without the bookkeeping
- Inject data into openStationConfig
- Render a list without losing clicks — renderKeyedList()
- Example: layout primitives (body → panel → row → col)
- Use <os-*> components from a plugin that ships as a zip
- Restyle and drive Mio
- Add an action that works on a whole selection
- WP Explorer — custom post types and their folder
- Add an action button to a WP Explorer preview pane
- Example: native Posts window
- Example: native window with tabs
- Native windows
- Customize note → post conversion
- Send a notification
- OAuth relay — connect to an external service
- OS-file drop
- <os-flyout> — window-scoped sliding card
- Plugins window — extras
- Track who's around — wp.os.presence
- Example: progress bar
- PWA install — surface your own button
- React to window events
- Example: extend the Trash
- Register a slash-command
- Register a desktop theme from a plugin
- Register a game
- Example: register a desktop icon (Jorvy)
- Register a wallpaper
- Register a widget
- Related entities — extend the title bar's "Related" menu
- The native-window render ctx
- Programmatic folder sharing
- Share state across multi-bundle plugins — wp.os.createSharedStore()
- Example: loading spinner
- Add an opt-in card to Station Home
- Accept drops on your desktop icon
- Give a tile two icons, one per state
- Add a row to a window's ⋯ menu
- Example: window activity & the status ring
- Window controls
- Subscribe to window lifecycle events
- Window links — relate windows and restyle the ties (Experimental)
- Window loading state — spinner overlay & ready signal
- Show a banner at the top of a window
- Pulse a window's icon — Window.requestAttention()
- Register a custom window reveal
- Window slots
- Window themes
- Native window with bundle-bound config