Command Plugins
Command plugins package reusable slash commands such as /find, /peek, or
/edit-last. They install into the active fast-agent home under
.fast-agent/plugins/ and are enabled by name from fast-agent.yaml.
Install a plugin from the configured plugin registry:
Manage installed plugins:
fast-agent plugins list
fast-agent plugins update
fast-agent plugins update all --yes
fast-agent plugins remove agent-finder
fast-agent plugins list shows each installed plugin, the slash commands it
adds, and any configured key bindings from the plugin manifest.
Use --registry to point at a local or remote marketplace:
Plugin registries are used for direct plugin installs and updates. Card-pack dependencies use the card-pack registry that supplied the selected pack; see Card Packs for the coupling rule.
Global Plugins
Global plugin installs write to FAST_AGENT_HOME when it is set; otherwise
they use ~/.fast-agent. The plugin is enabled in that directory's
fast-agent.yaml:
When FAST_AGENT_HOME is set, plugin names from
$FAST_AGENT_HOME/fast-agent.yaml are merged with the active environment,
including when you run with --env <dir>. If FAST_AGENT_HOME is not set,
~/.fast-agent/fast-agent.yaml is used as the global plugin layer when it
exists.
Only the global file's plugins block is merged; other settings still come
from the normal active config. Global plugins are loaded from the global
plugins/ directory, while project plugins are loaded from the active
environment's plugins/ directory. This allows a central set of slash commands
to be available across projects while still letting each project enable
additional plugins.
Project plugin commands override global commands with the same name. Inline
commands: entries in the active config override both.
Plugin-specific configuration can be stored under plugins.config:
plugins:
enabled:
- agent-finder
config:
agent-finder:
urls:
- https://evalstate-hf-agentfinder.hf.space/search
page_size: 10
prompt_when_multiple: true
Plugins can read their namespaced configuration from
ctx.settings.plugins.config and fall back to defaults when it is missing.
Plugin Manifests
A plugin is a directory containing plugin.yaml:
schema_version: 1
name: agent-finder
version: 0.1.0
description: Discover skills and MCP servers.
commands:
find:
description: Discover skills and MCP servers with Agent Finder
input_hint: "<query>"
handler: ./agentfinder.py:find
Handlers use the same async command-action API as inline commands: entries.
Relative handler paths resolve from the plugin directory, so published plugins
can be moved between environments without editing command paths.
Build a Plugin
The easiest development loop is to create a local plugin directory, point a local marketplace at it, and install from that marketplace:
# my-plugin/plugin.yaml
schema_version: 1
name: my-plugin
version: 0.1.0
description: Developer tools for my workflow.
commands:
draft-reply:
description: Draft a reply from the current conversation
input_hint: "[tone]"
handler: ./commands.py:draft_reply
key: "c-x r"
# my-plugin/commands.py
from fast_agent.command_actions import (
PluginCommandActionContext,
PluginCommandActionResult,
)
async def draft_reply(ctx: PluginCommandActionContext) -> PluginCommandActionResult:
tone = ctx.arguments.strip() or "concise"
last_message = ctx.message_history[-1] if ctx.message_history else None
del last_message
return PluginCommandActionResult(
buffer_prefill=f"Draft a {tone} reply to the last user request."
)
A local marketplace can live at the repository root:
{
"entries": [
{
"name": "my-pack",
"description": "My local card pack.",
"kind": "card",
"repo_url": ".",
"repo_path": "packs/my-pack"
}
],
"command_plugins": [
{
"name": "my-plugin",
"description": "Developer tools for my workflow.",
"repo_url": ".",
"repo_path": "my-plugin"
}
]
}
Install from the local marketplace:
For publication, add the plugin directory under the card-packs repository's
plugins/ directory and add a command_plugins entry to its marketplace.json.
Handler API
Handlers are async Python callables with this signature:
async def handler(
ctx: PluginCommandActionContext,
) -> PluginCommandActionResult | str | None:
...
Returning a plain string is shorthand for PluginCommandActionResult(message=...).
Return None for no visible output.
Manifest Command Fields
| Field | Type | Description |
|---|---|---|
name |
str |
Slash command name without the leading /. |
description |
str |
Human-readable help text for listings and /plugins output. |
handler |
str |
Python handler reference, for example ./commands.py:run. |
input_hint |
str | None |
Optional placeholder shown by surfaces that support command input hints. |
key |
str | None |
Optional key binding metadata. |
Result Fields
| Field | Type | Description |
|---|---|---|
message |
str | None |
Plain text shown in the command output. |
markdown |
str | None |
Markdown output rendered by the UI. |
buffer_prefill |
str | None |
Draft text inserted into the user's input buffer. |
switch_agent |
str | None |
Switch the active TUI agent after the command. |
refresh_agents |
bool |
Refresh agent/card state after the command. |
Context Fields
| Field | Type | Description |
|---|---|---|
command_name |
str |
Slash command name being executed. |
arguments |
str |
Raw text after the slash command. |
agent |
PluginCommandAgentProtocol |
Active agent surface exposed to the command. |
settings |
Settings | None |
Resolved fast-agent settings, when available. |
session_cwd |
Path | None |
Working directory for the interactive session, when available. |
runtime |
PluginRuntime | None |
Optional live-runtime capabilities. |
is_tui |
bool |
True when the command is running in the TUI surface. |
is_acp |
bool |
True when the command is running in the ACP surface. |
Context Helpers
| API | Signature | Description |
|---|---|---|
ctx.agent_name |
property |
Active agent name. |
ctx.context |
property |
Current agent context, when available. |
ctx.message_history |
property |
Current agent message history. |
ctx.agent_registry |
property |
Registered agents, when available. |
ctx.load_message_history |
(messages: list[PromptMessageExtended] | None) -> None |
Replace the active agent's message history. |
ctx.get_agent |
(name: str) -> AgentProtocol | None |
Look up another registered agent. |
ctx.mark_user_adjusted |
(message: PromptMessageExtended, *, note: str | None = None) -> None |
Mark a message as user-adjusted in the audit channel. |
Runtime API
Runtime capabilities are optional because not every surface can support live changes.
| API | Signature | Description |
|---|---|---|
attach_mcp_server |
(*, server_name: str, agent_name: str | None = None, server_config: MCPServerSettings | None = None, options: MCPAttachOptions | None = None) -> MCPAttachResult |
Attach an MCP server to a running MCP-capable agent and refresh instructions. |
detach_mcp_server |
(*, server_name: str, agent_name: str | None = None) -> MCPDetachResult |
Detach an MCP server from a running MCP-capable agent and refresh instructions. |
list_attached_mcp_servers |
(*, agent_name: str | None = None) -> tuple[str, ...] |
List MCP servers attached to a running MCP-capable agent. |
list_configured_detached_mcp_servers |
(*, agent_name: str | None = None) -> tuple[str, ...] |
List configured MCP servers that are not currently attached. |
Card Packs
Card packs can reference command plugins by name in manifest schema v2:
schema_version: 2
name: codex
kind: card
install:
agent_cards:
- agent-cards/dev.md
files:
- fast-agent.yaml
plugins:
required:
- edit-assistant
recommended:
- agent-finder
Required plugins are installed and enabled when the pack is installed. Recommended plugins are discoverable metadata for users and future tooling.
Required plugins are resolved from the same marketplace that supplied the selected card pack. This keeps private/custom registries self-contained:
If ./my-packs.json contains the codex entry above, it should also contain a
matching command_plugins entry for edit-assistant. fast-agent will use
that registry for the pack's required plugins during install and update, even if
your normal plugin registry points somewhere else.
You can still keep a separate plugin registry for ad hoc plugin installs:
That registry is used by fast-agent plugins add .... It is not the dependency
source of truth for a card pack installed from another registry. The simple rule
is: if a pack declares plugins.required, publish matching command_plugins
entries alongside that pack in the same marketplace file.