Plugins
Plugins package reusable slash commands such as /find, /peek, or
/edit-last, post-user-turn displays, or both. 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 fast-agent home,
including when you run with --home <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.
Post-user-turn display hooks
Manifest schema v2 adds one optional display hook:
schema_version: 2
name: price-calculator
version: 0.1.0
description: Display estimated cost for the last turn and current session.
hooks:
post_user_turn: ./price_calculator.py:display_cost
post_user_turn runs once after each successful top-level interactive user
turn. It does not run for quiet nested sends, failed turns, programmatic
agent.send(...) calls, or once per subagent. This boundary ensures parallel
workflows, tool loops, retries, and subagents contribute to one consolidated
display.
The handler may be synchronous or asynchronous and returns trusted Rich markup
as str, or None to display nothing:
from fast_agent.plugins import PluginPostUserTurnContext
def show_calls(ctx: PluginPostUserTurnContext) -> str | None:
if not ctx.turn_usage:
return None
return (
f"[dim]Provider calls:[/dim] {len(ctx.turn_usage)} last · {len(ctx.session_usage)} session"
)
The context contains:
plugin_name— manifest plugin name.agent_name— top-level agent selected by the user.turn_usage— canonical provider attempts for the completed user turn.session_usage— cumulative canonical provider attempts for the selected agent or parallel workflow.config— the plugin's mapping fromplugins.config.<plugin-name>.
Handlers run in enabled-plugin order. A load or execution failure is logged and
does not fail the completed agent turn or prevent later display plugins from
running. Hooks require schema_version: 2; schema v1 remains supported for
command-only plugins.
Price calculator
The card-packs registry includes a price calculator using hardcoded GPT-5.6, Kimi K3, and DeepSeek V4 Flash rates. Install and enable it with:
Use /cost for one rollup per top-level user turn, including explanatory
subagent or parallel-child ledgers. Use /cost detail for the provider-attempt
table with model, service tier, context band, token/cache partitions, and
estimated cost.
GPT-5.6 prompts above 272,000 tokens use the long-context table. Standard and Flex prices are supported; Fast-tier calls are shown as unpriced until a Fast price table is configured. Kimi K3 and DeepSeek V4 Flash cache writes are also shown as unpriced because their supplied tables do not specify a cache-write rate. Unknown calls never count as zero: the display labels partial totals with the number of unpriced model calls.
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. |
completer |
str | None |
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. |
images |
list[PluginCommandActionImage] |
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. |
user_turn_usage |
tuple[UserTurnUsage, ...] |
Immutable live-session usage snapshots grouped by top-level user turn. |
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.usage |
property |
Canonical usage accumulator for the active agent, 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.