
    `di<                     8   d Z ddlZddlZddlZddl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  ej(                  e      Z ed      Zed	z  d
z  Zed	z  dz  Zed	z  dz  ZdZdZdZdZdZ G d d      Z de!defdZ"de#ddfdZ$y)u  
GeminiDaemon — Persistent Daemon Runner for a Single Command Centre
====================================================================
Runs a GeminiAgent as a 24/7 daemon with:
  - JSONL task queue (pop-and-process)
  - Heartbeat file (written every 60 s)
  - Result files per task
  - Auto-save after every task
  - Error retry with exponential back-off
  - Graceful shutdown on SIGTERM / SIGINT

Author: Genesis Parallel Builder
Created: 2026-02-26
    N)datetimetimezone)Path)AnyOptional)uuid4)GeminiAgentz/mnt/e/genesis-systemdatagcc_heartbeatsgcc_resultsgcc_task_queues   <   g       @c                       e Zd ZdZdedefdedee   de	de
de	d	dfd
Zde	ded	dfdZddZded	dfdZdeded	dfdZdded	dfdZd	ee   fdZ	 	 ddededede
dee   d	dfdZy)GeminiDaemonaK  
    Runs a GeminiAgent as a persistent daemon with watchdog-compatible heartbeat.

    Task queue format (one JSON object per line in task_queue_file):
        {"id": "task-uuid", "prompt": "do X", "priority": 1, "created_at": "..."}

    Result files (one JSON per task, written to data/gcc_results/):
        {task_id}.json
    NTagenttask_queue_fileheartbeat_intervalrespawn_on_crashmax_retriesreturnc                    || _         || _        || _        || _        d| _        t
        j                  dd       t        j                  dd       t        j                  dd       |rt        |      | _
        nt        |j                   dz  | _
        | j                  j                  d       d| _        t        j                  t         j"                        | _        d| _        d| _        t+        j*                  t*        j,                  | j.                         t+        j*                  t*        j0                  | j.                         y)a  
        Initialise a GeminiDaemon.

        Args:
            agent: The GeminiAgent this daemon wraps.
            task_queue_file: Path to JSONL task queue.
                             Defaults to data/gcc_task_queues/{agent.name}.jsonl
            heartbeat_interval: Seconds between heartbeat writes.
            respawn_on_crash: Whether to continue looping after unhandled exceptions.
            max_retries: Maximum retries per individual task before skip.
        Tparentsexist_okz.jsonl)r           r   N)r   r   r   r   runningGCC_HEARTBEATS_DIRmkdirGCC_RESULTS_DIRGCC_TASK_QUEUES_DIRr   
task_queuenametouch_last_heartbeatr   nowr   utc_start_timetasks_processedtasks_failedsignalSIGTERM_signal_handlerSIGINT)selfr   r   r   r   r   s         ;/mnt/e/genesis-system/core/gemini_command_centres/daemon.py__init__zGeminiDaemon.__init__;   s   & 
"4 0& 	   =dT:!!$!> "?3DO1uzzl&4IIDO 	t, '*#<<5  ! 	fnnd&:&:;fmmT%9%9:    signumframec                 h    t         j                  d| j                  j                  |       d| _        y)z.Handle SIGTERM / SIGINT for graceful shutdown.u@   GeminiDaemon[%s] received signal %d — shutting down gracefullyFN)loggerinfor   r#   r   )r/   r3   r4   s      r0   r-   zGeminiDaemon._signal_handlern   s(    NJJOO	

 r2   c                   K   | j                   j                          | j                          t        j	                  d| j                   j
                  | j                   j                  | j                         | j                  r	 | j                         }|r| j                  |       d{    n!t        j                  t               d{    t        j                         }|| j                   z
  | j"                  k\  r| j                          | j                   j%                          | j                  r| j                   j%                          | j                  d	       t        j	                  d
| j                   j
                         y7 7 # t        j&                  $ r- t        j	                  d| j                   j
                         Y t(        $ rg}t        j+                  d| j                   j
                  |d       | j,                  sY d}~t        j                  d       d{  7   Y d}~d}~ww xY ww)aP  
        Main daemon event loop.

        1. Load previous session state.
        2. Write initial heartbeat.
        3. Poll task queue indefinitely.
        4. On task: call agent.chat(), write result, save session.
        5. On idle: sleep POLL_INTERVAL_SECONDS.
        6. Write heartbeat every HEARTBEAT_INTERVAL_SECONDS.
        u.   GeminiDaemon[%s] started — model=%s queue=%sNzGeminiDaemon[%s] cancelledz2GeminiDaemon[%s] unexpected error in main loop: %sT)exc_info
   stopped)statusz"GeminiDaemon[%s] shutdown complete)r   load_session
_heartbeatr6   r7   r#   modelr"   r   	_pop_task_process_taskasynciosleepPOLL_INTERVAL_SECONDStime	monotonicr%   r   save_sessionCancelledError	Exceptionerrorr   )r/   taskr&   excs       r0   runzGeminiDaemon.runy   s     	

!<JJOOJJOO		
 ll(~~',,T222!--(=>>> nn&---1H1HHOO%

'') ll> 	

!y)8$**//J; 3> )) 8$**//J 
(HJJOO!	   ,,mmB'''
(s   BI&F *F+!F FAF +I8AIF F =III9H<IH<0H31H<6I<IIrK   c           	        K   |j                  dt        t                           }|j                  dd      }d}t        }|| j                  k  r	 t
        j                  d| j                  j                  ||dz          | j                  j                  |       d{   }| j                  |||d	       | xj                  dz  c_        yyy7 1# t        $ r}|dz  }t
        j                  d
| j                  j                  ||| j                  dz   |       || j                  kD  r| j                  ||       Y d}~yt        j                   |       d{  7   |t"        z  }Y d}~nd}~ww xY w|| j                  k  r6w)z
        Process a single task with retry logic.

        Args:
            task: Task dict with at minimum {"id": ..., "prompt": ...}.
        idprompt r   z0GeminiDaemon[%s] processing task %s (attempt %d)   NT)successz3GeminiDaemon[%s] task %s failed (attempt %d/%d): %s)getstrr   INITIAL_RETRY_DELAYr   r6   r7   r   r#   chat_write_resultr)   rI   warning_handle_errorrB   rC   RETRY_BACKOFF_FACTOR)r/   rK   task_idrP   retriesdelayresult_textrL   s           r0   rA   zGeminiDaemon._process_task   sX     ((4UW.(B'#))).FJJOOaK	 %)JJOOF$;;""7Kt"L$$)$ * <
  .1IJJOO$$q( T---&&tS1mmE***--. )))s\   AFAC C.C FC 	E1A!E,;F E,EE,'F,E11FrL   c           	          |j                  dd      }t        j                  d| j                  j                  ||       | j                  |t        |      |dt        |             | xj                  dz  c_        y)z
        Handle a task that has exhausted all retries.

        Writes an error result file and increments the failure counter.
        rO   unknownz5GeminiDaemon[%s] task %s failed after max retries: %sF)rS   rJ   rR   N)rT   r6   rJ   r   r#   rX   rU   r*   )r/   rK   rL   r\   s       r0   rZ   zGeminiDaemon._handle_error   ss     ((4+CJJOO		
 	Hc( 	 	
 	Qr2   r<   c                    t         | j                  j                   dz  }| j                  j                         }| j                  j                  | j                  j                  |t        j                  t        j                        j                         | j                  j                  | j                  j                  | j                  | j                  |j                  dd      t        j                  t        j                        | j                  z
  j!                         t#        | j$                        d}	 t'        |dd      5 }t)        j*                  ||d	       d
d
d
       t-        j.                         | _        t2        j5                  d| j                  j                         y
# 1 sw Y   MxY w# t6        $ r5}t2        j9                  d| j                  j                  |       Y d
}~y
d
}~ww xY w)z
        Write a heartbeat JSON file to data/gcc_heartbeats/{name}.json.

        The watchdog uses this file's mtime to detect stale agents.

        Args:
            status: Current status string ("running" | "stopped" | "error").
        .jsonutilisation_pctr   )r   r?   r<   	timestamp
session_id
turn_countr)   r*   context_utilisation_pctuptime_seconds
queue_filewutf-8encoding   indentNz"GeminiDaemon[%s] heartbeat writtenz+GeminiDaemon[%s] heartbeat write failed: %s)r   r   r#   get_context_usager?   r   r&   r   r'   	isoformatrf   rg   r)   r*   rT   r(   total_secondsrU   r"   openjsondumprE   rF   r%   r6   debugOSErrorrJ   )r/   r<   hb_filecontext_usage	heartbeatfhrL   s          r0   r>   zGeminiDaemon._heartbeat   si    %$**//):%'@@

446 ZZ__ZZ%%!hll3==?**//**//#33 --'4'8'89JC'PX\\*T-=-==modoo.
	 
	gsW5 3		)R23#'>>#3D LL=tzzO3 3  	LL=

 	s1   <F; 
F/#AF; /F84F; ;	G9+G44G9c                    | j                   j                         r'| j                   j                         j                  dk(  ryd}g }	 t	        | j                   dd      5 }t        j                  |t
        j                         	 |j                         }|D ]?  }|j                         }|s|	 t        j                  |      }/|j#                  |       A |<|j%                  d       |j'                          |D ]  }|j)                  |dz           t        j                  |t
        j*                         	 ddd       |S # t        j                  $ r5}t        j                  d| j                  j                   |       Y d}~d}~ww xY w# t        j                  |t
        j*                         w xY w# 1 sw Y   |S xY w# t,        $ r5}t        j/                  d| j                  j                   |       Y d}~yd}~ww xY w)	z
        Atomically read and remove the first task from the queue file.

        Uses file locking to prevent race conditions with concurrent writers.

        Returns:
            Task dict or None if the queue is empty / unreadable.
        r   Nzr+rl   rm   z2GeminiDaemon[%s] skipping malformed queue line: %s
z%GeminiDaemon[%s] queue read error: %s)r"   existsstatst_sizeru   fcntlflockLOCK_EX	readlinesstriprv   loadsJSONDecodeErrorr6   rY   r   r#   appendseektruncatewriteLOCK_UNry   rJ   )r/   rK   remaining_linesr}   lineslinerL   	remainings           r0   r@   zGeminiDaemon._pop_task  s    %%'4??+?+?+A+I+IQ+N#%'#	dootg> 3"B.3LLNE % 9#zz|#$<"'+zz$'7 ,22489  '
)8 7IHHY%567 KKEMM293H 1 $(#7#7 " &$X$(JJOO$'!" !""  KKEMM293H   	LL7# 		sy   G  %F;*F1EAF$F;=G F+F
F
FF&F88F;;G G G 	H+HHr\   resultrS   rJ   c           
         t         j                  dd       t         | dz  }|| j                  j                  | j                  j                  ||||t        j                  t        j                        j                         | j                  j                  d	}	 t        |dd      5 }t        j                  ||d	       d
d
d
       t        j                  d| j                  j                  |j                  |       y
# 1 sw Y   @xY w# t         $ r5}	t        j#                  d| j                  j                  |	       Y d
}	~	y
d
}	~	ww xY w)aV  
        Write task result to data/gcc_results/{task_id}.json.

        Args:
            task_id: Task identifier (used as filename).
            result: Result text from the agent.
            task: Original task dict.
            success: Whether the task completed successfully.
            error: Error message if success=False.
        Tr   rc   )	r\   r   r?   rS   r   rJ   original_taskcompleted_atrg   rk   rl   rm   ro   rp   Nz0GeminiDaemon[%s] result written: %s (success=%s)z0GeminiDaemon[%s] failed to write result file: %s)r    r   r   r#   r?   r   r&   r   r'   rs   rg   ru   rv   rw   r6   r7   ry   rJ   )
r/   r\   r   rK   rS   rJ   result_filepayloadr}   rL   s
             r0   rX   zGeminiDaemon._write_resultT  s   $ 	dT:%7)5(99 ZZ__ZZ%%!$LL6@@B**//

	k39 1R		'2a01KKB

  	1 1  	LLBDJJOOUX 	s0   D *D>D DD 	E+EE)r   N)r   )TN)__name__
__module____qualname____doc__HEARTBEAT_INTERVAL_SECONDSMAX_RETRIESr	   r   rU   intboolr1   r   r-   rM   dictrA   rI   rZ   r>   r@   rX    r2   r0   r   r   0   s    *."<!%&1;1; "#1;  	1;
 1; 1; 
1;fc # $ 6Kp'. '. '.R$ Y 4 0& &T &T48D> 4z #-- - 	-
 - }- 
-r2   r   configr   c                     ddl m} | d   t        fd|D        d      }|st        d      t	        |d   |d   |d   	      S )
z.Build a GeminiAgent from a centre config dict.r   CENTRES_CONFIGr#   c              3   4   K   | ]  }|d    k(  s|  ywr#   Nr   .0cr#   s     r0   	<genexpr>z+_build_agent_from_config.<locals>.<genexpr>  s     B&	T0A1B   NzUnknown centre name: r?   system_promptr#   r?   r   )$core.gemini_command_centres.launcherr   next
ValueErrorr	   )r   r   centrer#   s      @r0   _build_agent_from_configr     sZ    C&>DBnBDIF09::F^Wo_- r2   r#   c           	          t        j                  t         j                  dt        j                         ddlm} t         fd|D        d      }|s=t        j                  d |D cg c]  }|d   	 c}       t        j                  d	       t        |d   |d
   |d         }t        |      }	 t        j                  |j                                yc c}w # t        $ r t        j!                  d        Y yw xY w)z
    Launch a single daemon for the named command centre.

    Args:
        name: Centre name (e.g. "orchestrator", "builder").
    z1%(asctime)s [%(levelname)s] %(name)s: %(message)s)levelformatstreamr   r   c              3   4   K   | ]  }|d    k(  s|  ywr   r   r   s     r0   r   zmain.<locals>.<genexpr>  s     GaQvY$5FGr   Nu)   Unknown centre name: %r — available: %sr#   rR   r?   r   r   )r   z(GeminiDaemon[%s] interrupted by keyboard)loggingbasicConfigINFOsysstdoutr   r   r   r6   rJ   exitr	   r   rB   rM   KeyboardInterruptr7   )r#   r   centre_confr   r   daemons   `     r0   mainr     s     llBzz DG>GNK@$\jHkWX6Hkl '"!/2E
 &FFFJJL! Il  F>EFs   (C4#C C?>C?)%r   rB   r   rv   r   osr+   r   rE   r   r   pathlibr   typingr   r   uuidr   !core.gemini_command_centres.agentr	   	getLoggerr   r6   GENESIS_ROOTr   r    r!   rD   r   r   rV   r[   r   r   r   rU   r   r   r2   r0   <module>r      s        	  
  '     9			8	$ +,!F*-== '-7"V+.??      Q Ql
T k  Fs Ft Fr2   