AgentPlugin¶
Purpose¶
Agent plugins extend the embedded Claude agent. One AgentPlugin can contribute, when enabled:
a skill — a system-prompt snippet materialized as a
SKILL.mdfile in the per-session SDK plugin directory (viaget_system_prompt()), optionally with supplementary reference documents loaded lazily on demand (viaget_references_dir());a bag of MCP tools — an in-process MCP server assembled from
@tool-decorated callables (viacreate_tools()).
A plugin may provide either or both; one settings toggle controls a plugin’s prompt and tools together.
Base Class¶
from lightfall.plugins.agent_plugin import AgentPlugin
Class attribute |
Value |
|---|---|
|
|
|
|
Required Properties¶
@property
def name(self) -> str: ...
@property
def description(self) -> str: ...
name— unique plugin identifier, at most 64 characters, lowercase with hyphens/underscores. It is used as the manifest entry name, theSKILL.mdfrontmattername(underscores are converted to hyphens at materialization), the MCP server name (tools becomemcp__<name>__<tool_name>), and the settings-UI preference identifier.description— one-line description shown in the settings UI and used as theSKILL.mdfrontmatterdescription. Truncated to 1024 characters at materialization (an SDK limit); a warning is logged if truncation occurs.
Optional Properties¶
Property |
Default |
Purpose |
|---|---|---|
|
|
Human-readable name in the settings UI |
|
|
Settings-UI grouping; common values: |
|
|
Whether the plugin is active before the user touches the toggle |
|
|
Sort order (lower = first) in the settings UI and in session assembly |
Optional Methods¶
def get_system_prompt(self) -> str:
"""Return the SKILL.md body. Empty string = no skill contribution."""
return ""
def create_tools(self) -> list[Any]:
"""Return @tool-decorated callables. Empty = no MCP server contribution."""
return []
def get_references_dir(self) -> Path | None:
"""Optional package directory of supplementary docs, copied to
references/ next to the SKILL.md and loaded lazily by the SDK Skill tool."""
return None
Tools are created with the claude_agent_sdk.tool decorator, which takes a name, description, and JSON input_schema, and wraps an async function receiving the arguments dict. Tool results must use the MCP content format; the helpers in lightfall.plugins.agents._mcp_helpers (mcp_result, mcp_error) produce it. Tools that touch Qt objects must hop to the main thread (lightfall.claude._internal.threading.run_on_main_thread).
Lifecycle: how a session is assembled¶
Registration.
PluginLoaderinstantiates each manifest entry withtype_name="agent"and registers the instance withAgentRegistry(lightfall.ui.panels.claude.agent_registry).Enablement.
AgentRegistry.enabled_plugins()applies the user’s toggles from the Claude Tools settings page, returning plugins sorted bypriority. Semantics are opt-out: a plugin is enabled when its name is not in thedisabled_tool_pluginspreference and eitherenabled_by_defaultis true or its name is inforced_enabled_tool_plugins— so newly installed plugins take their declared default.Session assembly. At agent construction time (
QtClaudeAgent.__init__, implemented inlightfall.claude._session_assembly):A fresh temporary SDK plugin directory is created with a minimal
plugin.json.For each enabled plugin with a non-empty prompt,
materialize_skill()writesskills/<name>/SKILL.md(frontmattername+description, then the prompt body) and copiesget_references_dir()toskills/<name>/references/if provided. References are not injected into the prompt — the SDK’s Skill tool loads them on demand when the skill is invoked.For each enabled plugin with tools,
assemble_mcp_servers()callscreate_sdk_mcp_server(name=plugin.name, ...)and addsmcp__<plugin.name>__<tool_name>entries to the session’s allowed-tools list. These per-plugin servers are merged with the always-onqtserver (screenshot, widget tree, click, type, logs).
Teardown. The session plugin directory is temporary and rebuilt on the next agent construction. Toggling a plugin in settings therefore takes effect on the next agent session, not mid-conversation.
Complete Example¶
A plugin contributing both a skill prompt and one tool. The prompt-only pattern matches the built-in panel_design plugin (lightfall.plugins.agents.panel_design); the tool pattern matches device_tools.
"""Ring-status agent plugin."""
from __future__ import annotations
from typing import Any
from lightfall.plugins.agent_plugin import AgentPlugin
from lightfall.plugins.agents._mcp_helpers import mcp_result
class RingStatusAgent(AgentPlugin):
"""Gives Claude storage-ring context and a current-reading tool."""
@property
def name(self) -> str:
return "ring_status"
@property
def display_name(self) -> str:
return "Ring Status"
@property
def description(self) -> str:
return "Storage-ring status context and a beam-current tool"
@property
def category(self) -> str:
return "operations"
@property
def priority(self) -> int:
return 50
def get_system_prompt(self) -> str:
return (
"## Storage Ring Status\n\n"
"When the user asks about beam availability, check the ring "
"current with the ring_current tool before answering. "
"Top-off mode holds the current near 500 mA; values near zero "
"mean no beam."
)
def create_tools(self) -> list[Any]:
try:
from claude_agent_sdk import tool
except ImportError:
return []
@tool(
name="ring_current",
description="Read the current storage-ring beam current in mA",
input_schema={"type": "object", "properties": {}},
)
async def ring_current(args: dict) -> dict[str, Any]:
value = read_ring_current_somehow() # your data source
return mcp_result({"current_mA": value})
return [ring_current]
With this plugin enabled, the agent’s session contains a skills/ring_status/SKILL.md and the tool is callable as mcp__ring_status__ring_current.
Registration¶
Add a manifest entry (built-in manifest during development, or your package’s manifest for distribution — see External Packages):
PluginEntry(
type_name="agent",
name="ring_status",
import_path="my_beamline.agents.ring_status:RingStatusAgent",
),
Built-in agent plugins¶
The built-in manifest registers agent plugins covering panel interaction (lightfall_core_tools), devices (device_tools), plans (plan_tools), the engine (engine_tools), the IPython console (ipython_tools), panel and plan authoring expertise (panel_design, panel_builder, plan_design), scan planning and alignment guidance (scan_planning, alignment), adaptive experiments (autonomous_experiment), and the current ESAF (current_esaf). Reading their sources under src/lightfall/plugins/agents/ is the fastest way to learn the patterns.