
    oq'j                    ^   d Z ddlmZ ddlZddlZddlZddlZddlmZ ddl	m
Z
mZmZmZ ddlmZ ddlmZ  ej        e          ZdZ ej        d	ej                  Z ej        d
ej                  Z ej        dej                  ZddZ G d d          ZddZ G d d          ZdS )uT  MemoryManager — orchestrates memory providers for the agent.

Single integration point in run_agent.py. Replaces scattered per-backend
code with one manager that delegates to registered providers.

Only ONE external plugin provider is allowed at a time — attempting to
register a second external provider is rejected with a warning.  This
prevents tool schema bloat and conflicting memory backends.

Usage in run_agent.py:
    self._memory_manager = MemoryManager()
    # Only ONE of these:
    self._memory_manager.add_provider(plugin_provider)

    # System prompt
    prompt_parts.append(self._memory_manager.build_system_prompt())

    # Pre-turn
    context = self._memory_manager.prefetch_all(user_message)

    # Post-turn
    self._memory_manager.sync_all(user_msg, assistant_response)
    self._memory_manager.queue_prefetch_all(user_msg)
    )annotationsN)ThreadPoolExecutor)AnyDictListOptional)MemoryProvider)
tool_errorg      @z</?\s*memory-context\s*>z5<\s*memory-context\s*>[\s\S]*?</\s*memory-context\s*>z\[System note:\s*The following is recalled memory context,\s*NOT new user input\.\s*Treat as (?:informational background data|authoritative reference data[^\]]*)\.\]\s*textstrreturnc                    t                               d|           } t                              d|           } t                              d|           } | S )zQStrip fence tags, injected context blocks, and system notes from provider output. )_INTERNAL_CONTEXT_REsub_INTERNAL_NOTE_RE_FENCE_TAG_RE)r   s    9/home/ubuntu/.hermes/hermes-agent/agent/memory_manager.pysanitize_contextr   >   sF    ##B--D  T**DR&&DK    c                      e Zd ZdZdZdZddZddZdd
ZddZ	e
dd            ZddZddZd dZd dZd!dZd"dZdS )#StreamingContextScrubbera  Stateful scrubber for streaming text that may contain split memory-context spans.

    The one-shot ``sanitize_context`` regex cannot survive chunk boundaries:
    a ``<memory-context>`` opened in one delta and closed in a later delta
    leaks its payload to the UI because the non-greedy block regex needs
    both tags in one string.  This scrubber runs a small state machine
    across deltas, holding back partial-tag tails and discarding
    everything inside a span (including the system-note line).

    Usage::

        scrubber = StreamingContextScrubber()
        for delta in stream:
            visible = scrubber.feed(delta)
            if visible:
                emit(visible)
        trailing = scrubber.flush()  # at end of stream
        if trailing:
            emit(trailing)

    The scrubber is re-entrant per agent instance.  Callers building new
    top-level responses (new turn) should create a fresh scrubber or call
    ``reset()``.
    z<memory-context>z</memory-context>r   Nonec                0    d| _         d| _        d| _        d S NFr   T_in_span_buf_at_block_boundaryselfs    r   __init__z!StreamingContextScrubber.__init__c   s    #	(,r   c                0    d| _         d| _        d| _        d S r   r   r    s    r   resetzStreamingContextScrubber.reseth   s    	"&r   r   r   c                   |sdS | j         |z   }d| _         g }|r| j        r|                                                    | j                  }|dk    rD|                     || j                  }|r|| d         nd| _         d                    |          S ||t          | j                  z   d         }d| _        n|                     |          }|dk    r| 	                    |          p|                     || j
                  }|r0|                     ||d|                     || d         | _         n|                     ||           d                    |          S |dk    r|                     ||d|                    ||t          | j
                  z   d         }d| _        |d                    |          S )a  Return the visible portion of ``text`` after scrubbing.

        Any trailing fragment that could be the start of an open/close tag
        is held back in the internal buffer and surfaced on the next
        ``feed()`` call or discarded/emitted by ``flush()``.
        r   NFr   T)r   r   lowerfind
