+
    iHàj-  ã                   óê   € R t ^ RIt^ RIt^ RIHtHt ^ RIHt ]P                  ! ]	4      t
RtRtR R ltRR R	 lltR
 R ltR R ltR R ltRtRtRR R lltR R ltR R ltR R ltR R ltR# )uÈ  Rotation-stable logical cache scope for prompt_cache_key derivation.

Legacy compression rotation mints a new physical ``session_id`` mid-conversation, moving it
into a fresh cache bucket. ``resolve_prompt_cache_scope()`` maps the physical id to the ROOT
of its compression lineage â€” NOT ``get_conversation_root`` (the Portal-attribution walk),
which would collapse /branch children and delegate trees into one id. ``/new`` starts a
fresh scope; fork children (branch, delegate, tool-tagged) are isolated. Hosts minting one id
per RESPONSE declare the conversation via ``gateway_session_key``, which wins over the lineage
walk and is hashed to ``gwk_<sha256[:24]>`` (it embeds platform/chat/user identifiers).
N)ÚAnyÚOptional)Úbase_url_hostnameÚ_prompt_cache_scope_memoÚgwk_c                óR   € V ^8„  d   QhR\         R\        R\        \         ,          /# )é   Ú
session_idÚ
session_dbÚreturn)Ústrr   r   )Úformats   "Ú=/home/ubuntu/.hermes/hermes-agent/agent/prompt_cache_scope.pyÚ__annotate__r      s%   € ÷ ñ œcð ¬sð ´xÄµ}ñ ó    c                ó"  € Vf   R#  VP                  V 4      p\	        T\
        \        34      '       d2   T'       d*   T^ ,          p\	        T\        4      '       d   T'       d   T# R#   \         d    \        P                  RRR7        R# i ; i)zRCompression-lineage root of *session_id*, or None (tolerates test-double results).Nz&prompt-cache scope lineage walk failedT©Úexc_info)Úget_compression_lineageÚ	ExceptionÚloggerÚdebugÚ
isinstanceÚlistÚtupler   )r	   r
   ÚlineageÚroots   &&  r   Ú_lineage_rootr      sz   € àÒÙðØ×4Ñ4°ZÓ@ˆô �'œD¤%˜=×)Ò)¯gØ�q�zˆÜ�dœC× Ò §TØˆKÙøô ô Ü�‰Ð=ÈˆÔMÚðús   ˆA( Á("BÂBc          
      ój   € V ^8„  d   QhR\         R\        R\         R\        \        ,          R\        /# )r   Úagentr	   r
   Ú
row_sourcer   )r   r   r   )r   s   "r   r   r   (   s8   € ÷ 5ñ 5Üð5Üð5Ü-0ð5Ü>FÄs½mð5äñ5r   c                óŠ  € Vf[   V'       dS   VeO    VP                  V4      pV'       d3   \	        VP                  R4      ;'       g    R4      P                  4       MRpV'       d   V# \        V RR4      p^ RIH	p \	        V! V4      4      P                  4       #   \         d    \        P                  RRR7       Rp Lži ; i)	a”  The ``sessions.source`` this agent's conversation is recorded under.

