Build a custom plugin
A plugin bundles hooks and tools into one reusable object you attach to an agent. It works against the low-level primitives the Agent class exposes: model, system_promptsystemPromptmessages, tools, and hooks. When the built-in plugins don’t fit, you build and distribute your own. See Get Featured to share your plugins with the community.
Using Plugins
Section titled “Using Plugins”Plugins are passed to agents during initialization via the plugins parameter:
from strands import Agentfrom strands.vended_plugins.steering import LLMSteeringHandler
# Create an agent with pluginsagent = Agent( tools=[my_tool], plugins=[LLMSteeringHandler(system_prompt="Guide the agent...")])import { Agent, Plugin, Tool } from '@strands-agents/sdk'
// Create an agent with pluginsconst agent = new Agent({ tools: [myTool], plugins: [new GuidancePlugin('Guide the agent...')],})Building Plugins
Section titled “Building Plugins”Basic Plugin Structure
Section titled “Basic Plugin Structure”A plugin is a class that extends the Plugin base class and defines a name property. For example, a simple logging plugin would look like this:
from strands import Agent, toolfrom strands.plugins import Plugin, hookfrom strands.hooks import BeforeToolCallEvent, AfterToolCallEvent
class LoggingPlugin(Plugin): """A plugin that logs all tool calls and provides a utility tool."""
name = "logging-plugin"
@hook def log_before_tool(self, event: BeforeToolCallEvent) -> None: """Called before each tool execution.""" print(f"[LOG] Calling tool: {event.tool_use['name']}") print(f"[LOG] Input: {event.tool_use['input']}")
@hook def log_after_tool(self, event: AfterToolCallEvent) -> None: """Called after each tool execution.""" print(f"[LOG] Tool completed: {event.tool_use['name']}")
@tool def debug_print(self, message: str) -> str: """Print a debug message.
Args: message: The message to print """ print(f"[DEBUG] {message}") return f"Printed: {message}"
# Using the pluginagent = Agent(plugins=[LoggingPlugin()])agent("Calculate 2 + 2 and print the result")import { Agent, FunctionTool, Plugin, Tool } from '@strands-agents/sdk'import { BeforeToolCallEvent, AfterToolCallEvent } from '@strands-agents/sdk'
class LoggingPlugin implements Plugin { name = 'logging-plugin'
initAgent(agent: LocalAgent): void { // Register hooks manually in initAgent agent.addHook(BeforeToolCallEvent, (event) => { console.log(`[LOG] Calling tool: ${event.toolUse.name}`) console.log(`[LOG] Input: ${JSON.stringify(event.toolUse.input)}`) })
agent.addHook(AfterToolCallEvent, (event) => { console.log(`[LOG] Tool completed: ${event.toolUse.name}`) }) }
getTools(): Tool[] { // Provide additional tools via the plugin return [debugPrintTool] }}
// Using the pluginconst agent = new Agent({ plugins: [new LoggingPlugin()],})
// Custom tool to addconst debugPrintTool = new FunctionTool({ name: 'debug_print', description: 'Print a debug message', inputSchema: { type: 'object', properties: { message: { type: 'string', description: 'The message to print' }, }, required: ['message'], }, callback: async (input: unknown) => { const typedInput = input as { message: string } console.log(`[DEBUG] ${typedInput.message}`) return `Printed: ${typedInput.message}` },})What Happens on Attach
Section titled “What Happens on Attach”When you attach a plugin to an agent, the following happens:
- Discovery: The
Pluginbase class scans for methods decorated with@hookand@tool - Hook Registration: Each
@hookmethod is registered with the agent’s hook registry based on its event type hint - Tool Registration: Each
@toolmethod is added to the agent’s tools list - Initialization: The
init_agent(agent)method is called for any custom setup
- Tool Registration: The
getTools()method is called to get tools provided by the plugin - Initialization: The
initAgent(agent)method is called for hook registration and setup - Hook Registration: In
initAgent, useagent.addHook()to register event callbacks manually
Note: TypeScript does not use @hook or @tool decorators. Instead, tools are returned from getTools() and hooks are registered manually in initAgent().
flowchart TD A[Plugin Attached] --> B["Discover Tools\n(@tool / getTools)"] A --> C["Initialize\n(init_agent / initAgent)"] B --> D[Add Tools] C --> E["Register Hooks\n(@hook / addHook)"] D --> F[Plugin Ready] E --> FRegistering Hooks in Plugins
Section titled “Registering Hooks in Plugins”The @hook Decorator
Section titled “The @hook Decorator”The @hook decorator marks methods as hook callbacks. The event type is automatically inferred from the type hint:
from strands.plugins import Plugin, hookfrom strands.hooks import BeforeModelCallEvent, AfterModelCallEvent
class ModelMonitorPlugin(Plugin): name = "model-monitor"
@hook def before_model(self, event: BeforeModelCallEvent) -> None: """Event type inferred from type hint.""" print("Model call starting...")
@hook def on_model_event(self, event: BeforeModelCallEvent | AfterModelCallEvent) -> None: """Handle multiple event types with a union.""" print(f"Model event: {type(event).__name__}")Manual Hook Registration
Section titled “Manual Hook Registration”TypeScript plugins register hooks manually in the initAgent method using agent.addHook():
import { Plugin } from '@strands-agents/sdk'import { BeforeModelCallEvent, AfterModelCallEvent } from '@strands-agents/sdk'
class ModelMonitorPlugin implements Plugin { name = 'model-monitor'
initAgent(agent: LocalAgent): void { // Register a hook for a single event type agent.addHook(BeforeModelCallEvent, () => { console.log('Model call starting...') })
// Register the same handler for multiple event types (union equivalent) const onModelEvent = (event: BeforeModelCallEvent | AfterModelCallEvent) => { console.log(`Model event: ${event.constructor.name}`) } agent.addHook(BeforeModelCallEvent, onModelEvent) agent.addHook(AfterModelCallEvent, onModelEvent) }}Manual Hook and Tool Registration
Section titled “Manual Hook and Tool Registration”For more control, you can manually register hooks and tools in the init_agentinitAgent
from strands.plugins import Pluginfrom strands.hooks import BeforeToolCallEvent
class ManualPlugin(Plugin): name = "manual-plugin"
def __init__(self, verbose: bool = False): super().__init__() self.verbose = verbose
def init_agent(self, agent: "Agent") -> None: # Conditionally register additional hooks if self.verbose: agent.add_hook(self.verbose_log, BeforeToolCallEvent)
# Access agent properties print(f"Attached to agent with {len(agent.tool_names)} tools")
def verbose_log(self, event: BeforeToolCallEvent) -> None: print(f"[VERBOSE] {event.tool_use}")import { Plugin } from '@strands-agents/sdk'import { BeforeToolCallEvent } from '@strands-agents/sdk'
class ManualPlugin implements Plugin { private verbose: boolean
name = 'manual-plugin'
constructor(options: { verbose?: boolean } = {}) { this.verbose = options.verbose ?? false }
initAgent(agent: LocalAgent): void { // Conditionally register additional hooks if (this.verbose) { agent.addHook(BeforeToolCallEvent, (event) => { console.log(`[VERBOSE] ${JSON.stringify(event.toolUse)}`) }) }
// Access agent tools via toolRegistry console.log(`Attached to agent with ${agent.toolRegistry.list().length} tools`) }}Managing Plugin State
Section titled “Managing Plugin State”Plugins can maintain state that persists across agent invocations. For state that needs to be serialized or shared, use the Agent State mechanism:
from strands import Agentfrom strands.plugins import Plugin, hookfrom strands.hooks import BeforeToolCallEvent, AfterToolCallEvent
class MetricsPlugin(Plugin): """Track tool execution metrics using agent state."""
name = "metrics-plugin"
def init_agent(self, agent: "Agent") -> None: # Initialize state values if not present if "metrics_call_count" not in agent.state: agent.state.set("metrics_call_count", 0)
@hook def count_calls(self, event: BeforeToolCallEvent) -> None: current = event.agent.state.get("metrics_call_count", 0) event.agent.state.set("metrics_call_count", current + 1)
# Usageagent = Agent(plugins=[MetricsPlugin()])agent("Do some work")print(f"Tool calls: {agent.state.get('metrics_call_count')}")import { Agent, Plugin } from '@strands-agents/sdk'import { BeforeToolCallEvent } from '@strands-agents/sdk'
class MetricsPlugin implements Plugin { name = 'metrics-plugin'
initAgent(agent: LocalAgent): void { // Initialize state values if not present if (!agent.appState.get('metrics_call_count')) { agent.appState.set('metrics_call_count', 0) }
agent.addHook(BeforeToolCallEvent, () => { const current = (agent.appState.get('metrics_call_count') as number) ?? 0 agent.appState.set('metrics_call_count', current + 1) }) }}
// Usageconst metricsPlugin = new MetricsPlugin()const agent = new Agent({ plugins: [metricsPlugin],})console.log(`Tool calls: ${agent.appState.get('metrics_call_count')}`)See Agent State for more information on state management.
Async Plugin Initialization
Section titled “Async Plugin Initialization”Plugins can perform asynchronous initialization:
import asynciofrom strands.plugins import Plugin, hookfrom strands.hooks import BeforeToolCallEvent
class AsyncConfigPlugin(Plugin): name = "async-config"
async def init_agent(self, agent: "Agent") -> None: # Async initialization self.config = await self.load_config()
async def load_config(self) -> dict: await asyncio.sleep(0.1) # Simulate async operation return {"setting": "value"}
@hook def use_config(self, event: BeforeToolCallEvent) -> None: print(f"Config: {self.config}")import { Plugin } from '@strands-agents/sdk'import { BeforeToolCallEvent } from '@strands-agents/sdk'
class AsyncConfigPlugin implements Plugin { private config: Record<string, unknown> = {}
name = 'async-config'
async initAgent(agent: LocalAgent): Promise<void> { // Async initialization this.config = await this.loadConfig()
agent.addHook(BeforeToolCallEvent, () => { console.log(`Config: ${JSON.stringify(this.config)}`) }) }
private async loadConfig(): Promise<Record<string, unknown>> { await new Promise((resolve) => setTimeout(resolve, 100)) // Simulate async operation return { setting: 'value' } }}Next Steps
Section titled “Next Steps”- Hooks - Learn about the underlying hook system
- Agent State - Persist and share plugin state
- Get Featured - Share your plugins with the community