
    xj                         U d Z ddlZddlmZ ddlmZ dZdZ ej                    Z	 e            Z
ded<   d	ed
dfdZded
ee         fdZddZdS )u|  Builder-declared stable prefixes for Anthropic prompt caching (#81867).

Skill, webhook, and cron builders concatenate a large static scaffold
(activation note + expanded skill body) with a small volatile invocation
tail (ticket payload, timestamps, run context) into one user-message
string. Only the builder knows the exact byte where the volatile tail
begins, so it registers the stable prefix here at construction time; the
cache planner consults the registry to place a cache breakpoint at that
boundary instead of caching the whole message as one atomic block.

This deliberately avoids re-parsing scaffold marker strings out of the
message at request time: markers can legitimately appear inside skill
bodies or inside event payloads (e.g. a helpdesk ticket quoting an agent
transcript), and any delimiter-search heuristic then either shrinks the
cached prefix or — worse — silently absorbs volatile bytes into it,
reintroducing the per-invocation cache miss this exists to fix.

The registry is process-local by design. A freshly fired webhook/cron
invocation is always built and sent by the same process, which is the
only window where the split pays off. Any miss (restart, eviction,
historic message) falls back to the pre-existing whole-message policy.

Split-shape lifetime: the split is applied only while the skill message is
one of the plan's marked endpoints (the last few cacheable messages). Once
later turns rotate it out of that window it ships as a single string block
again, which changes the block boundary once and re-ingests the prefix from
that message onward exactly one time in a long-lived session. Webhook/cron
invocations — the workload this exists for — send the skill turn as the
newest message every time, so they always hit the split shape; the one-time
re-ingest only affects long interactive sessions and nets out far below the
per-invocation full rewrite this removes.
    N)OrderedDict)Optional    i  @ zOrderedDict[str, None]	_prefixesprefixreturnc                    | sdS t           5  dt          | <   t                              |            t          t                    t          k    r8t                              d           t          t                    t          k    8t          t                    dk    rt          t          t          t                              t          k    rct                              d           t          t                    dk    r0t          t          t          t                              t          k    cddd           dS # 1 swxY w Y   dS )zARecord ``prefix`` as the stable scaffold of a just-built message.NF)last   )	_lockr   move_to_endlen_MAX_ENTRIESpopitemsummap
_MAX_CHARS)r   s    ?/home/agent/.hermes/hermes-agent/agent/prompt_cache_boundary.pyregister_stable_prefixr   8   sE    	 * * 	&f%%%)nn|++5))) )nn|++)nnq  SS))<)<%=%=
%J%J5))) )nnq  SS))<)<%=%=
%J%J* * * * * * * * * * * * * * * * * *s   D%D>>EEcontentc                 J   t           5  d}t          D ][}t          |           t          |          k    r9|                     |          r$| t          |          t          |          k    r|}\|t                              |           |cddd           S # 1 swxY w Y   dS )a  Longest registered prefix that is a *proper* prefix of ``content``.

    Proper (``len(content) > len(prefix)``) so the split never produces an
    empty volatile text block, which Anthropic rejects on the wire.

    A hit refreshes the entry's LRU position: a scaffold fired every minute
    by cron must not be evicted by a burst of one-off skill invocations,
    which would silently drop it back to whole-message caching.
    N)r   r   r   
startswithr   )r   bestr   s      r   find_stable_prefixr   E   s     
 	 	" 	" 	"F7||c&kk))g.@.@.H.H)<3v;;T#:#:!D!!$'''	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	 	s   BBBBc                  x    t           5  t                                           ddd           dS # 1 swxY w Y   dS )zTest isolation helper.N)r   r   clear     r   clear_stable_prefixesr   [   s|    	                   s   /33)r   N)__doc__	threadingcollectionsr   typingr   r   r   Lockr   r   __annotations__strr   r   r   r   r   r   <module>r'      s     B     # # # # # #      
  
	&1kmm	# 3 3 3
*3 
*4 
* 
* 
* 
*     ,     r   