``row_source``: the row's value if already read (``""`` = read, none; ``None`` = look it
up). Before the row lands, use the SAME resolver persistence uses, not ``agent.platform``:
they diverge under ``HERMES_SESSION_SOURCE`` and the declared scope is memoized at once,
so both sides of a ``/new`` would otherwise hash the same scope.
Nz#declared-scope source lookup failedTr   ÚsourceÚ Úplatform)Úsession_source_for)
Úget_sessionr   r   r   r   ÚgetÚstripÚgetattrÚagent.session_sourcer%   )r   r	   r
   r    Úrowr$   r%   s   &&&&   r   Ú_agent_sourcer,   (   s¬   € ð ÒŸj¨ZÒ-Cð	Ø×(Ñ(¨Ó4ˆC÷ >A”S˜Ÿ™ Ó*×0Ð0¨bÓ1×7Ñ7Ô9Àbˆ
ßØÐÜ�u˜j¨$Ó/€HÝ7ÜÑ! (Ó+Ó,×2Ñ2Ó4Ð4øô ô 	Ü�L‰LÐ>ÈˆLÔNØŠCð	ús   ’B Â$CÃCc                óH   € V ^8„  d   QhR\         R\         R\        R\         /# )r   Úsession_keyr"   r
   r   )r   r   )r   s   "r   r   r   @   s)   € ÷ >ñ >¬#ð >´sð >Ìð >ÔPSñ >r   c                ó†   € \        VRR4      p\        V4      '       g   R# V! W4      pVf   R# \        \        V4      4      # )a—  Durable generation for *session_key*'s current conversation (``""`` if none).

The declared key survives ``/new``, so hashing it alone would reuse one scope across
conversations. The counter advances with each reset boundary, independent of prunable
rows and wall-clock; compression does not advance it.

The declared key names a chat and deliberately survives `/new` and policy resets. See #79017, #86733.
Úlatest_conversation_boundaryNr#   )r)   Úcallabler   Úint)r.   r"   r
   ÚreaderÚ
generations   &&&  r   Ú_conversation_generationr5   @   sD   € ô �ZÐ!?ÀÓF€FÜ�F×ÒÙÙ˜Ó,€JØÒ#ˆ2Ð=¬¬S°«_Ó)=Ð=r   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# ©r   r   r   ©r   r   r   )r   s   "r   r   r   P   s   € ÷ 6/ñ 6/¤sð 6/¬x¼­}ñ 6/r   c                óœ  € \        V RR4      p\        V\        4      '       d   VP                  \        4      '       d   V# \        \        V RR4      ;'       g    R4      P                  4       pV'       d   \        V RR4      '       d   R# \        \        V RR4      ;'       g    R4      p\        V RR4      pRpRpV'       dK   VeG    \        VR	R4      p\        V4      '       d   V! V4      w  r†MVP                  V4      pV'       d   R#  \        WWF4      p	Ve    \        W)V4      pV	 RV RV 2p
\        P                  ! V
P                  RRR7      4      P!                  4       R,          p\         V 2#   \         d    \        P                  R
RR7        R# i ; i  \         d    \        P                  RRR7        R# i ; i)uË  Host-declared logical conversation scope (``gwk_<sha256[:24]>``), or None.

Hashes ``(source, gateway_session_key, generation)``. None (fall back to the physical id)
when no key is declared, for a background-review fork (``_persist_disabled``), for an
explicit fork child, and on any DB error (fail closed rather than merge a fork onto its
parent's key).

The one sanctioned exception is a same-model cache-parity fork (#109964): its whole purpose
is prefix parity with the parent, yet ``_persist_disabled`` + ``_session_db=None`` made both
resolvers key it into a different bucket (one cold ~full-context request per review).
``build_cache_parity_fork`` stamps the parent's ALREADY-RESOLVED scope as
``_inherited_cache_scope`` (no DB access from the fork). Only a ``gwk_`` value is a declared
scope; a physical lineage root stays out of the affinity header so the fork publishes
exactly what its parent publishes (None â†’ consumers fall back to the conversation root).
Ú_inherited_cache_scopeNÚ_gateway_session_keyr#   Ú_persist_disabledFr	   Ú_session_dbÚdeclared_scope_identityz declared-scope fork check failedTr   z%declared-scope generation read failedÚ|zutf-8Úreplace)Úerrors:Né   N)r)   r   r   Ú
startswithÚ_DECLARED_SCOPE_PREFIXr(   r1   Úis_explicit_fork_childr   r   r   r,   r5   ÚhashlibÚsha256ÚencodeÚ	hexdigest)r   Ú	inheritedÚkeyÚsidÚdbr4   r    ÚidentityÚis_forkr"   ÚcarrierÚdigests   &           r   Údeclared_conversation_scoperR   P   s´  € ô  ˜Ð7¸Ó>€IÜ�)œS×!Ò! i×&:Ñ&:Ô;Q×&RÒ&RØÐÜ
Œg�eÐ3°RÓ8×>Ð>¸BÓ
?×
EÑ
EÓ
G€Cß”'˜%Ð!4°e×<Ò<ÙÜ
Œg�e˜\¨4Ó0×6Ð6°BÓ
7€CÜ	�˜ tÓ	,€BØ€JØ $€Jß
ˆrŠ~ð	ô ˜rÐ#<¸dÓCˆHÜ˜×!Ò!Ù&.¨s£mÑ#�˜à×3Ñ3°CÓ8�ßÙð ô
 ˜5 rÓ6€FØ	‚~ð	Ü1°#¸rÓBˆJð
 �˜˜#˜˜a 
˜|Ð,€GÜ�^Š^˜GŸN™N¨7¸9˜NÓEÓF×PÑPÓRÐSVÕW€FÜ$Ð% f XÐ.Ð.øô ô 	Ü�L‰LÐ;ÀdˆLÔKÚð	ûô ô 	Ü�L‰LÐ@È4ˆLÔPÚð	ús%   Ã
AE< Ä F% Å<"F"Æ!F"Æ%"GÇ
Gc                ó0   € V ^8„  d   QhR\         R\        /# r7   ©r   r   )r   s   "r   r   r   ‰   s   € ÷ )ñ )¤cð )¬cñ )r   c                ó|  € \        V RR4      p\        V\        4      '       d   V'       d   \        W4      # \        \        V RR4      ;'       g    R4      pV'       g   R# \        V RR4      pW#RJ3p\        V \        R4      p\        V\
        4      '       d1   \        V4      ^8X  d!   V^ ,          V8X  d   \        W^,          4      # \        V 4      ;'       g    \        W#4      pT;'       g    TpVf   Ve   \        V RR4      '       d    \        V \        WG34       \        W4      #   \         d     Li ; i)zîRotation-stable cache-scope id: the inherited parent scope of a same-model cache-parity
fork, else the declared scope, else the compression-lineage root of ``agent.session_id``
(the physical id without ancestry/DB). Memoized on the agent.r:   Nr	   r#   r=   r<   F)r)   r   r   Ú_apply_fork_tagÚ
_MEMO_ATTRr   ÚlenrR   r   Úsetattrr   )r   rJ   rL   rM   rK   Úmemor   Úscopes   &       r   Úresolve_prompt_cache_scoper\   ‰   s  € ô ˜Ð7¸Ó>€IÜ�)œS×!Ò!§iÜ˜uÓ0Ð0Ü
Œg�e˜\¨4Ó0×6Ð6°BÓ
7€CßÙÜ	�˜ tÓ	,€Bà˜$�Ð
€CÜ�5œ* dÓ+€DÜ�$œ×Ò¤3 t£9°¤>°d¸1µgÀ´nÜ˜u¨1¥gÓ.Ð.Ü& uÓ-×GÐG´¸sÓ1G€DØ�KˆK�C€Eð Ò˜2š:¬°Ð8KÈU×)SÒ)Sð	Ü�Eœ:¨ |Ô4ô ˜5Ó(Ð(øô ô 	Ùð	ús   ÄD- Ä-D;Ä:D;z::c                óH   € V ^8„  d   QhR\         R\         R\         R\        /# )r   ÚproviderÚmodelÚbase_urlr   ©r   Úbool)r   s   "r   r   r   ®   s/   € ÷ Wñ W¬ð W´Cð WÄ3ð WÔPTñ Wr   c                óD  € \        T ;'       g    R4      P                  4       P                  4       R9   d   R# \        \        T;'       g    R4      4      R8X  d   R# \        T;'       g    R4      P                  4       P                  4       P	                  \
        4      # )ag  True when the cache key selects ONE server-side slot per conversation.

xAI (``xai``/``xai-oauth``, ``api.x.ai``, or Grok via OpenRouter ``x-ai/grok-*``) pins its
prompt cache to the server picked by ``x-grok-conv-id`` / ``prompt_cache_key``: two divergent
request streams under one key evict each other. Anthropic, DeepSeek and Gemini caches are
content-addressed and OpenAI's ``prompt_cache_key`` only routes over a prefix match, so the
shared scope (#109964) stays a win there. Only the OpenRouter profile reads ``cache_scope_id``
for the sticky header; other aggregators fronting Grok change only the body key.
r#   Tzapi.x.ai>   Úxaiú	xai-oauth)r   r(   Úlowerr   rC   ÚGROK_AGGREGATOR_MODEL_PREFIXES)r^   r_   r`   s   &&&r   Úis_slot_keyed_cache_routerh   ®   sw   € ô ˆ8�>ˆ>�rÓ× Ñ Ó"×(Ñ(Ó*Ð.BÔBÙÜœ˜XŸ^˜^¨Ó,Ó-°Ô;ÙÜˆu�{ˆ{˜Ó×!Ñ!Ó#×)Ñ)Ó+×6Ñ6Ô7UÓVÐVr   c                ó0   € V ^8„  d   QhR\         R\        /# )r   r[   r   ra   )r   s   "r   r   r   ¿   s   € ÷ Dñ Dœsð D¤tñ Dr   c                óD   € \        V \        4      ;'       d
    \        V 9   # )z?True when *scope* is a fork-derived scope (``<scope>::<tag>``).)r   r   ÚFORK_SCOPE_SEPARATOR)r[   s   &r   Úis_fork_cache_scoperl   ¿   s   € ä�eœSÓ!×CÐCÔ&:¸eÑ&CÐCr   c                ó<   € V ^8„  d   QhR\         R\        R\        /# )r   r   r[   r   rT   )r   s   "r   r   r   Ä   s!   € ÷ 1ñ 1œ3ð 1¤sð 1¬sñ 1r   c           	     ó.  € \        V RR4      pV'       d   \        V\        4      '       d	   V'       g   V# \        \        V RR4      R^ 4      ^8  d   V# \        \        V RR4      \        V RR4      \        V RR4      4      '       g   V# V \         V 2# )	a  ``<scope>::<tag>`` for a tagged fork on a slot-keyed route, once the fork's OWN compaction
committed: before it the fork extends the parent's prefix (warm read, #109964); after it the
rewritten stream would evict the parent's xAI slot. Read per call, so fallbacks re-evaluate.Ú_prompt_cache_fork_tagNÚcontext_compressorÚcompression_countr^   r#   r_   r`   )r)   r   r   rh   rk   )r   r[   Útags   && r   rV   rV   Ä   s™   € ô �%Ð1°4Ó
8€Cßœ
 3¬×,Ò,·CØˆÜŒw�uÐ2°DÓ9Ð;NÐPQÓRÐUVÔVØˆÜ$Ü��z 2Ó&¬°°wÀÓ(CÄWÈUÐT^Ð`bÓEc÷ò ð ˆØˆWÔ)Ð*¨3¨%Ð0Ð0r   c                óF   € V ^8„  d   QhR\         R\        \        ,          /# r7   r8   )r   s   "r   r   r   Ô   s   € ÷ ñ ¬Cð ´H¼SµMñ r   c                ól   €  \        V 4      #   \         d    \        P                  RRR7        R# i ; i)z=Never-raising variant of :func:`declared_conversation_scope`.z-declared conversation scope resolution failedTr   N)rR   r   r   r   ©r   s   &r   Ú declared_conversation_scope_saferv   Ô   s5   € ðÜ*¨5Ó1Ð1øÜô Ü�‰ÐDÈtˆÔTÚðús   ‚
 �"3²3c                óF   € V ^8„  d   QhR\         R\        \        ,          /# r7   r8   )r   s   "r   r   r   Ý   s   € ÷ ñ ¬3ð ´8¼Cµ=ñ r   c                ó€   €  \        V 4      ;'       g    R#   \         d    \        P                  RRR7        R# i ; i)uO  Never-raising variant of :func:`resolve_prompt_cache_scope` (None = use the physical id).
At turn_context an exception inside ``set_runtime_main(...)`` would skip the whole binding.

Returns None on any failure (or when there is no scope). Consumers treat None/empty as "fall back to the
physical session_id", so a resolution failure degrades to pre-#79017 behavior instead of blocking the
caller â€” important at turn_context's call site, where an exception raised inside the
``set_runtime_main(...)`` argument list would otherwise skip the whole runtime binding, not just the
cache scope.
Nz$prompt-cache scope resolution failedTr   )r\   r   r   r   ru   s   &r   Úresolve_prompt_cache_scope_safery   Ý   s=   € ðÜ)¨%Ó0×8Ð8°DÐ8øÜô Ü�‰Ð;ÀdˆÔKÚðús   ‚ ” —"=¼=)N)z
x-ai/grok-z	xai/grok-)r#   )Ú__doc__rF   ÚloggingÚtypingr   r   Úutilsr   Ú	getLoggerÚ__name__r   rW   rD   r   r,   r5   rR   r\   rk   rg   rh   rl   rV   rv   ry   © r   r   Ú<module>r�      sx   ðñ	ó Û ß  å #à	×	Ò	˜8Ó	$€à'€
ØÐ õ÷ 5õ0>õ 6/õr)ð> Ð ð "=Ð ÷Wõ"Dõ
1õ ÷r   