_CLOSE_TAG_max_partial_suffixjoinlen_find_boundary_open_tag_max_pending_open_suffix	_OPEN_TAG_append_visible)r!   r   bufoutidxhelds         r   feedzStreamingContextScrubber.feedm   s     	2i$	 	%} %iikk&&t77"9933CIID/3 ;TEFFDI773<<'#DO 4 44556 %22377"99 55c:: I33CHH   7,,S#fuf+>>>$'K		,,S#666773<<'77((c$3$i888#DN 3 33445 $;  	%> wws||r   c                P    | j         rd| _        d| _         dS | j        }d| _        |S )aQ  Emit any held-back buffer at end-of-stream.

        If we're still inside an unterminated span the remaining content is
        discarded (safer: leaking partial memory context is worse than a
        truncated answer).  Otherwise the held-back partial-tag tail is
        emitted verbatim (it turned out not to be a real tag).
        r   F)r   r   )r!   tails     r   flushzStreamingContextScrubber.flush   s4     = 	DI!DM2y	r   r1   tagintc                   |                                 }|                                  }t          t          |          t          |          dz
            }t          |dd          D ]$}|                    || d                   r|c S %dS )zReturn the length of the longest buf-suffix that is a tag-prefix.

        Case-insensitive.  Returns 0 if no suffix could start the tag.
           r   r&   N)r'   minr,   range
startswith)r1   r9   	tag_lower	buf_lower	max_checkis         r   r*   z,StreamingContextScrubber._max_partial_suffix   s     IIKK	IIKK	II(:;;	y!R(( 	 	A##IqbccN33 qr   c                    |                                 }d}	 |                    | j        |          }|dk    rdS |                     ||          r|                     ||          r|S |dz   }W)z<Find an opening fence only when it starts a block-like span.r   Tr&   r<   )r'   r(   r/   _is_block_boundary_has_block_opener_suffix)r!   r1   rA   search_startr3   s        r   r-   z0StreamingContextScrubber._find_boundary_open_tag   s    IIKK		#..>>Cbyyr&&sC00 T5R5RSVX[5\5\ 
7L	#r   c                    |                                                     | j                  sdS t          |          t          | j                  z
  }|                     ||          sdS t          | j                  S )zBHold a complete boundary tag until the following char confirms it.r   )r'   endswithr/   r,   rE   )r!   r1   r3   s      r   r.   z1StreamingContextScrubber._max_pending_open_suffix   sn    yy{{##DN33 	1#hhT^,,,&&sC00 	14>"""r   r3   boolc                n    |t          | j                  z   }|t          |          k    rdS ||         dv S )NFz
)r,   r/   )r!   r1   r3   	after_idxs       r   rF   z1StreamingContextScrubber._has_block_opener_suffix   s;    #dn---	C  59~''r   c                    |dk    r| j         S |d |         }|                    d          }|dk    r| j         o|                                dk    S ||dz   d                                          dk    S )Nr   
r&   r   r<   )r   rfindstrip)r!   r1   r3   	precedinglast_newlines        r   rE   z+StreamingContextScrubber._is_block_boundary   s    !88**I	 t,,2*Fy/@/@B/FF)**+1133r99r   r2   	list[str]c                b    |sd S |                     |           |                     |           d S N)append_update_block_boundary)r!   r2   r   s      r   r0   z(StreamingContextScrubber._append_visible   s;     	F

4##D)))))r   c                    |                     d          }|dk    r*||dz   d                                          dk    | _        d S | j        o|                                dk    | _        d S )NrN   r&   r<   r   )rO   rP   r   )r!   r   rR   s      r   rW   z/StreamingContextScrubber._update_block_boundary   sq    zz$''2&*<!+;+<+<&=&C&C&E&E&KD###&*&=&T$**,,RTBTD###r   Nr   r   r   r   r   r   r   r   )r1   r   r9   r   r   r:   )r1   r   r   r:   )r1   r   r3   r:   r   rJ   )r2   rS   r   r   r   r   )r   r   r   r   )__name__
__module____qualname____doc__r/   r)   r"   r$   r5   r8   staticmethodr*   r-   r.   rF   rE   r0   rW    r   r   r   r   F   s        2 #I$J- - - -
' ' ' '
, , , ,\        \
# 
# 
# 
## # # #( ( ( (: : : :* * * *U U U U U Ur   r   raw_contextc                    | r|                                  sdS t          |           }|| k    rt                              d           d| dS )z:Wrap prefetched memory in a fenced block with system note.r   z6memory provider returned pre-wrapped context; strippedu   <memory-context>
[System note: The following is recalled memory context, NOT new user input. Treat as authoritative reference data — this is the agent's persistent memory and should inform all responses.]

z
</memory-context>)rP   r   loggerwarning)rb   cleans     r   build_memory_context_blockrg      sk     k//11 r[))EOPPP	 		 	 	r   c                  T   e Zd ZdZdLdZdMdZedNd	            ZdOdZdPdZ	dddQdZ
dddRdZedSd            ZddddTdZdLdZdUd ZdVdWd#ZdXd%ZdYd'ZdZd)Zd[d,Zd\d0Zd]d1Zdd2d2d3d^d8Zd_d9Zed`d:            Z	 dVdad@ZddAdbdEZdLdFZdLdGZedcdJ            ZdddKZ dS )eMemoryManagerzOrchestrates the built-in provider plus at most one external provider.

    The builtin provider is always first. Only one non-builtin (external)
    provider is allowed.  Failures in one provider never block the other.
    r   r   c                n    g | _         i | _        d| _        d | _        t	          j                    | _        d S )NF)
