From 8121168bbdbfc79bec2535b1d9874e29431d71d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 11:57:57 +0000 Subject: [PATCH 1/2] Initial plan From 9c7b4fb323491f9ef14d129abcef5e4a57f9c9e2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 6 Feb 2026 12:04:12 +0000 Subject: [PATCH 2/2] feat: add plugin architecture with amico-plugin crate Introduces a plugin system covering all aspects of the agent lifecycle: - Plugin trait: core lifecycle hooks (on_start, on_shutdown) - ToolPlugin: plugins that provide tools - EventSourcePlugin: plugins that provide event streams - EventInterceptor: middleware for event handling - PluginSet: composable plugin collections - PluginRuntime: runtime with plugin support Co-authored-by: takasaki404 <193405421+takasaki404@users.noreply.github.com> --- ARCHITECTURE.md | 155 ++++++++++++++++++++- Cargo.lock | 11 ++ Cargo.toml | 2 + amico-plugin/Cargo.toml | 18 +++ amico-plugin/src/lib.rs | 293 ++++++++++++++++++++++++++++++++++++++++ amico/Cargo.toml | 1 + amico/src/lib.rs | 57 +++++++- 7 files changed, 528 insertions(+), 9 deletions(-) create mode 100644 amico-plugin/Cargo.toml create mode 100644 amico-plugin/src/lib.rs diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 6ea8c83..0a3bc01 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -16,7 +16,7 @@ This document describes the system architecture using functional design principl ## System Layers -The Amico V2 architecture consists of four distinct layers: +The Amico V2 architecture consists of five distinct layers: ``` ┌─────────────────────────────────────────┐ @@ -24,6 +24,8 @@ The Amico V2 architecture consists of four distinct layers: ├─────────────────────────────────────────┤ │ Workflows Layer (Presets) │ ← Tool loop agents, etc. ├─────────────────────────────────────────┤ +│ Plugin Layer │ ← Extensible capabilities +├─────────────────────────────────────────┤ │ Runtime Layer │ ← Workflow execution ├─────────────────────────────────────────┤ │ Models Layer │ ← Model abstractions @@ -422,7 +424,150 @@ pub trait MultiAgentWorkflow { - **Multi-agent support**: Coordination patterns for multiple agents - **Reusable**: Built on generic traits, works with any model/tool implementations -## 5. Application Layer (Event Handlers) +## 5. Plugin Layer (`amico-plugin`) + +The plugin layer provides an extensibility mechanism that covers all aspects of the agent lifecycle. Plugins can provide tools, introduce event sources, intercept event handling, and hook into runtime startup/shutdown. + +### Functional Design + +```haskell +-- Plugin lifecycle +type Plugin config error = Build config -> Start -> Shutdown + +-- Plugin capabilities +type ToolPlugin plugin tool = plugin -> [tool] +type EventSourcePlugin plugin event = plugin -> Stream +type EventInterceptor plugin event = plugin -> BeforeHandle event -> AfterHandle event + +-- Plugin composition +type PluginSet plugins = StartAll -> ShutdownAll + +-- Plugin-aware runtime +type PluginRuntime runtime plugins = runtime + plugins +``` + +### Rust Trait Design + +```rust +/// Core plugin trait - all plugins implement this +pub trait Plugin { + type Config; + type Error; + + fn name(&self) -> &str; + fn version(&self) -> &str; + + /// Build the plugin from configuration + fn build(config: Self::Config) -> Result + where + Self: Sized; + + /// Called when the runtime starts + fn on_start(&mut self) -> impl Future> + Send; + + /// Called when the runtime shuts down + fn on_shutdown(&mut self) -> impl Future> + Send; +} + +/// Plugin that provides tools to the agent +pub trait ToolPlugin: Plugin { + type ProvidedTool: Tool; + + fn provided_tools(&self) -> &[Self::ProvidedTool]; +} + +/// Plugin that provides event sources +pub trait EventSourcePlugin: Plugin { + type ProvidedEvent: Event; + type EventStream: Stream; + + fn subscribe(&self) -> Self::EventStream; +} + +/// Plugin that intercepts events (middleware) +pub trait EventInterceptor: Plugin { + type Event: Event; + + fn before_handle(&self, event: &Self::Event) + -> impl Future> + Send; + fn after_handle(&self, event: &Self::Event) + -> impl Future> + Send; +} + +/// Composable set of plugins +pub trait PluginSet { + type Error; + + fn start_all(&mut self) -> impl Future> + Send; + fn shutdown_all(&mut self) -> impl Future> + Send; +} + +/// Runtime with plugin support +pub trait PluginRuntime: Runtime { + type Plugins: PluginSet; + + fn plugins(&self) -> &Self::Plugins; + fn plugins_mut(&mut self) -> &mut Self::Plugins; +} +``` + +### Example: A2A Connector Plugin + +A plugin that connects to an agent-to-agent collaboration platform: + +```rust +struct A2APlugin { + endpoint: String, + // ... connection state +} + +impl Plugin for A2APlugin { + type Config = A2AConfig; + type Error = A2AError; + + fn name(&self) -> &str { "a2a-connector" } + fn version(&self) -> &str { "1.0.0" } + + fn build(config: A2AConfig) -> Result { + Ok(Self { endpoint: config.endpoint }) + } + + async fn on_start(&mut self) -> Result<(), A2AError> { + // Connect to A2A platform + Ok(()) + } + + async fn on_shutdown(&mut self) -> Result<(), A2AError> { + // Disconnect from A2A platform + Ok(()) + } +} + +// The agent developer only handles A2A events: +impl EventHandler for MyHandler { + type Context = AgentContext; + type Response = A2AResponse; + type Error = HandlerError; + + async fn handle(&self, event: A2ARequestEvent, ctx: &AgentContext) + -> Result + { + // Focus on business logic, not protocol details + let response = self.agent.execute(ctx, event.payload).await?; + Ok(A2AResponse::from(response)) + } +} +``` + +### Key Features + +- **Lifecycle-aware**: Plugins hook into startup, shutdown, and event processing phases +- **Capability-based**: Plugins declare what they provide (tools, events, interceptors) +- **Composable**: Multiple plugins compose via `PluginSet` with unified lifecycle +- **Zero-cost**: Traits + generics, no dynamic dispatch overhead +- **Separation of concerns**: Plugin authors handle infrastructure; agent developers handle business logic + +## 6. Application Layer (Event Handlers) The application layer is where developers write their business logic using event handlers - similar to REST endpoint handlers in web frameworks. @@ -594,6 +739,12 @@ amico/ │ │ └── runtime.rs │ └── Cargo.toml │ +├── amico-plugin/ # Plugin architecture (extensibility) +│ ├── src/ +│ │ ├── lib.rs +│ │ └── ... +│ └── Cargo.toml +│ ├── amico-workflows/ # Preset workflows (tool loop, ReAct, etc.) │ ├── src/ │ │ ├── lib.rs diff --git a/Cargo.lock b/Cargo.lock index adc380c..5da1b96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,6 +22,7 @@ name = "amico" version = "2.0.0" dependencies = [ "amico-models", + "amico-plugin", "amico-runtime", "amico-system", "amico-workflows", @@ -37,6 +38,16 @@ dependencies = [ "tokio", ] +[[package]] +name = "amico-plugin" +version = "2.0.0" +dependencies = [ + "amico-runtime", + "amico-system", + "futures", + "tokio", +] + [[package]] name = "amico-runtime" version = "2.0.0" diff --git a/Cargo.toml b/Cargo.toml index 5af03e8..139b42a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "amico-models", "amico-system", "amico-runtime", + "amico-plugin", "amico-workflows", ] resolver = "2" @@ -13,6 +14,7 @@ resolver = "2" amico-models = { path = "./amico-models", version = "2.0.0" } amico-system = { path = "./amico-system", version = "2.0.0" } amico-runtime = { path = "./amico-runtime", version = "2.0.0" } +amico-plugin = { path = "./amico-plugin", version = "2.0.0" } amico-workflows = { path = "./amico-workflows", version = "2.0.0" } # Core dependencies diff --git a/amico-plugin/Cargo.toml b/amico-plugin/Cargo.toml new file mode 100644 index 0000000..7146c74 --- /dev/null +++ b/amico-plugin/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "amico-plugin" +version = "2.0.0" +edition = "2021" +description = "Plugin architecture for extending Amico V2 agent capabilities" +repository = "https://github.com/AIMOverse/amico" +license = "MIT OR Apache-2.0" + +[dependencies] +# Lower-level Amico layers +amico-system = { path = "../amico-system", version = "2.0.0" } +amico-runtime = { path = "../amico-runtime", version = "2.0.0" } + +# Core async runtime +futures = "0.3" + +[dev-dependencies] +tokio = { version = "1.44", features = ["rt", "macros"] } diff --git a/amico-plugin/src/lib.rs b/amico-plugin/src/lib.rs new file mode 100644 index 0000000..df6f4b6 --- /dev/null +++ b/amico-plugin/src/lib.rs @@ -0,0 +1,293 @@ +//! # Amico Plugin System +//! +//! This crate provides the plugin architecture for Amico V2, enabling extensibility +//! across all aspects of the agent lifecycle. Plugins can provide tools, hook into +//! runtime lifecycle events, and extend agent capabilities without modifying core code. +//! +//! ## Design Principles +//! +//! - **Lifecycle-aware**: Plugins hook into startup, shutdown, and event processing +//! - **Capability-based**: Plugins declare what they provide (tools, events, interceptors) +//! - **Composable**: Multiple plugins compose via `PluginSet` +//! - **Zero-cost**: Traits + generics, no dynamic dispatch +//! +//! ## Example +//! +//! ```rust,ignore +//! use amico_plugin::{Plugin, ToolPlugin}; +//! +//! struct A2APlugin { +//! endpoint: String, +//! } +//! +//! impl Plugin for A2APlugin { +//! type Config = A2AConfig; +//! type Error = A2AError; +//! +//! fn name(&self) -> &str { "a2a-connector" } +//! fn version(&self) -> &str { "1.0.0" } +//! +//! fn build(config: A2AConfig) -> Result { +//! Ok(Self { endpoint: config.endpoint }) +//! } +//! +//! async fn on_start(&mut self) -> Result<(), A2AError> { +//! // Connect to A2A platform +//! Ok(()) +//! } +//! +//! async fn on_shutdown(&mut self) -> Result<(), A2AError> { +//! // Disconnect from A2A platform +//! Ok(()) +//! } +//! } +//! ``` + +use std::future::Future; + +/// Core plugin trait - all plugins implement this. +/// +/// A plugin represents a reusable extension that hooks into the agent lifecycle. +/// Plugins are initialized with configuration, started with the runtime, and +/// shut down when the runtime stops. +pub trait Plugin { + /// Plugin configuration type + type Config; + + /// Error type for plugin operations + type Error; + + /// Plugin name (used for identification and debugging) + fn name(&self) -> &str; + + /// Plugin version + fn version(&self) -> &str; + + /// Build the plugin from configuration + fn build(config: Self::Config) -> Result + where + Self: Sized; + + /// Called when the runtime starts - initialize connections, resources, etc. + fn on_start(&mut self) -> impl Future> + Send; + + /// Called when the runtime shuts down - clean up resources + fn on_shutdown(&mut self) -> impl Future> + Send; +} + +/// Plugin that provides tools to the agent. +/// +/// A `ToolPlugin` extends the agent's capabilities by making additional tools +/// available. For example, an A2A connector plugin might provide tools for +/// sending messages to other agents on a collaboration platform. +pub trait ToolPlugin: Plugin { + /// The tool type provided by this plugin + type ProvidedTool: amico_system::Tool; + + /// Returns the tools provided by this plugin + fn provided_tools(&self) -> &[Self::ProvidedTool]; +} + +/// A composable set of plugins with unified lifecycle management. +/// +/// `PluginSet` allows multiple plugins to be composed and their lifecycles +/// managed together. The runtime calls `start_all` and `shutdown_all` to +/// drive all plugins through their lifecycle phases. +pub trait PluginSet { + /// Error type for set operations + type Error; + + /// Start all plugins in the set + fn start_all(&mut self) -> impl Future> + Send; + + /// Shutdown all plugins in the set + fn shutdown_all(&mut self) -> impl Future> + Send; +} + +/// Empty plugin set - no plugins loaded. +impl PluginSet for () { + type Error = PluginError; + + async fn start_all(&mut self) -> Result<(), PluginError> { + Ok(()) + } + + async fn shutdown_all(&mut self) -> Result<(), PluginError> { + Ok(()) + } +} + +/// `PluginSet` implementation for a single plugin. +impl PluginSet for (P,) { + type Error = P::Error; + + async fn start_all(&mut self) -> Result<(), P::Error> { + self.0.on_start().await + } + + async fn shutdown_all(&mut self) -> Result<(), P::Error> { + self.0.on_shutdown().await + } +} + +/// A runtime that supports plugins. +/// +/// Extends the base `Runtime` trait with plugin management. The runtime +/// is responsible for driving the plugin lifecycle alongside its own. +pub trait PluginRuntime: amico_runtime::Runtime { + /// The set of plugins managed by this runtime + type Plugins: PluginSet; + + /// Get a reference to the plugin set + fn plugins(&self) -> &Self::Plugins; + + /// Get a mutable reference to the plugin set + fn plugins_mut(&mut self) -> &mut Self::Plugins; +} + +/// Plugin error types +#[derive(Debug)] +pub enum PluginError { + /// Plugin failed to initialize from configuration + InitializationFailed(String), + /// Plugin failed to start + StartupFailed(String), + /// Plugin failed to shut down + ShutdownFailed(String), + /// A plugin operation failed + OperationFailed(String), + /// Any other plugin error + Other(String), +} + +impl std::fmt::Display for PluginError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::InitializationFailed(msg) => { + write!(f, "Plugin initialization failed: {}", msg) + } + Self::StartupFailed(msg) => write!(f, "Plugin startup failed: {}", msg), + Self::ShutdownFailed(msg) => write!(f, "Plugin shutdown failed: {}", msg), + Self::OperationFailed(msg) => write!(f, "Plugin operation failed: {}", msg), + Self::Other(msg) => write!(f, "Plugin error: {}", msg), + } + } +} + +impl std::error::Error for PluginError {} + +#[cfg(test)] +mod tests { + use super::*; + + // -- Mock plugin for testing -- + + struct MockConfig { + name: String, + } + + #[derive(Debug)] + struct MockError; + + struct MockPlugin { + plugin_name: String, + started: bool, + } + + impl Plugin for MockPlugin { + type Config = MockConfig; + type Error = MockError; + + fn name(&self) -> &str { + &self.plugin_name + } + + fn version(&self) -> &str { + "0.1.0" + } + + fn build(config: MockConfig) -> Result { + Ok(Self { + plugin_name: config.name, + started: false, + }) + } + + async fn on_start(&mut self) -> Result<(), MockError> { + self.started = true; + Ok(()) + } + + async fn on_shutdown(&mut self) -> Result<(), MockError> { + self.started = false; + Ok(()) + } + } + + #[tokio::test] + async fn test_plugin_build() { + let config = MockConfig { + name: "test-plugin".to_string(), + }; + let plugin = MockPlugin::build(config).unwrap(); + assert_eq!(plugin.name(), "test-plugin"); + assert_eq!(plugin.version(), "0.1.0"); + assert!(!plugin.started); + } + + #[tokio::test] + async fn test_plugin_lifecycle() { + let config = MockConfig { + name: "lifecycle-test".to_string(), + }; + let mut plugin = MockPlugin::build(config).unwrap(); + + assert!(!plugin.started); + + plugin.on_start().await.unwrap(); + assert!(plugin.started); + + plugin.on_shutdown().await.unwrap(); + assert!(!plugin.started); + } + + #[tokio::test] + async fn test_empty_plugin_set() { + let mut set = (); + set.start_all().await.unwrap(); + set.shutdown_all().await.unwrap(); + } + + #[tokio::test] + async fn test_single_plugin_set() { + let config = MockConfig { + name: "set-test".to_string(), + }; + let plugin = MockPlugin::build(config).unwrap(); + let mut set = (plugin,); + + set.start_all().await.unwrap(); + assert!(set.0.started); + + set.shutdown_all().await.unwrap(); + assert!(!set.0.started); + } + + #[test] + fn test_plugin_error_display() { + let err = PluginError::InitializationFailed("bad config".to_string()); + assert_eq!(err.to_string(), "Plugin initialization failed: bad config"); + + let err = PluginError::StartupFailed("connection refused".to_string()); + assert_eq!(err.to_string(), "Plugin startup failed: connection refused"); + + let err = PluginError::ShutdownFailed("timeout".to_string()); + assert_eq!(err.to_string(), "Plugin shutdown failed: timeout"); + + let err = PluginError::OperationFailed("not ready".to_string()); + assert_eq!(err.to_string(), "Plugin operation failed: not ready"); + + let err = PluginError::Other("unknown".to_string()); + assert_eq!(err.to_string(), "Plugin error: unknown"); + } +} diff --git a/amico/Cargo.toml b/amico/Cargo.toml index 36fcb53..02019e7 100644 --- a/amico/Cargo.toml +++ b/amico/Cargo.toml @@ -11,6 +11,7 @@ license = "MIT OR Apache-2.0" amico-models = { path = "../amico-models", version = "2.0.0" } amico-system = { path = "../amico-system", version = "2.0.0" } amico-runtime = { path = "../amico-runtime", version = "2.0.0" } +amico-plugin = { path = "../amico-plugin", version = "2.0.0" } amico-workflows = { path = "../amico-workflows", version = "2.0.0" } # Core async runtime diff --git a/amico/src/lib.rs b/amico/src/lib.rs index 25c4919..825d0bc 100644 --- a/amico/src/lib.rs +++ b/amico/src/lib.rs @@ -6,12 +6,13 @@ //! //! ## Architecture //! -//! Amico V2 consists of four layers: +//! Amico V2 consists of five layers: //! //! 1. **Models Layer** (`amico-models`): Abstracts AI models by capability //! 2. **System Layer** (`amico-system`): Tools and side-effects for interacting with the world //! 3. **Runtime Layer** (`amico-runtime`): Workflow execution on different runtime types -//! 4. **Workflows Layer** (`amico-workflows`): Preset workflow patterns +//! 4. **Plugin Layer** (`amico-plugin`): Plugin architecture for extending agent capabilities +//! 5. **Workflows Layer** (`amico-workflows`): Preset workflow patterns //! //! ## Design Principles //! @@ -19,6 +20,7 @@ //! - **Zero-cost Abstractions**: No runtime overhead //! - **Platform Agnostic**: Works on OS, browsers, mobile, embedded devices //! - **Type Safe**: Extensive compile-time verification +//! - **Extensible**: Plugin system covers all aspects of the agent lifecycle //! //! ## Example //! @@ -51,15 +53,17 @@ use std::future::Future; // Re-export all layers pub use amico_models as models; -pub use amico_system as system; +pub use amico_plugin as plugin; pub use amico_runtime as runtime; +pub use amico_system as system; pub use amico_workflows as workflows; // Re-export commonly used types -pub use amico_models::{Model, LanguageModel, LanguageInput, LanguageOutput}; -pub use amico_system::{Tool, SystemEffect, Permission, Observable}; -pub use amico_runtime::{Workflow, ExecutionContext, Runtime, Scheduler}; -pub use amico_workflows::{ToolLoopAgent, AgentResponse, WorkflowError}; +pub use amico_models::{LanguageInput, LanguageModel, LanguageOutput, Model}; +pub use amico_plugin::{Plugin, PluginError, PluginRuntime, PluginSet, ToolPlugin}; +pub use amico_runtime::{ExecutionContext, Runtime, Scheduler, Workflow}; +pub use amico_system::{Observable, Permission, SystemEffect, Tool}; +pub use amico_workflows::{AgentResponse, ToolLoopAgent, WorkflowError}; /// Timestamp in milliseconds since epoch pub type Timestamp = u64; @@ -248,3 +252,42 @@ impl Event for SensorEvent { &self.metadata } } + +/// Plugin that provides event sources. +/// +/// An `EventSourcePlugin` introduces new event streams into the runtime. +/// For example, an A2A connector plugin subscribes to an external agent +/// collaboration platform and surfaces inbound requests as events that the +/// agent developer can handle with an `EventHandler`. +pub trait EventSourcePlugin: Plugin { + /// The event type produced by this plugin + type ProvidedEvent: Event; + + /// The stream type that yields events + type EventStream: amico_system::Stream; + + /// Subscribe to the plugin's event stream + fn subscribe(&self) -> Self::EventStream; +} + +/// Plugin that intercepts events before and after handling (middleware). +/// +/// An `EventInterceptor` can observe or transform events at the boundary of +/// the event dispatch pipeline. Use cases include logging, authentication, +/// rate limiting, or metric collection. +pub trait EventInterceptor: Plugin { + /// The event type this interceptor applies to + type Event: Event; + + /// Called before the event handler processes the event + fn before_handle<'a>( + &'a self, + event: &'a Self::Event, + ) -> impl Future> + Send + 'a; + + /// Called after the event handler processes the event + fn after_handle<'a>( + &'a self, + event: &'a Self::Event, + ) -> impl Future> + Send + 'a; +}