
    )di;                         d Z ddlZddlZddlZddlZddlmZmZ ddlmZ ddl	m
Z
mZ ddlmZ ddlmZ ddlmZ dd	lmZmZ  ej,                  e      Z ed
      Zedz  dz  ZdZdZ G d d      Zy)u  
GeminiAgent — Persistent Gemini Agent with Session Memory and CTM Hooks
========================================================================
A stateful Gemini agent that:
  - Maintains conversation history across turns
  - Persists session state to JSONL files (E: drive)
  - Extracts and commits [CTM] blocks to the Knowledge Graph
  - Tracks token usage and compacts history when approaching context limits

Uses google.genai (new SDK, NOT deprecated google.generativeai).

Author: Genesis Parallel Builder
Created: 2026-02-26
    N)datetimetimezone)Path)AnyOptional)uuid4)genai)types)	ctm_to_kgprocess_ctm_blocksz/mnt/e/genesis-systemdatagcc_sessionsi 5    c                       e Zd ZdZ	 	 	 ddedededee   dedee   d	dfd
Zded	efdZde	e
   d	efdZd	e	e
   fdZded	efdZded	dfdZddZddeded	e
fdZd	efdZd	efdZd	e
fdZd	efdZy)GeminiAgenta1  
    A persistent Gemini agent with session memory and CTM hooks.

    Conversation history is stored as a list of dicts matching the
    google.genai Content format:
        {"role": "user" | "model", "parts": [{"text": "..."}]}

    Session state is persisted to a JSONL file in data/gcc_sessions/.
    Nnamemodelsystem_promptmemory_filemax_historyapi_keyreturnc                 v   || _         || _        || _        || _        t	        t                     | _        t        j                  t        j                        | _        d| _        g | _        g | _        d| _        d| _        d| _        t$        j'                  dd       |rt)        |      | _        nt$        | dz  | _        |xs# t,        j.                  j1                  d      xs d}t3        j4                  |      | _        t8        j;                  d| j                   | j                  | j                         y	)
a[  
        Initialise a GeminiAgent.

        Args:
            name: Short identifier (e.g. "orchestrator", "builder").
            model: Gemini model ID (e.g. "gemini-3.1-pro-preview").
            system_prompt: System-level instruction injected each chat.
            memory_file: Path to JSONL session file. Auto-generated if None.
            max_history: Maximum conversation turns to retain before
                         compaction consideration (soft limit).
            api_key: Gemini API key. Falls back to GEMINI_API_KEY env var,
                     then the Genesis default key.
        r   Tparentsexist_okz_session.jsonlGEMINI_API_KEY'AIzaSyCT_rx0NusUJWoqtT7uxHAKEfHo129SJb8)r   u3   GeminiAgent[%s] initialised — model=%s session=%sN)r   r   r   r   strr   
session_idr   nowr   utc
created_at
turn_count
ctm_bufferhistorytotal_prompt_tokenstotal_completion_tokensestimated_context_tokensGCC_SESSIONS_DIRmkdirr   r   osenvirongetr	   Clientclientloggerinfo)selfr   r   r   r   r   r   resolved_keys           :/mnt/e/genesis-system/core/gemini_command_centres/agent.py__init__zGeminiAgent.__init__8   s   , 	
*&eg,",,x||4&( $& $% '($()% 	td;#K0D/TF.2IID  9zz~~./98 	
 ll<8AIIJJOO		
    messagec                   K   | j                   j                  dd|igd       | j                         }	 t        j                  | j
                  |       d{   }| j                  |      }| j                   j                  dd|igd       | xj                  dz  c_        | j                  |       t        || j                  	      }|rE| j                  j!                  |       t        j#                  d
| j                  t%        |             | j&                  t(        k\  r| j+                          |S 7 # t        $ rA}t        j                  d| j                  |       | j                   j                           d}~ww xY ww)a  
        Send a user message and receive a model response.

        Automatically:
        - Appends message to history
        - Calls Gemini API with full history + system prompt
        - Extracts [CTM] blocks from response and writes to KG
        - Updates token counters
        - Triggers history compaction if context is near limit

        Args:
            message: User message text.

        Returns:
            Model response text.
        usertextroleparts)contentsNzGeminiAgent[%s] API error: %sr      )
