Skip to main content

awaken_runtime/plugins/
lifecycle.rs

1use crate::state::MutationBatch;
2use awaken_contract::registry_spec::AgentSpec;
3use awaken_contract::{PluginConfigKey, StateError};
4
5use super::{PluginDescriptor, PluginRegistrar};
6
7/// Schema declaration for a plugin's config section.
8///
9/// Returned by [`Plugin::config_schemas`] to enable eager validation
10/// during resolve — before hooks ever run.
11///
12/// Contains a JSON Schema generated via `schemars`, validated at resolve
13/// time with `jsonschema`.
14pub struct ConfigSchema {
15    /// Section key in `AgentSpec.sections` (must match `PluginConfigKey::KEY`).
16    pub key: &'static str,
17    /// JSON Schema for this section, generated by `schemars::schema_for!`.
18    pub json_schema: serde_json::Value,
19}
20
21impl ConfigSchema {
22    /// Build a schema declaration directly from a typed plugin config key.
23    pub fn for_key<K: PluginConfigKey>() -> Self {
24        Self {
25            key: K::KEY,
26            json_schema: serde_json::to_value(schemars::schema_for!(K::Config)).unwrap_or_default(),
27        }
28    }
29}
30
31pub trait Plugin: Send + Sync + 'static {
32    fn descriptor(&self) -> PluginDescriptor;
33
34    /// Bind per-run runtime context to the plugin instance.
35    ///
36    /// This is invoked at run startup and on agent handoff so plugins that
37    /// keep runtime-owned handles can bind to the current run's store or inbox.
38    fn bind_runtime_context(
39        &self,
40        _store: &crate::state::StateStore,
41        _owner_inbox: Option<&crate::inbox::InboxSender>,
42    ) {
43    }
44
45    /// Declare capabilities: state keys, hooks, action handlers, effect handlers, permission checkers.
46    /// Called once per resolve to build the ExecutionEnv.
47    fn register(&self, _registrar: &mut PluginRegistrar) -> Result<(), StateError> {
48        Ok(())
49    }
50
51    /// Declare config section schemas for eager validation during resolve.
52    ///
53    /// Override this to declare which `AgentSpec.sections` keys this plugin
54    /// owns and how to validate them. The resolve pipeline calls this to
55    /// catch malformed config before hooks run.
56    fn config_schemas(&self) -> Vec<ConfigSchema> {
57        Vec::new()
58    }
59
60    /// Agent activated: read spec config, write initial state.
61    /// Called when this plugin becomes active for a specific agent.
62    fn on_activate(
63        &self,
64        _agent_spec: &AgentSpec,
65        _patch: &mut MutationBatch,
66    ) -> Result<(), StateError> {
67        Ok(())
68    }
69
70    /// Agent deactivated: clean up agent-scoped state.
71    /// Called when switching away from an agent that uses this plugin.
72    fn on_deactivate(&self, _patch: &mut MutationBatch) -> Result<(), StateError> {
73        Ok(())
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use serde::{Deserialize, Serialize};
81
82    #[derive(Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
83    struct TestConfig {
84        value: String,
85    }
86
87    struct TestConfigKey;
88
89    impl PluginConfigKey for TestConfigKey {
90        const KEY: &'static str = "test";
91        type Config = TestConfig;
92    }
93
94    #[test]
95    fn config_schema_for_key_uses_typed_key_and_config() {
96        let schema = ConfigSchema::for_key::<TestConfigKey>();
97
98        assert_eq!(schema.key, TestConfigKey::KEY);
99        assert_eq!(schema.json_schema["type"], "object");
100        assert!(schema.json_schema["properties"].get("value").is_some());
101    }
102}