_providers_tool_to_provider_has_external_sync_executor	threadingLock_sync_executor_lockr    s    r   r"   zMemoryManager.__init__  s:    02<>#( =A#,>#3#3   r   providerr	   c                   |j         dk    }|sP| j        rBt          d | j        D             d          }t                              d|j         |           dS d| _        | j                            |           ddlm} t          |          }|
                                D ]}|                    d	d
          }||v r"t                              d|j         |           >|r|| j        vr|| j        |<   T|| j        v r2t                              d|| j        |         j         |j                    t                              d|j         t          |
                                                     dS )u   Register a memory provider.

        Built-in provider (name ``"builtin"``) is always accepted.
        Only **one** external (non-builtin) provider is allowed — a second
        attempt is rejected with a warning.
        builtinc              3  :   K   | ]}|j         d k    |j         V  dS )rt   N)name.0ps     r   	<genexpr>z-MemoryManager.add_provider.<locals>.<genexpr>  s0      LL)8K8KQV8K8K8K8KLLr   unknownu   Rejected memory provider '%s' — external provider '%s' is already registered. Only one external memory provider is allowed at a time. Configure which one via memory.provider in config.yaml.NTr   _HERMES_CORE_TOOLSrv   r   u   Memory provider '%s' tool '%s' shadows a reserved core tool name; registration ignored. Core tools always win — rename the provider's tool to something unique.zJMemory tool name conflict: '%s' already registered by %s, ignoring from %sz*Memory provider '%s' registered (%d tools))rv   rm   nextrk   rd   re   rV   toolsetsr}   setget_tool_schemasgetrl   infor,   )r!   rr   
