1use klieo_core::{ids::RunId, memory::EpisodicMemory};
12use std::future::Future;
13use std::sync::Arc;
14use tokio::task::JoinHandle;
15
16#[derive(Clone)]
20#[non_exhaustive]
21pub struct RunContext {
22 pub episodic: Arc<dyn EpisodicMemory>,
24 pub run_id: RunId,
26}
27
28impl RunContext {
29 #[must_use]
32 pub fn new(episodic: Arc<dyn EpisodicMemory>, run_id: RunId) -> Self {
33 Self { episodic, run_id }
34 }
35}
36
37tokio::task_local! {
38 static RUN_CTX: Option<RunContext>;
39}
40
41pub async fn scope_run_context<F, R>(ctx: Option<RunContext>, fut: F) -> R
44where
45 F: Future<Output = R>,
46{
47 RUN_CTX.scope(ctx, fut).await
48}
49
50#[must_use]
53pub fn current_run_context() -> Option<RunContext> {
54 RUN_CTX.try_with(|v| v.clone()).unwrap_or(None)
55}
56
57pub fn spawn_with_run_context<F>(fut: F) -> JoinHandle<F::Output>
61where
62 F: Future + Send + 'static,
63 F::Output: Send + 'static,
64{
65 let ctx = current_run_context();
66 tokio::spawn(async move { RUN_CTX.scope(ctx, fut).await })
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use klieo_core::ids::RunId;
73 use klieo_core::test_utils::InMemoryEpisodic;
74
75 fn make_episodic() -> Arc<dyn EpisodicMemory> {
76 Arc::new(InMemoryEpisodic::default())
77 }
78
79 #[tokio::test]
80 async fn current_run_context_returns_scoped_value() {
81 let ep = make_episodic();
82 let run_id = RunId::new();
83 let result = scope_run_context(
84 Some(RunContext {
85 episodic: ep.clone(),
86 run_id,
87 }),
88 async {
89 let ctx = current_run_context().expect("inside scope");
90 ctx.run_id
91 },
92 )
93 .await;
94 assert_eq!(result, run_id);
95 }
96
97 #[tokio::test]
98 async fn current_run_context_none_outside_scope() {
99 assert!(current_run_context().is_none());
100 }
101
102 #[tokio::test]
103 async fn spawn_with_run_context_inherits() {
104 let ep = make_episodic();
105 let run_id = RunId::new();
106 let inherited = scope_run_context(
107 Some(RunContext {
108 episodic: ep.clone(),
109 run_id,
110 }),
111 async move {
112 let handle =
113 spawn_with_run_context(async { current_run_context().expect("inherited") });
114 handle.await.unwrap()
115 },
116 )
117 .await;
118 assert_eq!(inherited.run_id, run_id);
119 }
120}