agent_namez2GeminiAgent[%s] CTM: %d blocks extracted and saved)r&   append_build_contentsasyncio	to_thread	_call_api	Exceptionr1   errorr   pop_extract_textr$   _update_token_countsr   r%   extendr2   lenr)   COMPACTION_TOKEN_THRESHOLD_compact_history)r3   r8   r?   responseexcresponse_textctm_resultss          r5   chatzGeminiAgent.chatw   sQ    $ 	V7H6IJK '')		$..! H **84 	W8O7PQR1!!(+ )499MOO"";/KKD		K  ((,FF!!#A  	LL8$))SILL		s;   2F$D; D9D; CF9D; ;	F<F  FFr?   c                     t        j                  | j                        }| j                  j                  j                  | j                  ||      }|S )z
        Synchronous Gemini API call (run inside asyncio.to_thread).

        Args:
            contents: List of Content dicts for the conversation.

        Returns:
            GenerateContentResponse object.
        )system_instruction)r   r?   config)genai_typesGenerateContentConfigr   r0   modelsgenerate_contentr   )r3   r?   rW   rP   s       r5   rF   zGeminiAgent._call_api   sR     22#11
 ;;%%66** 7 

 r7   c                 ,    t        | j                        S )z}
        Build the contents list from current history.
        The history already includes the latest user message.
        )listr&   r3   s    r5   rC   zGeminiAgent._build_contents   s    
 DLL!!r7   rP   c                     	 |j                   S # t        $ r Y nw xY w	 |j                  d   j                  j                  d   j                   S # t        t
        t        f$ r t        |      cY S w xY w)z,Extract text from a GenerateContentResponse.r   )r;   AttributeError