is_builtinexistingr}   _core_tool_namesschema	tool_names           r   add_providerzMemoryManager.add_provider  s    ]i/
 	&! LLT_LLLi  & M8   !%Dx((( 	0/////122 //11 	 	F

62..I,,,F M9	    	Yd.DDD4<&y11d444'*95:M   	8M))++,,	
 	
 	
 	
 	
r   List[MemoryProvider]c                *    t          | j                  S )z"All registered providers in order.)listrk   r    s    r   	providerszMemoryManager.providersR  s     DO$$$r   rv   r   Optional[MemoryProvider]c                8    | j         D ]}|j        |k    r|c S dS )z2Get a provider by name, or None if not registered.N)rk   rv   )r!   rv   ry   s      r   get_providerzMemoryManager.get_providerW  s1     	 	Av~~ tr   c                4   g }| j         D ]z}	 |                                }|r)|                                r|                    |           C# t          $ r+}t
                              d|j        |           Y d}~sd}~ww xY wd                    |          S )zCollect system prompt blocks from all providers.

        Returns combined text, or empty string if no providers contribute.
        Each non-empty block is labeled with the provider name.
        z5Memory provider '%s' system_prompt_block() failed: %sN

)	rk   system_prompt_blockrP   rV   	Exceptionrd   re   rv   r+   )r!   blocksrr   blockes        r   build_system_promptz!MemoryManager.build_system_prompt`  s      		 		H 4466 )U[[]] )MM%(((   KM1       
 {{6"""s   ?A
B!A==Br   
session_idqueryr   c               :   g }| j         D ]}}	 |                    ||          }|r)|                                r|                    |           F# t          $ r+}t
                              d|j        |           Y d}~vd}~ww xY wd                    |          S )zCollect prefetch context from all providers.

        Returns merged context text labeled by provider. Empty providers
        are skipped. Failures in one provider don't block others.
        r   z4Memory provider '%s' prefetch failed (non-fatal): %sNr   )	rk   prefetchrP   rV   r   rd   debugrv   r+   )r!   r   r   partsrr   resultr   s          r   prefetch_allzMemoryManager.prefetch_allu  s      		 		H!**5Z*HH )fllnn )LL(((   JM1       
 {{5!!!s   AA
B!B  Bc               v    t          | j                  sdS dfd}|                     |           dS )a!  Queue background prefetch on all providers for the next turn.

        Provider work is dispatched to a background worker so a slow or
        wedged provider can never block the caller. See ``sync_all`` for
        the full rationale (agent stuck "running" minutes after a turn).
        Nr   r   c                     D ]R} 	 |                                 # t          $ r+}t                              d| j        |           Y d }~Kd }~ww xY wd S )Nr   z:Memory provider '%s' queue_prefetch failed (non-fatal): %s)queue_prefetchr   rd   r   rv   )rr   r   r   r   r   s     r   _runz.MemoryManager.queue_prefetch_all.<locals>._run  s    %  ++Ej+IIII    LLT q        s   
A!AArY   r   rk   _submit_background)r!   r   r   r   r   s    `` @r   queue_prefetch_allz MemoryManager.queue_prefetch_all  sg     ))	 	F	 	 	 	 	 	 	 	 	%%%%%r   rJ   c                    	 t          j        | j                  }n# t          t          f$ r Y dS w xY wt          |j                                                  }t          d |D                       rdS d|j        v S )z4Return whether sync_turn accepts a messages keyword.Tc              3  J   K   | ]}|j         t          j        j        k    V  d S rU   kindinspect	ParameterVAR_KEYWORDrw   s     r   rz   z@MemoryManager._provider_sync_accepts_messages.<locals>.<genexpr>  /      GG1qv*66GGGGGGr   messages)	r   	signature	sync_turn	TypeError
ValueErrorr   
parametersvaluesany)rr   r   paramss      r   _provider_sync_accepts_messagesz-MemoryManager._provider_sync_accepts_messages  s    	)(*<==II:& 	 	 	44	i*113344GGGGGGG 	4Y111    11Nr   r   user_contentassistant_contentr   Optional[List[Dict[str, Any]]]c                    t           j                  sdS d fd}                     |           dS )u  Sync a completed turn to all providers.

        Runs on a background worker thread, NOT inline on the
        turn-completion path. A provider's ``sync_turn`` may make a
        blocking network/daemon call (a misconfigured Hindsight daemon
        was observed blocking ~298s before failing); doing that inline
        held ``run_conversation`` open long after the user saw their
        response, so every interface (CLI, TUI, gateway) kept the agent
        marked "running" for minutes and any follow-up message triggered
        an aggressive interrupt. Dispatching off-thread means a slow or
        broken provider can never stall the turn — the sync simply
        completes (or fails, logged) in the background.

        Writes are serialized through a single worker so turn N lands
        before turn N+1; provider implementations don't need their own
        ordering guarantees.
        Nr   r   c                    D ]} 	 /                     |           r|                                n|                                M# t          $ r+}t                              d| j        |           Y d }~}d }~ww xY wd S )Nr   r   z)Memory provider '%s' sync_turn failed: %s)r   r   r   rd   re   rv   )rr   r   r   r   r   r!   r   r   s     r   r   z$MemoryManager.sync_all.<locals>._run  s    %  +0T0TU]0^0^+ **(-'1%-	 +     !**(-'1 +   
 !   NNC q        s   A	A
B!BBrY   r   )r!   r   r   r   r   r   r   s   ````` @r   sync_allzMemoryManager.sync_all  sy    2 ))	 	F	 	 	 	 	 	 	 	 	 	 	, 	%%%%%r   c                ~   |                                  }|@	  |             n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wdS 	 |                    |           dS # t
          $ rC 	  |             Y dS # t          $ r'}t                              d|           Y d}~Y dS d}~ww xY ww xY w)u  Run ``fn`` on the manager's background worker.

        The executor is created lazily and shared across calls. If the
        executor can't be created or has already been shut down, ``fn``
        runs inline as a last-resort fallback — losing the async benefit
        but never losing the write itself. ``fn`` must do its own
        per-provider error handling; this wrapper only guards executor
        plumbing.
        Nz(Inline memory background task failed: %s)_get_sync_executorr   rd   r   submitRuntimeError)r!   fnexecutorr   s       r   r   z MemoryManager._submit_background  s    **,,L L L LGKKKKKKKKLF	LOOB 	L 	L 	LL L L LGKKKKKKKKKKL	LsD   
