Skip to content

Plugin System

Every major subsystem in SYNAPSEED is a plugin that implements the SynapsePlugin trait. This enables clean separation of concerns, priority-based initialization, and graceful degradation.

The SynapsePlugin Trait

rust
pub trait SynapsePlugin: Send + Sync {
    /// Unique plugin name.
    fn name(&self) -> &str;

    /// Synchronous initialization. Called once at startup.
    fn on_init(&mut self, ctx: &SynapseContext) -> Result<()>;

    /// Async event handler. Returns an optional new event to broadcast.
    fn on_event<'a>(
        &'a self,
        event: &'a SynapseEvent,
        ctx: &'a SynapseContext,
    ) -> Pin<Box<dyn Future<Output = Result<Option<SynapseEvent>>> + Send + 'a>>;

    /// Priority for initialization order (lower = earlier).
    fn priority(&self) -> u32 { 100 }

    /// Cleanup hook.
    fn on_shutdown(&self) {}
}

Plugin Registry

Plugins are registered in cmd_serve() and sorted by priority before initialization:

rust
let mut plugins: Vec<Box<dyn SynapsePlugin>> = vec![
    Box::new(HuskPlugin::new()),         // priority: 10
    Box::new(RootPlugin::new()),         // priority: 20
    Box::new(CortexPlugin::new()),       // priority: 50
    Box::new(ChronosPlugin::new()),      // priority: 100
    Box::new(ShadowCheckPlugin::new()),  // priority: 150
    Box::new(SearchPlugin::new()),       // priority: 160
    Box::new(TelemetrySinkPlugin::new()),// priority: 200
    Box::new(ArchitectPlugin::new()),    // priority: 210
    Box::new(WhisperPlugin::new()),      // priority: 999
    Box::new(GymPlugin::new()),          // priority: 1000
    Box::new(JanitorPlugin::new()),      // priority: 1010
];
plugins.sort_by_key(|p| p.priority());

Priority Convention

RangePurposeExamples
0–49Security (must init first)Husk (10), Root (20)
50–99Core analysisCortex (50)
100–149History & contextChronos (100)
150–199ToolingShadow (150), Search (160)
200–299InfrastructureTelemetry (200), Architect (210)
900+OrchestrationWhisper (999)

Context Extensions

Plugins share state via type-erased extensions on SynapseContext:

rust
// Plugin registers during on_init:
ctx.set_extension(Arc::new(my_store.clone()));

// MCP tools read later:
if let Some(store) = ctx.get_extension::<MyStore>() {
    store.query(...)
}

This pattern avoids circular dependencies — the MCP crate depends on the feature crate's types, not the other way around.

Graceful Degradation

If a plugin fails to initialize, the system continues without it:

rust
for plugin in &mut plugins {
    if let Err(e) = plugin.on_init(&ctx) {
        eprintln!("[WARN] {} failed to init: {e}", plugin.name());
    }
}

MCP tools that depend on a plugin check for its extension and return a helpful message if unavailable.

Released under the Apache License 2.0.