Skip to main content

agent_framework_core/
memory.rs

1//! Context / memory providers.
2//!
3//! Rust equivalent of `agent_framework._memory`. A [`ContextProvider`] injects
4//! extra instructions, messages, and tools into an agent invocation without
5//! persisting them to the conversation history.
6//!
7//! Upstream renamed `ContextProvider.invoking`/`invoked` to `before_run`/
8//! `after_run` and removed the `thread_created` hook entirely; `before_run`
9//! mutates a [`SessionContext`] in place instead of returning a value. There
10//! is no aggregate wrapper any more — consumers hold a
11//! `Vec<Arc<dyn ContextProvider>>` and iterate it directly.
12
13use async_trait::async_trait;
14
15use crate::error::{Error, Result};
16use crate::tools::ToolDefinition;
17use crate::types::Message;
18
19/// Per-invocation context a provider contributes to a run. Providers mutate
20/// this in place in before_run. Rust equivalent of upstream SessionContext.
21#[derive(Debug, Clone, Default)]
22pub struct SessionContext {
23    /// Local session identifier (from the thread), for provider scoping.
24    pub session_id: Option<String>,
25    /// Service-managed session/conversation id, when applicable.
26    pub service_session_id: Option<String>,
27    /// The run's input messages (read-only for providers).
28    pub input_messages: Vec<Message>,
29    /// Extra system instructions to inject (providers append via add_instructions).
30    pub instructions: Option<String>,
31    /// Extra context messages to inject ahead of history.
32    pub messages: Vec<Message>,
33    /// Extra tools to make available for this run.
34    pub tools: Vec<ToolDefinition>,
35}
36
37impl SessionContext {
38    pub fn new(input_messages: Vec<Message>) -> Self {
39        Self {
40            input_messages,
41            ..Default::default()
42        }
43    }
44    /// Append instructions, newline-concatenating with any already present.
45    pub fn add_instructions(&mut self, s: impl Into<String>) {
46        let s = s.into();
47        self.instructions = match self.instructions.take() {
48            Some(existing) => Some(format!("{existing}\n{s}")),
49            None => Some(s),
50        };
51    }
52}
53
54/// A source of per-invocation context (memory, RAG, etc.).
55/// Upstream renamed invoking/invoked -> before_run/after_run and REMOVED
56/// thread_created. before_run mutates the SessionContext in place instead of
57/// returning a Context.
58#[async_trait]
59pub trait ContextProvider: Send + Sync {
60    /// Called before the model is invoked; mutate ctx to inject instructions,
61    /// messages, and/or tools. Read ctx.input_messages / ctx.session_id.
62    async fn before_run(&self, ctx: &mut SessionContext) -> Result<()>;
63
64    /// Called after an invocation completes, on BOTH success and failure.
65    /// On success, error is None and response_messages holds the output.
66    /// On failure, error is Some and response_messages is empty.
67    async fn after_run(
68        &self,
69        _request_messages: &[Message],
70        _response_messages: &[Message],
71        _error: Option<&Error>,
72    ) -> Result<()> {
73        Ok(())
74    }
75
76    /// Whether this provider manages conversation history (a
77    /// [`HistoryProvider`](crate::history::HistoryProvider)). [`Agent`](crate::agent::Agent)
78    /// and [`WorkflowAgent`](crate::workflow::WorkflowAgent) use this to
79    /// detect an already-attached history provider among a session's
80    /// `context_providers` and avoid auto-attaching a redundant
81    /// [`InMemoryHistoryProvider`](crate::history::InMemoryHistoryProvider).
82    /// Defaults to `false`; history providers override it to `true`.
83    fn is_history_provider(&self) -> bool {
84        false
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn add_instructions_sets_when_none() {
94        let mut ctx = SessionContext::new(vec![]);
95        assert!(ctx.instructions.is_none());
96        ctx.add_instructions("be brief");
97        assert_eq!(ctx.instructions.as_deref(), Some("be brief"));
98    }
99
100    #[test]
101    fn add_instructions_newline_concatenates() {
102        let mut ctx = SessionContext::new(vec![]);
103        ctx.add_instructions("first");
104        ctx.add_instructions("second");
105        ctx.add_instructions("third");
106        assert_eq!(ctx.instructions.as_deref(), Some("first\nsecond\nthird"));
107    }
108
109    #[test]
110    fn new_sets_input_messages_and_defaults_rest() {
111        let messages = vec![Message::user("hi")];
112        let ctx = SessionContext::new(messages.clone());
113        assert_eq!(ctx.input_messages.len(), messages.len());
114        assert_eq!(ctx.input_messages[0].text(), "hi");
115        assert!(ctx.session_id.is_none());
116        assert!(ctx.service_session_id.is_none());
117        assert!(ctx.instructions.is_none());
118        assert!(ctx.messages.is_empty());
119        assert!(ctx.tools.is_empty());
120    }
121}