33//! This module provides the seam for integrating the `agentmemory` service
44//! as a replacement for Codex's native memory engine.
55
6+ mod observe_payload;
7+
8+ use crate :: agentmemory:: observe_payload:: build_observe_payload;
69use crate :: config:: types:: AgentmemoryConfig ;
710use crate :: config:: types:: MemoriesConfig ;
811use codex_git_utils:: get_git_repo_root;
@@ -81,28 +84,6 @@ pub(crate) fn workspace_project(cwd: &Path) -> PathBuf {
8184 get_git_repo_root ( cwd) . unwrap_or_else ( || cwd. to_path_buf ( ) )
8285}
8386
84- fn extract_project_and_cwd ( payload : & serde_json:: Value ) -> ( String , String ) {
85- let cwd = payload
86- . get ( "cwd" )
87- . and_then ( |value| value. as_str ( ) )
88- . map ( ToOwned :: to_owned)
89- . or_else ( || {
90- std:: env:: current_dir ( )
91- . ok ( )
92- . map ( |path| path. to_string_lossy ( ) . into_owned ( ) )
93- } )
94- . unwrap_or_default ( ) ;
95- let project = if cwd. is_empty ( ) {
96- String :: new ( )
97- } else {
98- get_git_repo_root ( Path :: new ( & cwd) )
99- . unwrap_or_else ( || Path :: new ( & cwd) . to_path_buf ( ) )
100- . to_string_lossy ( )
101- . into_owned ( )
102- } ;
103- ( project, cwd)
104- }
105-
10687impl AgentmemoryAdapter {
10788 pub fn new ( ) -> Self {
10889 Self :: default ( )
@@ -220,7 +201,6 @@ impl AgentmemoryAdapter {
220201 fn parse_structured_tool_input ( raw : & serde_json:: Value ) -> serde_json:: Value {
221202 if let Some ( s) = raw. as_str ( )
222203 && let Ok ( parsed) = serde_json:: from_str :: < serde_json:: Value > ( s)
223- && parsed. is_object ( )
224204 {
225205 return parsed;
226206 }
@@ -278,34 +258,6 @@ impl AgentmemoryAdapter {
278258 ( files, search_terms)
279259 }
280260
281- /// Transforms Codex's internal hook payloads into Claude-parity structures
282- /// expected by the `agentmemory` REST API. This provides a central, malleable
283- /// place to adjust mapping logic in the future without touching the hooks engine.
284- fn format_claude_parity_payload (
285- & self ,
286- event_name : & str ,
287- payload : serde_json:: Value ,
288- ) -> serde_json:: Value {
289- let session_id = payload
290- . get ( "session_id" )
291- . and_then ( |v| v. as_str ( ) )
292- . unwrap_or ( "unknown" )
293- . to_string ( ) ;
294- let ( project, cwd) = extract_project_and_cwd ( & payload) ;
295-
296- let timestamp = chrono:: Utc :: now ( ) . to_rfc3339 ( ) ;
297- let hook_type = normalize_hook_type ( event_name) ;
298-
299- json ! ( {
300- "sessionId" : session_id,
301- "hookType" : hook_type,
302- "project" : project,
303- "cwd" : cwd,
304- "timestamp" : timestamp,
305- "data" : payload,
306- } )
307- }
308-
309261 /// Asynchronously captures and stores lifecycle events in `agentmemory`.
310262 ///
311263 /// This method allows Codex hooks (like `SessionStart`, `PostToolUse`) to
@@ -317,7 +269,17 @@ impl AgentmemoryAdapter {
317269 memories : & MemoriesConfig ,
318270 ) {
319271 let url = format ! ( "{}/agentmemory/observe" , self . api_base( memories) ) ;
320- let body = self . format_claude_parity_payload ( event_name, payload_json) ;
272+ let body = match build_observe_payload ( event_name, payload_json) {
273+ Ok ( body) => body,
274+ Err ( err) => {
275+ tracing:: warn!(
276+ "Agentmemory observation skipped for unsupported or invalid {} payload: {}" ,
277+ event_name,
278+ err
279+ ) ;
280+ return ;
281+ }
282+ } ;
321283
322284 match self
323285 . request_builder ( reqwest:: Method :: POST , & url, memories)
@@ -844,23 +806,6 @@ impl AgentmemoryAdapter {
844806 Self :: json_or_error ( res) . await
845807 }
846808}
847- fn normalize_hook_type ( event_name : & str ) -> & str {
848- match event_name {
849- "SessionStart" => "session_start" ,
850- "UserPromptSubmit" => "prompt_submit" ,
851- "PreToolUse" => "pre_tool_use" ,
852- "PostToolUse" => "post_tool_use" ,
853- "PostToolUseFailure" => "post_tool_failure" ,
854- "AssistantResult" => "assistant_result" ,
855- "SubagentStart" => "subagent_start" ,
856- "SubagentStop" => "subagent_stop" ,
857- "Stop" => "stop" ,
858- "Notification" => "notification" ,
859- "TaskCompleted" => "task_completed" ,
860- "SessionEnd" => "session_end" ,
861- _ => event_name,
862- }
863- }
864809#[ cfg( test) ]
865810#[ allow( clippy:: await_holding_lock) ]
866811mod tests {
@@ -934,28 +879,6 @@ mod tests {
934879 assert ! ( instructions. contains( "Do not call memory tools on every turn" ) ) ;
935880 }
936881
937- #[ test]
938- fn format_claude_parity_payload_normalizes_codex_hook_names ( ) {
939- let adapter = AgentmemoryAdapter :: new ( ) ;
940- let payload = json ! ( { "session_id" : "session-123" } ) ;
941-
942- let prompt_submit =
943- adapter. format_claude_parity_payload ( "UserPromptSubmit" , payload. clone ( ) ) ;
944- assert_eq ! ( prompt_submit[ "hookType" ] , json!( "prompt_submit" ) ) ;
945- assert_eq ! ( prompt_submit[ "sessionId" ] , json!( "session-123" ) ) ;
946-
947- let post_tool_failure =
948- adapter. format_claude_parity_payload ( "PostToolUseFailure" , payload. clone ( ) ) ;
949- assert_eq ! ( post_tool_failure[ "hookType" ] , json!( "post_tool_failure" ) ) ;
950-
951- let stop = adapter. format_claude_parity_payload ( "Stop" , payload) ;
952- assert_eq ! ( stop[ "hookType" ] , json!( "stop" ) ) ;
953-
954- let session_end = adapter
955- . format_claude_parity_payload ( "SessionEnd" , json ! ( { "session_id" : "session-1" } ) ) ;
956- assert_eq ! ( session_end[ "hookType" ] , json!( "session_end" ) ) ;
957- }
958-
959882 #[ tokio:: test]
960883 #[ serial_test:: serial( agentmemory_env) ]
961884 async fn update_memories_returns_consolidate_payload ( ) {
@@ -1216,26 +1139,6 @@ mod tests {
12161139 ) ;
12171140 }
12181141
1219- #[ test]
1220- fn test_format_claude_parity_payload ( ) {
1221- let adapter = AgentmemoryAdapter :: new ( ) ;
1222- let raw_payload = json ! ( {
1223- "session_id" : "1234" ,
1224- "turn_id" : "turn-5" ,
1225- "cwd" : "/tmp/project" ,
1226- "command" : "echo hello"
1227- } ) ;
1228-
1229- let formatted = adapter. format_claude_parity_payload ( "PreToolUse" , raw_payload. clone ( ) ) ;
1230-
1231- assert_eq ! ( formatted[ "sessionId" ] , "1234" ) ;
1232- assert_eq ! ( formatted[ "hookType" ] , "pre_tool_use" ) ;
1233- assert_eq ! ( formatted[ "project" ] , "/tmp/project" ) ;
1234- assert_eq ! ( formatted[ "cwd" ] , "/tmp/project" ) ;
1235- assert ! ( formatted. get( "timestamp" ) . is_some( ) ) ;
1236- assert_eq ! ( formatted[ "data" ] , raw_payload) ;
1237- }
1238-
12391142 #[ tokio:: test]
12401143 #[ serial_test:: serial( agentmemory_env) ]
12411144 async fn refresh_context_posts_query_aware_payload ( ) {
@@ -1286,6 +1189,51 @@ mod tests {
12861189 ) ;
12871190 }
12881191
1192+ #[ tokio:: test]
1193+ #[ serial_test:: serial( agentmemory_env) ]
1194+ async fn recall_context_posts_query_aware_payload ( ) {
1195+ let server = MockServer :: start ( ) . await ;
1196+ let adapter = AgentmemoryAdapter :: new ( ) ;
1197+ let _guard = ENV_LOCK . lock ( ) . expect ( "lock env" ) ;
1198+ let _url_guard = EnvVarGuard :: set ( "AGENTMEMORY_URL" , "" ) ;
1199+ let memories = test_memories ( & server) ;
1200+
1201+ Mock :: given ( method ( "POST" ) )
1202+ . and ( path ( "/agentmemory/context" ) )
1203+ . respond_with ( ResponseTemplate :: new ( 200 ) . set_body_json ( json ! ( {
1204+ "context" : "<agentmemory-context>recall</agentmemory-context>" ,
1205+ } ) ) )
1206+ . expect ( 1 )
1207+ . mount ( & server)
1208+ . await ;
1209+
1210+ let context = adapter
1211+ . recall_context (
1212+ "session-1" ,
1213+ Path :: new ( "/tmp/project" ) ,
1214+ Some ( "debug agentmemory recall semantics" ) ,
1215+ DEFAULT_RUNTIME_RECALL_TOKEN_BUDGET ,
1216+ & memories,
1217+ )
1218+ . await
1219+ . expect ( "recall context should succeed" ) ;
1220+
1221+ assert_eq ! ( context, "<agentmemory-context>recall</agentmemory-context>" ) ;
1222+
1223+ let requests = server. received_requests ( ) . await . unwrap_or_default ( ) ;
1224+ let body = serde_json:: from_slice :: < serde_json:: Value > ( & requests[ 0 ] . body )
1225+ . expect ( "recall request body should be json" ) ;
1226+ assert_eq ! (
1227+ body,
1228+ json!( {
1229+ "sessionId" : "session-1" ,
1230+ "project" : "/tmp/project" ,
1231+ "budget" : DEFAULT_RUNTIME_RECALL_TOKEN_BUDGET ,
1232+ "query" : "debug agentmemory recall semantics" ,
1233+ } )
1234+ ) ;
1235+ }
1236+
12891237 #[ tokio:: test]
12901238 #[ serial_test:: serial( agentmemory_env) ]
12911239 async fn remember_memory_posts_content_and_returns_json ( ) {
0 commit comments