candidatescontentr>   
IndexError	TypeErrorr   )r3   rP   s     r5   rJ   zGeminiAgent._extract_text   sp    	==  			!&&q)1177:???
I6 	!x= 	!s    	/A A0/A0c                 D   	 |j                   }| xj                  t        |dd      xs dz  c_        | xj                  t        |dd      xs dz  c_        t        |dd      xs d| _        y# t
        $ r) t        d | j                  D              }|dz  | _        Y yw xY w)z3Update token counters from response usage_metadata.prompt_token_countr   candidates_token_countc              3   D   K   | ]  }t        |d    d   d           yw)r>   r   r;   N)rM   ).0ms     r5   	<genexpr>z3GeminiAgent._update_token_counts.<locals>.<genexpr>   s'      /0AgJqM&)*s       N)usage_metadatar'   getattrr(   r)   r`   sumr&   )r3   rP   usagetotal_charss       r5   rK   z GeminiAgent._update_token_counts   s    	=++E$$7KQ(O(TSTT$((7;@q(
 3Q7<1 )  	= 48LL K -81,<D)	=s   A*A- -/BBc                    t         dz  }t        | j                        |k  ryt        | j                        |z
  }| j                  | d | _        d| d| j                  t         z
   d}| j                  j	                  ddd|igd	       | j                  j	                  d
dddigd	       d| _        t        j                  d| j                  |t        | j                               y)a  
        Compact history when approaching context limit.

        Keeps the most recent COMPACTION_KEEP_TURNS conversation turns
        (each turn = 1 user msg + 1 model msg = 2 entries).
        Prepends a synthetic summary entry noting the compaction.
           Nz[CONTEXT COMPACTED: z0 earlier messages removed. Continuing from turn z+]r   r:   r;   r<   r@   r   z,Understood. Resuming from compacted context.uB   GeminiAgent[%s] history compacted — dropped %d messages, kept %d)	COMPACTION_KEEP_TURNSrM   r&   r$   insertr)   r1   r2   r   )r3   keep_messagesdroppedsummary_msgs       r5   rO   zGeminiAgent._compact_history   s     .1t||-dll#m3||]NO4 #7) ,$$(OO6K$K#LBP 	 	A6;:O9PQR!#QRS	
 )*%PII		
r7   rb   categoryc                 l    t        | j                  ||      }| j                  j                  |       |S )z
        Explicitly commit content to the Knowledge Graph.

        Args:
            content: Insight or fact to persist.
            category: "entity" or "axiom".

        Returns:
            Write result dict from ctm_to_kg().
        )rA   rb   ry   )r   r   r%   rB   )r3   rb   ry   results       r5   ctmzGeminiAgent.ctm  s4     yy

 	v&r7   c                    	 | j                   | j                  | j                  | j                  j	                         t        j                  t        j                        j	                         | j                  | j                  | j                  | j                  | j                  d
}| j                  j                  j!                  dd       t#        | j                  dd      5 }|j%                  t'        j(                  |      dz          ddd       t*        j-                  d	| j                  | j                         y# 1 sw Y   5xY w# t.        $ r+}t*        j1                  d
| j                  |       Y d}~yd}~ww xY w)z
        Persist session state to memory_file (JSONL format).

        The file contains a single JSON object (last line wins for loading).

        Returns:
            True on success, False on error.
        )
r    r   r   r#   saved_atr$   r&   r'   r(   r)   Tr   wutf-8encoding
Nz#GeminiAgent[%s] session saved to %sz&GeminiAgent[%s] save_session error: %sF)r    r   r   r#   	isoformatr   r!   r   r"   r$   r&   r'   r(   r)   r   parentr+   openwritejsondumpsr1   debugrG   rH   )r3   statefhrQ   s       r5   save_sessionzGeminiAgent.save_session(  s    	"oo		"oo779$LL6@@B"oo<<'+'?'?+/+G+G,0,I,IE ##))$)Fd&&g> 3"E*T123LL>		4K[K[\3 3  	LLA499cR	s0   C#E %(E3E E
E 	F!E<<Fc                    | j                   j                         s,t        j                  d| j                  | j                          y	 d}t        | j                   dd      5 }|D ]  }|j                         }|s|} 	 ddd       |syt        j                  |      }|j                  d| j                        | _
        |j                  d	d
      | _        |j                  dg       | _        |j                  dd
      | _        |j                  dd
      | _        |j                  dd
      | _        t        j                  d| j                  | j                  t!        | j                               y# 1 sw Y   xY w# t        j"                  t$        f$ r+}t        j'                  d| j                  |       Y d}~yd}~ww xY w)a  
        Resume from last saved session state.

        Reads the most recent JSON object in memory_file and restores
        history, turn_count, and token counters.

        Returns:
            True if session was loaded, False if file does not exist or parse error.
        u9   GeminiAgent[%s] no saved session at %s — starting freshF rr   r   Nr    r$   r   r&   r'   r(   r)   u@   GeminiAgent[%s] session loaded — %d turns, %d history messagesTu?   GeminiAgent[%s] load_session parse error: %s — starting fresh)r   existsr1   r2   r   r   stripr   loadsr.   r    r$   r&   r'   r(   r)   rM   JSONDecodeErrorKeyErrorwarning)r3   	last_liner   liner   rQ   s         r5   load_sessionzGeminiAgent.load_sessionG  s    &&(KKK		  
 !	Id&&g> )" )D::<D$(	)) JJy)E#iidooFDO#iia8DO 99Y3DL',yy1F'JD$+0995NPQ+RD(,1II6PRS,TD)KKR		DLL!	 /) )0 $$h/ 	NNQ		
 	s=   F  "E4;E4 
F  C(F  4E=9F   G!F??Gc                    d}|dkD  r| j                   |z  dz  nd}| j                  | j                  | j                  | j                  t        | j                        | j                  | j                  | j                   |t        |d      d
S )z
        Return token usage stats for this agent.

        Returns:
            dict with token counts and context utilisation percentage.
        i   r   d   g        rs   )
agentr   r    r$   history_messagesr'   r(   r)   max_context_tokensutilisation_pct)
r)   r   r   r    r$   rM   r&   r'   r(   round)r3   
max_tokensr   s      r5   get_context_usagezGeminiAgent.get_context_usage}  s     
 A~ **Z73> 	 YYZZ//// #DLL 1#'#;#;'+'C'C(,(E(E",$_a8
 	
r7   c           
          d| j                   d| j                  d| j                   dt        | j                         d	S )NzGeminiAgent(name=z, model=z, turns=z
, history=))r   r   r$   rM   r&   r^   s    r5   __repr__zGeminiAgent.__repr__  sD    		}HTZZN C__%ZDLL0A/B!E	
r7   )N2   N)r   N)entity)__name__
__module____qualname____doc__r   r   intr6   rT   r]   dictr   rF   rC   rJ   rK   rO   r|   boolr   r   r   r    r7   r5   r   r   -   s    &*!%;
;
 ;
 	;

 c];
 ;
 #;
 
;
~8# 8# 8t$t*  ("d "
!c 
!c 
!=S =T =*"
L3 # T *d >2d 2l
4 
4
# 
r7   r   )r   rD   r   loggingr,   r   r   pathlibr   typingr   r   uuidr   googler	   google.genair
   rX   core.gemini_command_centres.ctmr   r   	getLoggerr   r1   GENESIS_ROOTr*   rN   rt   r   r   r7   r5   <module>r      sv       	 '      - I			8	$ +,&(>9 
 %  n
 n
r7   