# 
AAAA/ /
B<:
B
B8B3,B<3B88B<Optional[ThreadPoolExecutor]c                (   | j         | j         S | j        5  | j         V	 t          dd          | _         n># t          $ r1}t                              d|           Y d}~ddd           dS d}~ww xY w| j         cddd           S # 1 swxY w Y   dS )z4Lazily create the single-worker background executor.Nr<   zmem-sync)max_workersthread_name_prefixz)Failed to create memory sync executor: %s)rn   rq   r   r   rd   re   )r!   r   s     r   r   z MemoryManager._get_sync_executor  s   *&&% 
	' 
	'"* *<$%+5+ + +D'' !      NN#NPQRRR444
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	'  &
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	' 
	's7   B6B
A1 A,B,A11	BBBtimeoutOptional[float]c                    | j         }|dS 	 |                    d           }n# t          $ r Y dS w xY w	 |                    |           dS # t          $ r Y dS w xY w)a  Block until queued sync/prefetch work has drained.

        Single-worker executor means submitting a sentinel and waiting on
        it guarantees every previously-submitted task has run. Returns
        True if the barrier completed within ``timeout`` (or no executor
        exists), False on timeout. Used at real session boundaries and by
        tests that need to assert provider state deterministically.
        NTc                     d S rU   ra   ra   r   r   <lambda>z-MemoryManager.flush_pending.<locals>.<lambda>  s    $ r   r   F)rn   r   r   r   r   )r!   r   r   futs       r   flush_pendingzMemoryManager.flush_pending  s     &4	//,,//CC 	 	 	44		JJwJ'''4 	 	 	55	s   $ 
22A 
AAList[Dict[str, Any]]c                   ddl m} t          |          }g }t                      }| j        D ]}	 |                                D ]M}|                    dd          }||v r|r.||vr*|                    |           |                    |           Nf# t          $ r+}t          
                    d|j        |           Y d}~d}~ww xY w|S )uC  Collect tool schemas from all providers.

        Reserved core tool names (``clarify``, ``delegate_task``, etc.) are
        skipped — they are rejected from the routing table in
        :meth:`add_provider`, so the manager must not advertise a schema it
        will never route. Built-ins always win (#40466).
        r   r|   rv   r   z2Memory provider '%s' get_tool_schemas() failed: %sN)r   r}   r   rk   r   r   rV   addr   rd   re   rv   )	r!   r}   r   schemasseenrr   r   rv   r   s	            r   get_all_tool_schemasz"MemoryManager.get_all_tool_schemas*  s     	0/////122uu 	 	H&7799 ' 'F!::fb11D///  'D 0 0v...'    HM1       
 s   A"B
