Expose the shared agent session manager via AppComponents (#532)

* Expose agent session manager via AppComponents

* Polish AppComponents session manager naming
This commit is contained in:
Henrik Rosenquist
2026-03-12 12:14:01 -07:00
committed by GitHub
parent c592c50dad
commit fd574b2859
2 changed files with 74 additions and 3 deletions
+72
View File
@@ -9,6 +9,7 @@
use std::sync::Arc;
use crate::agent::SessionManager as AgentSessionManager;
use crate::channels::web::log_layer::LogBroadcaster;
use crate::config::Config;
use crate::context::ContextManager;
@@ -46,6 +47,8 @@ pub struct AppComponents {
pub log_broadcaster: Arc<LogBroadcaster>,
pub context_manager: Arc<ContextManager>,
pub hooks: Arc<HookRegistry>,
/// Shared thread/session manager used by the standard agent runtime.
pub agent_session_manager: Arc<AgentSessionManager>,
pub skill_registry: Option<Arc<std::sync::RwLock<SkillRegistry>>>,
pub skill_catalog: Option<Arc<SkillCatalog>>,
pub cost_guard: Arc<crate::agent::cost_guard::CostGuard>,
@@ -689,6 +692,8 @@ impl AppBuilder {
// Create hook registry early so runtime extension activation can register hooks.
let hooks = Arc::new(HookRegistry::new());
let agent_session_manager =
Arc::new(AgentSessionManager::new().with_hooks(Arc::clone(&hooks)));
let (
mcp_session_manager,
@@ -795,6 +800,7 @@ impl AppBuilder {
log_broadcaster: self.log_broadcaster,
context_manager,
hooks,
agent_session_manager,
skill_registry,
skill_catalog,
cost_guard,
@@ -805,3 +811,69 @@ impl AppBuilder {
})
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use async_trait::async_trait;
use tokio::sync::mpsc;
use crate::agent::SessionManager as AgentSessionManager;
use crate::hooks::{
Hook, HookContext, HookError, HookEvent, HookOutcome, HookPoint, HookRegistry,
};
struct SessionStartHook {
tx: mpsc::UnboundedSender<(String, String)>,
}
#[async_trait]
impl Hook for SessionStartHook {
fn name(&self) -> &str {
"session-start-test"
}
fn hook_points(&self) -> &[HookPoint] {
&[HookPoint::OnSessionStart]
}
async fn execute(
&self,
event: &HookEvent,
_ctx: &HookContext,
) -> Result<HookOutcome, HookError> {
if let HookEvent::SessionStart {
user_id,
session_id,
} = event
{
self.tx
.send((user_id.clone(), session_id.clone()))
.expect("test channel receiver should be alive");
} else {
panic!("SessionStartHook received an unexpected event: {event:?}");
}
Ok(HookOutcome::ok())
}
}
#[tokio::test]
async fn agent_session_manager_runs_session_start_hooks() {
let hooks = Arc::new(HookRegistry::new());
let (tx, mut rx) = mpsc::unbounded_channel();
hooks.register(Arc::new(SessionStartHook { tx })).await;
let manager = AgentSessionManager::new().with_hooks(Arc::clone(&hooks));
manager.get_or_create_session("user-123").await;
let (user_id, session_id) =
tokio::time::timeout(std::time::Duration::from_secs(1), rx.recv())
.await
.expect("session start hook should fire")
.expect("session start payload should be present");
assert_eq!(user_id, "user-123");
assert!(!session_id.is_empty());
}
}
+2 -3
View File
@@ -429,9 +429,8 @@ async fn async_main() -> anyhow::Result<()> {
"Lifecycle hooks initialized"
);
// Create session manager (shared between agent and web gateway)
let session_manager =
Arc::new(ironclaw::agent::SessionManager::new().with_hooks(components.hooks.clone()));
// Reuse the shared agent session manager prepared by AppBuilder.
let session_manager = Arc::clone(&components.agent_session_manager);
// Lazy scheduler slot — filled after Agent::new creates the Scheduler.
// Allows CreateJobTool to dispatch local jobs via the Scheduler even though