44//! as a replacement for Codex's native memory engine.
55
66use std:: path:: Path ;
7+ use std:: sync:: OnceLock ;
8+ use serde_json:: json;
79
810/// A placeholder adapter struct for agentmemory integration.
911#[ derive( Debug , Default , Clone ) ]
1012pub struct AgentmemoryAdapter {
1113 // Configuration and state will be added here in subsequent PRs.
1214}
1315
16+ /// A shared, pooled HTTP client for agentmemory interactions.
17+ /// Reusing the client allows connection pooling (keep-alive) for high throughput.
18+ static CLIENT : OnceLock < reqwest:: Client > = OnceLock :: new ( ) ;
19+
20+ fn get_client ( ) -> & ' static reqwest:: Client {
21+ CLIENT . get_or_init ( || {
22+ reqwest:: Client :: builder ( ) . build ( ) . unwrap_or_default ( )
23+ } )
24+ }
25+
1426impl AgentmemoryAdapter {
1527 pub fn new ( ) -> Self {
1628 Self :: default ( )
1729 }
1830
31+ fn api_base ( & self ) -> String {
32+ std:: env:: var ( "III_REST_PORT" )
33+ . map ( |p| format ! ( "http://localhost:{}" , p) )
34+ . unwrap_or_else ( |_| "http://localhost:3111" . to_string ( ) )
35+ }
36+
1937 /// Builds the developer instructions for startup memory injection
2038 /// using the `agentmemory` retrieval stack.
2139 ///
@@ -26,34 +44,78 @@ impl AgentmemoryAdapter {
2644 _codex_home : & Path ,
2745 _token_budget : usize ,
2846 ) -> Option < String > {
29- // TODO: Call agentmemory REST/MCP endpoints to fetch top-K results
30- // For now, return a placeholder instructions block.
31- Some (
32- "Use the `AgentMemory` tools to search and retrieve relevant memory.\n \
33- Your context is bounded; use targeted queries to expand details as needed."
34- . to_string ( ) ,
35- )
47+ let client = get_client ( ) ;
48+ let url = format ! ( "{}/agentmemory/profile" , self . api_base( ) ) ;
49+ let profile_result = client. get ( & url) . send ( ) . await ;
50+
51+ let mut instructions = "Use the `AgentMemory` tools to search and retrieve relevant memory.\n \
52+ Your context is bounded; use targeted queries to expand details as needed.". to_string ( ) ;
53+
54+ if let Ok ( res) = profile_result {
55+ if let Ok ( text) = res. text ( ) . await {
56+ if !text. is_empty ( ) {
57+ instructions. push_str ( "\n \n <agentmemory_profile>\n " ) ;
58+ instructions. push_str ( & text) ;
59+ instructions. push_str ( "\n </agentmemory_profile>" ) ;
60+ }
61+ }
62+ }
63+
64+ Some ( instructions)
65+ }
66+
67+ /// Transforms Codex's internal hook payloads into Claude-parity structures
68+ /// expected by the `agentmemory` REST API. This provides a central, malleable
69+ /// place to adjust mapping logic in the future without touching the hooks engine.
70+ fn format_claude_parity_payload ( & self , event_name : & str , payload : serde_json:: Value ) -> serde_json:: Value {
71+ // TODO: As agentmemory evolves, perform explicit property mapping here.
72+ // For example, mapping Codex `turn_id` to Claude `message_id` or extracting specific nested fields.
73+
74+ json ! ( {
75+ "event" : event_name,
76+ "payload" : payload,
77+ } )
3678 }
3779
3880 /// Asynchronously captures and stores lifecycle events in `agentmemory`.
3981 ///
4082 /// This method allows Codex hooks (like `SessionStart`, `PostToolUse`) to
4183 /// be transmitted without blocking the hot path of the shell or model output.
42- pub async fn capture_event < P : Send + ' static > ( & self , _event_name : & str , _payload : P ) {
43- // TODO: Transmit the event to agentmemory's ingestion endpoint.
44- // The payload will typically be a hook request (e.g. `PostToolUseRequest`).
45- // This is a stub for future PRs.
84+ pub async fn capture_event ( & self , event_name : & str , payload_json : serde_json:: Value ) {
85+ let url = format ! ( "{}/agentmemory/observe" , self . api_base( ) ) ;
86+ let client = get_client ( ) ;
87+
88+ let body = self . format_claude_parity_payload ( event_name, payload_json) ;
89+
90+ if let Err ( e) = client. post ( & url) . json ( & body) . send ( ) . await {
91+ // Log a warning instead of failing silently. This won't crash the session,
92+ // but will alert developers that memory observation is degraded.
93+ tracing:: warn!(
94+ "Agentmemory observation failed: could not send {} event to {}: {}" ,
95+ event_name, url, e
96+ ) ;
97+ }
4698 }
4799
48100 /// Asynchronously triggers a memory refresh/update operation in `agentmemory`.
49101 pub async fn update_memories ( & self ) -> Result < ( ) , String > {
50- // TODO: Call agentmemory's sync/refresh endpoint.
102+ let url = format ! ( "{}/agentmemory/consolidate" , self . api_base( ) ) ;
103+ let client = get_client ( ) ;
104+ let res = client. post ( & url) . send ( ) . await . map_err ( |e| e. to_string ( ) ) ?;
105+ if !res. status ( ) . is_success ( ) {
106+ return Err ( format ! ( "Consolidate failed with status {}" , res. status( ) ) ) ;
107+ }
51108 Ok ( ( ) )
52109 }
53110
54111 /// Asynchronously drops/clears the memory store in `agentmemory`.
55112 pub async fn drop_memories ( & self ) -> Result < ( ) , String > {
56- // TODO: Call agentmemory's clear/drop endpoint.
113+ let url = format ! ( "{}/agentmemory/forget" , self . api_base( ) ) ;
114+ let client = get_client ( ) ;
115+ let res = client. post ( & url) . json ( & json ! ( { "all" : true } ) ) . send ( ) . await . map_err ( |e| e. to_string ( ) ) ?;
116+ if !res. status ( ) . is_success ( ) {
117+ return Err ( format ! ( "Forget failed with status {}" , res. status( ) ) ) ;
118+ }
57119 Ok ( ( ) )
58120 }
59121}
0 commit comments