C!CCr   c                N    t          | j                                                  S )z2Return set of all tool names across all providers.)r   rl   keysr    s    r   get_all_tool_namesz MemoryManager.get_all_tool_namesG  s    4)..00111r   r   c                    || j         v S )z(Check if any provider handles this tool.)rl   )r!   r   s     r   has_toolzMemoryManager.has_toolK  s    D222r   argsDict[str, Any]c                   | j                             |          }|t          d| d          S 	  |j        ||fi |S # t          $ rA}t
                              d|j        ||           t          d| d|           cY d}~S d}~ww xY w)zRoute a tool call to the correct provider.

        Returns JSON string result. Raises ValueError if no provider
        handles the tool.
        Nz!No memory provider handles tool ''z4Memory provider '%s' handle_tool_call(%s) failed: %szMemory tool 'z
' failed: )rl   r   r
   handle_tool_callr   rd   errorrv   )r!   r   r   kwargsrr   r   s         r   r   zMemoryManager.handle_tool_callO  s     )--i88N)NNNOOO	H,8,YGGGGG 	H 	H 	HLLFy!   FiFF1FFGGGGGGGG	Hs   A   
B
6B BBturn_numberr:   messagec                    | j         D ]J}	  |j        ||fi | # t          $ r+}t                              d|j        |           Y d}~Cd}~ww xY wdS )zxNotify all providers of a new turn.

        kwargs may include: remaining_tokens, model, platform, tool_count.
        z-Memory provider '%s' on_turn_start failed: %sN)rk   on_turn_startr   rd   r   rv   )r!   r   r   r   rr   r   s         r   r   zMemoryManager.on_turn_starte  s    
  	 	H&&{GFFvFFFF   CM1       	 	s   
A!AAc                    | j         D ]P}	 |                    |           # t          $ r+}t                              d|j        |           Y d}~Id}~ww xY wdS )z$Notify all providers of session end.z.Memory provider '%s' on_session_end failed: %sN)rk   on_session_endr   rd   r   rv   )r!   r   rr   r   s       r   r   zMemoryManager.on_session_ends  s     	 	H''1111   DM1       	 	s   !
A!AAF)parent_session_idr$   rewoundnew_session_idr   r$   r   c                   |sdS |rd|d<   | j         D ]L}	  |j        |f||d| # t          $ r+}t                              d|j        |           Y d}~Ed}~ww xY wdS )u  Notify all providers that the agent's session_id has rotated.

        Fires on ``/resume``, ``/branch``, ``/reset``, ``/new``, and
        context compression — any path that reassigns
        ``AIAgent.session_id`` without tearing the provider down.

        Providers keep running; they only need to refresh cached
        per-session state so subsequent writes land in the correct
        session's record. See ``MemoryProvider.on_session_switch`` for
        the full contract.

        ``rewound=True`` signals that session_id is unchanged but the
        transcript was truncated; providers caching per-turn document
        state should invalidate.
        NTr   )r   r$   z1Memory provider '%s' on_session_switch failed: %s)rk   on_session_switchr   rd   r   rv   )r!   r   r   r$   r   r   rr   r   s           r   r   zMemoryManager.on_session_switch~  s    0  	F  	% $F9 	 	H**"&7  	       GM1       	 	s   (
A!AAc                6   g }| j         D ]{}	 |                    |          }|r)|                                r|                    |           D# t          $ r+}t
                              d|j        |           Y d}~td}~ww xY wd                    |          S )zNotify all providers before context compression.

        Returns combined text from providers to include in the compression
        summary prompt. Empty string if no provider contributes.
        z/Memory provider '%s' on_pre_compress failed: %sNr   )	rk   on_pre_compressrP   rV   r   rd   r   rv   r+   )r!   r   r   rr   r   r   s         r   r   zMemoryManager.on_pre_compress  s      		 		H!11(;; )fllnn )LL(((   EM1       
 {{5!!!s   A A
B!A>>Bc                F   	 t          j        | j                  }n# t          t          f$ r Y dS w xY wt          |j                                                  }t          d |D                       rdS d|j        v rdS d |D             }t          |          dk    rdS dS )z>Return how to pass metadata to a provider's memory-write hook.keywordc              3  J   K   | ]}|j         t          j        j        k    V  d S rU   r   rw   s     r   rz   zEMemoryManager._provider_memory_write_metadata_mode.<locals>.<genexpr>  r   r   metadatac                    g | ]=}|j         t          j        j        t          j        j        t          j        j        hv ;|>S ra   )r   r   r   POSITIONAL_ONLYPOSITIONAL_OR_KEYWORDKEYWORD_ONLYrw   s     r   
<listcomp>zFMemoryManager._provider_memory_write_metadata_mode.<locals>.<listcomp>  sW     
 
 
v!1!7!.     r      
positionallegacy)
r   r   on_memory_writer   r   r   r   r   r   r,   )rr   r   r   accepteds       r   $_provider_memory_write_metadata_modez2MemoryManager._provider_memory_write_metadata_mode  s    	)(*BCCII:& 	 	 	99	 i*113344GGGGGGG 	9---9
 

 
 
 x==A<xr   actiontargetcontentr   Optional[Dict[str, Any]]c           	        | j         D ]}|j        dk    r	 |                     |          }|dk    r)|                    |||t	          |pi                      nE|dk    r(|                    |||t	          |pi                      n|                    |||           # t
          $ r+}t                              d|j        |           Y d}~d}~ww xY wdS )zNotify external providers when the built-in memory tool writes.

        Skips the builtin provider itself (it's the source of the write).
        rt   r   )r   r   z/Memory provider '%s' on_memory_write failed: %sN)rk   rv   r  r   dictr   rd   r   )r!   r  r  r  r   rr   metadata_moder   s           r   r   zMemoryManager.on_memory_write  s1     	 	H}	)) $ I I( S S I--,,$x~2:N:N -     #l22,,VVWd8>WYFZFZ[[[[,,VVWEEE   EM1       	 	s   B	B!!
C+!CC)child_session_idtaskr   r
  c                   | j         D ]L}	  |j        ||fd|i| # t          $ r+}t                              d|j        |           Y d}~Ed}~ww xY wdS )z/Notify all providers that a subagent completed.r
  z-Memory provider '%s' on_delegation failed: %sN)rk   on_delegationr   rd   r   rv   )r!   r  r   r
  r   rr   r   s          r   r  zMemoryManager.on_delegation  s      		 		H&&& 3CGM       CM1       		 		s   
A!AAc                    |                                   t          | j                  D ]O}	 |                                 # t          $ r+}t
                              d|j        |           Y d}~Hd}~ww xY wdS )a  Shut down all providers (reverse order for clean teardown).

        Drains the background sync/prefetch executor first (bounded by
        ``_SYNC_DRAIN_TIMEOUT_S``) so a turn's final sync has a chance to
        land before providers are torn down. The worker threads are
        daemon, so anything still wedged past the drain window dies with
        the interpreter rather than blocking exit.
        z(Memory provider '%s' shutdown failed: %sN)_drain_sync_executorreversedrk   shutdownr   rd   re   rv   )r!   rr   r   s      r   shutdown_allzMemoryManager.shutdown_all  s     	!!### 11 	 	H!!####   >M1       	 	s   A
A6!A11A6c                D     j         5   j        d _        ddd           n# 1 swxY w Y   dS 	                     dd           n# t          $ rN 	                     d           n2# t          $ r%}t
                              d|           Y d}~nd}~ww xY wY dS t          $ r&}t
                              d|           Y d}~dS d}~ww xY wt          j         fddd	          }|	                                 |
                    t          
           dS )a  Shut down the background executor, waiting briefly for drain.

        Bounded by ``_SYNC_DRAIN_TIMEOUT_S``: a wedged provider must never
        hang process/session teardown. We stop accepting new work and
        cancel anything still queued, then wait at most the drain timeout
        for the currently-running task on a watcher thread. The worker is
        daemon, so an over-running task dies with the interpreter.
        NFT)waitcancel_futuresr  z(Memory sync executor shutdown failed: %sc                 .                                    S rU   )_bounded_executor_wait)r   r!   s   r   r   z4MemoryManager._drain_sync_executor.<locals>.<lambda>:  s    466x@@ r   zmem-sync-drain)r  daemonrv   r   )rq   rn   r  r   r   rd   r   ro   Threadstartr+   _SYNC_DRAIN_TIMEOUT_S)r!   r   drainerr   s   `  @r   r  z"MemoryManager._drain_sync_executor  s    % 	' 	'*H"&D	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' 	' F	 5>>>> 	 	 	L!!u!---- L L LGKKKKKKKKLFF 	 	 	LLCQGGGFFFFF	
 "@@@@@!
 
 

 	233333sS   %))A 
CA/.C/
B9BCBC$	C-CCr   r   c                    	 |                      d           d S # t          $ r&}t                              d|           Y d }~d S d }~ww xY w)NTr  z*Memory sync executor drain wait failed: %s)r  r   rd   r   )r   r   s     r   r  z$MemoryManager._bounded_executor_waitA  so    	J4((((( 	J 	J 	JLLEqIIIIIIIII	Js    
A
AA
c                    d|vr ddl m} t           |                      |d<   | j        D ]J}	  |j        dd|i| # t
          $ r+}t                              d|j        |           Y d}~Cd}~ww xY wdS )zInitialize all providers.

        Automatically injects ``hermes_home`` into *kwargs* so that every
        provider can resolve profile-scoped storage paths without importing
        ``get_hermes_home()`` themselves.
        hermes_homer   )get_hermes_homer   z*Memory provider '%s' initialize failed: %sNra   )	hermes_constantsr!  r   rk   
initializer   rd   re   rv   )r!   r   r   r!  rr   r   s         r   initialize_allzMemoryManager.initialize_allH  s     &&888888$'(9(9$:$:F=! 	 	H##DDzDVDDDD   @M1       	 	s   ?
A4	!A//A4rY   )rr   r	   r   r   )r   r   )rv   r   r   r   r[   )r   r   r   r   r   r   )r   r   r   r   r   r   )rr   r	   r   rJ   )
r   r   r   r   r   r   r   r   r   r   )r   r   rU   )r   r   r   rJ   )r   r   )r   r   )r   r   r   rJ   )r   r   r   r   r   r   )r   r:   r   r   r   r   )r   r   r   r   )
r   r   r   r   r$   rJ   r   rJ   r   r   )r   r   r   r   )rr   r	   r   r   )
r  r   r  r   r  r   r   r  r   r   )r  r   r   r   r
  r   r   r   )r   r   r   r   )r   r   r   r   )!r\   r]   r^   r_   r"   r   propertyr   r   r   r   r   r`   r   r   r   r   r   r   r   r   r   r   r   r   r   r  r   r  r  r  r  r$  ra   r   r   ri   ri      s        
4 
4 
4 
4?
 ?
 ?
 ?
B % % % X%   # # # #* =? " " " " " "& CE & & & & & &2 	2 	2 	2 \	2  373& 3& 3& 3& 3& 3&nL L L L:' ' ' '     2   :2 2 2 23 3 3 3H H H H,   	 	 	 	 "$. . . . . .`" " " "&    \< .2    > /1        &%4 %4 %4 %4N J J J \J     r   ri   rZ   )rb   r   r   r   )r_   
__future__r   loggingrer   ro   concurrent.futuresr   typingr   r   r   r   agent.memory_providerr	   tools.registryr
   	getLoggerr\   rd   r  compile
IGNORECASEr   r   r   r   r   rg   ri   ra   r   r   <module>r0     s   2 # " " " " "  				      1 1 1 1 1 1 , , , , , , , , , , , , 0 0 0 0 0 0 % % % % % %		8	$	$   
6FF!rz<M   BJ pM     bU bU bU bU bU bU bU bUJ   "]	 ]	 ]	 ]	 ]	 ]	 ]	 ]	 ]	 ]	r   