
    PL
joB                       d Z ddlmZ ddlZddlZddlmZmZ ddlm	Z	m
Z
 ddlmZ ddlmZ  eh d          Z eh d	          Z ed
           G d d                      Z ed
           G d d                      Z ed
           G d d                      Zd,dZd-dZ G d d          Zd.dZd/d Zd0d"Zd1d#Zd2d(Zd3d*Zd4d+ZdS )5a"  Pure tool-call loop guardrail primitives.

The controller in this module is intentionally side-effect free: it tracks
per-turn tool-call observations and returns decisions. Runtime code owns whether
those decisions become warning guidance, synthetic tool results, or controlled
turn halts.
    )annotationsN)	dataclassfield)AnyMapping)safe_json_loads)file_mutation_result_landed>   	read_file
web_searchweb_extractsearch_filessession_searchbrowser_consolebrowser_snapshotbrowser_get_imagesmcp_filesystem_read_filemcp_filesystem_search_filesmcp_filesystem_get_file_infomcp_filesystem_directory_treemcp_filesystem_list_directorymcp_filesystem_read_text_file"mcp_filesystem_read_multiple_files(mcp_filesystem_list_directory_with_sizes>   todopatchmemorycronjobprocessterminal
write_filebrowser_typeexecute_codesend_messageskill_managebrowser_clickbrowser_pressdelegate_taskbrowser_scrollbrowser_navigateT)frozenc                      e Zd ZU dZdZded<   dZded<   dZded	<   d
Zded<   dZ	ded<   dZ
ded<   dZded<   d
Zded<    ed           Zded<    ed           Zded<   edd            ZdS )ToolCallGuardrailConfiga  Thresholds for per-turn tool-call loop detection.

    Warnings are enabled by default and never prevent tool execution. Hard stops
    are explicit opt-in so interactive CLI/TUI sessions get a gentle nudge unless
    the user enables circuit-breaker behavior in config.yaml.
    Tboolwarnings_enabledFhard_stop_enabled   intexact_failure_warn_after   exact_failure_block_after   same_tool_failure_warn_after   same_tool_failure_halt_afterno_progress_warn_afterno_progress_block_afterc                     t           S N)IDEMPOTENT_TOOL_NAMES     9/home/kuhnn/.hermes/hermes-agent/agent/tool_guardrails.py<lambda>z ToolCallGuardrailConfig.<lambda>P   s    EZ r?   )default_factoryzfrozenset[str]idempotent_toolsc                     t           S r<   )MUTATING_TOOL_NAMESr>   r?   r@   rA   z ToolCallGuardrailConfig.<lambda>Q   s    CV r?   mutating_toolsdataMapping[str, Any] | Nonereturn'ToolCallGuardrailConfig'c                z   t          |t                    s
 |             S |                    d          }t          |t                    si }|                    d          }t          |t                    si } |             } | t          |                    d          |j                  t          |                    d          |j                  t          |                    d|                    d                    |j                  t          |                    d|                    d                    |j                  t          |                    d	|                    d
                    |j	                  t          |                    d|                    d                    |j
                  t          |                    d|                    d                    |j                  t          |                    d	|                    d                    |j                            S )zABuild config from the `tool_loop_guardrails` config.yaml section.
warn_afterhard_stop_afterr.   r/   exact_failurer2   same_tool_failurer6   idempotent_no_progressr9   r4   r8   r:   )r.   r/   r2   r6   r9   r4   r8   r:   )
isinstancer   get_as_boolr.   r/   _positive_intr2   r6   r9   r4   r8   r:   )clsrG   rL   rM   defaultss        r@   from_mappingz$ToolCallGuardrailConfig.from_mappingS   s
    $(( 	355LXXl++
*g.. 	J((#455/733 	! O355s%dhh/A&B&BHD]^^&txx0C'D'DhF`aa%29S0T0TUU1& & *72DHH=[4\4\]]5* * $17BZ9[9[\\/$ $ '4##OTXX>Y5Z5Z[[2' ' *7##$7B`9a9abb5* * %2##$<dhhG`>a>abb0% %/
 
 
 	
r?   N)rG   rH   rI   rJ   )__name__
__module____qualname____doc__r.   __annotations__r/   r2   r4   r6   r8   r9   r:   r   rC   rF   classmethodrW   r>   r?   r@   r,   r,   ?   s          "!!!!#####$%%%%%%&&&&&() ))))() ))))"######$$$$$',u=Z=Z'['['[[[[[%*U;V;V%W%W%WNWWWW(
 (
 (
 [(
 (
 (
r?   r,   c                  H    e Zd ZU dZded<   ded<   edd	            ZddZdS )ToolCallSignaturezDStable, non-reversible identity for a tool name plus canonical args.str	tool_name	args_hashargsrH   rI   'ToolCallSignature'c                X    t          |pi           } | |t          |                    S )Nra   rb   )canonical_tool_args_sha256)rU   ra   rc   	canonicals       r@   	from_callzToolCallSignature.from_call   s0    '
33	sY')2D2DEEEEr?   dict[str, str]c                     | j         | j        dS )z3Return public metadata without raw argument values.rf   rf   selfs    r@   to_metadatazToolCallSignature.to_metadata   s    !^$.IIIr?   N)ra   r`   rc   rH   rI   rd   )rI   rk   )rX   rY   rZ   r[   r\   r]   rj   ro   r>   r?   r@   r_   r_      sj         NNNNNNNNF F F [FJ J J J J Jr?   r_   c                      e Zd ZU dZdZded<   dZded<   dZded<   dZded<   d	Z	d
ed<   dZ
ded<   edd            Zedd            ZddZdS )ToolGuardrailDecisionz8Decision returned by the tool-call guardrail controller.allowr`   actioncode messagera   r   r1   countNzToolCallSignature | None	signaturerI   r-   c                    | j         dv S )N>   warnrr   rs   rm   s    r@   allows_executionz&ToolGuardrailDecision.allows_execution       {///r?   c                    | j         dv S )N>   haltblockr{   rm   s    r@   should_haltz!ToolGuardrailDecision.should_halt   r}   r?   dict[str, Any]c                    | j         | j        | j        | j        | j        d}| j        | j                                        |d<   |S )N)rs   rt   rv   ra   rw   rx   )rs   rt   rv   ra   rw   rx   ro   )rn   rG   s     r@   ro   z!ToolGuardrailDecision.to_metadata   sN    kI|Z 
  
 >% $ : : < <Dr?   )rI   r-   )rI   r   )rX   rY   rZ   r[   rs   r\   rt   rv   ra   rw   rx   propertyr|   r   ro   r>   r?   r@   rq   rq      s         BBFDGIENNNN*.I....0 0 0 X0 0 0 0 X0
 
 
 
 
 
r?   rq   rc   Mapping[str, Any]rI   r`   c                    t          | t                    s$t          dt          |           j                   t          j        | dddt                    S )z5Return sorted compact JSON for parsed tool arguments.z!tool args must be a mapping, got FT,:ensure_ascii	sort_keys
separatorsdefault)rQ   r   	TypeErrortyperX   jsondumpsr`   rc   s    r@   rg   rg      s]    dG$$ SQDJJ<OQQRRR:   r?   ra   result
str | Nonetuple[bool, str]c                    |dS t          | |          rdS | dk    rKt          |          }t          |t                    r%|                    d          }||dk    rdd| dfS dS | d	k    rUt          |          }t          |t                    r1|                    d
          du rd|                    dd          v rdS |dd                                         }d|v sd|v s|                    d          rdS dS )a  Safety-fallback classifier used only when callers don't pass ``failed``.

    Mirrors ``agent.display._detect_tool_failure`` exactly so the guardrail
    never disagrees with the CLI's user-visible ``[error]`` tag. Production
    callers in ``run_agent.py`` always pass an explicit ``failed=`` derived
    from ``_detect_tool_failure``; this function exists so standalone callers
    (tests, tooling) still get consistent behavior.
    N)Fru   r   	exit_coder   Tz [exit ]r   successFzexceed the limiterrorru   )Tz [full]i  z"error"z"failed"Error)Tz [error])r	   r   rQ   dictrR   lower
startswith)ra   r   rG   r   r   s        r@   classify_tool_failurer      s6    ~y"9f55 yJv&&dD!! 	4--I$a3y33333yHv&&dD!! 	'xx	""e++0BdhhwXZF[F[0[0[&4C4L  EEZ500F4E4Eg4N4N09r?   c                  Z    e Zd ZdZdddZddZedd
            ZddZddddZ	ddZ
dS ) ToolCallGuardrailControllerzCPer-turn controller for repeated failed/non-progressing tool calls.NconfigToolCallGuardrailConfig | Nonec                X    |pt                      | _        |                                  d S r<   )r,   r   reset_for_turn)rn   r   s     r@   __init__z$ToolCallGuardrailController.__init__   s-    9 7 9 9r?   rI   Nonec                >    i | _         i | _        i | _        d | _        d S r<   )_exact_failure_counts_same_tool_failure_counts_no_progress_halt_decisionrm   s    r@   r   z*ToolCallGuardrailController.reset_for_turn   s(    CE"9;&FH<@r?   ToolGuardrailDecision | Nonec                    | j         S r<   )r   rm   s    r@   halt_decisionz)ToolCallGuardrailController.halt_decision   s    ""r?   ra   r`   rc   rH   rq   c           	     $   t                               |t          |                    }| j        j        st          ||          S | j                            |d          }|| j        j        k    r%t          ddd| d| d|||          }|| _	        |S | 
                    |          rV| j                            |          }|:|\  }}|| j        j        k    r%t          dd	d| d
| d|||          }|| _	        |S t          ||          S )Nra   rx   r   r   repeated_exact_failure_blockzBlocked z: the same tool call failed zd times with identical arguments. Stop retrying it unchanged; change strategy or explain the blocker.rs   rt   rv   ra   rw   rx   idempotent_no_progress_blockz/: this read-only call returned the same result z^ times. Stop repeating it unchanged; use the result already provided or try a different query.)r_   rj   _coerce_argsr   r/   rq   r   rR   r4   r   _is_idempotentr   r:   )	rn   ra   rc   rx   exact_countdecisionrecord_result_hashrepeat_counts	            r@   before_callz'ToolCallGuardrailController.before_call   sz   %//	<;M;MNN	{, 	S(9	RRRR044YBB$+???,3>y > >k > > > $!#  H #+DOy)) 	$&**955F!-3*l4;#FFF4&;Xy X X&2X X X #,*"+     H +3D'#O$yINNNNr?   )failedr   r   r   bool | Nonec          	        t          |          }t                              ||          }|t          ||          \  }}|r,| j                            |d          dz   }|| j        |<   | j                            |d            | j                            |d          dz   }|| j        |<   | j	        j
        r5|| j	        j        k    r%t          ddd| d| d|||          }	|	| _        |	S | j	        j        r+|| j	        j        k    rt          d	d
| d| d|||          S | j	        j        r+|| j	        j        k    rt          d	d| d| d|||          S t          |||          S | j                            |d            | j                            |d            |                     |          s,| j                            |d            t          ||          S t%          |          }
| j                            |          }d}||d         |
k    r|d         dz   }|
|f| j        |<   | j	        j        r+|| j	        j        k    rt          d	d| d| d|||          S t          |||          S )Nr      r   same_tool_failure_haltzStopped z: it failed z[ times this turn. Stop retrying the same failing tool path and choose a different approach.r   rz   repeated_exact_failure_warningz has failed z times with identical arguments. This looks like a loop; inspect the error and change strategy instead of retrying it unchanged.same_tool_failure_warningzJ times this turn. This looks like a loop; change approach before retrying.)ra   rw   rx   r   idempotent_no_progress_warningz returned the same result z^ times. Use the result already provided or change the query instead of repeating it unchanged.)r   r_   rj   r   r   rR   r   popr   r   r/   r8   rq   r   r.   r2   r6   r   r   r9   )rn   ra   rc   r   r   rx   _r   
same_countr   result_hashpreviousr   s                r@   
after_callz&ToolCallGuardrailController.after_call  sY    D!!%//	4@@	>-i@@IFA 2	f488AFFJK4?D&y1!!)T2227;;IqIIAMJ8BD*95{,  t{?g1g1g0!1d9 d d* d d d ($'
 
 
 '/#{+ t{?c0c0c,!9$ < <+ < < < (%'    {+ 
dk>f0f0f,!4$ S S* S S S ($'
 
 
 
 )9K[deeee"&&y$777&**9d;;;""9-- 	S!!)T222(9	RRRR"6**$((33HQK;$>$>#A;?L(3\'B)$;' 	LDK<^,^,^(5  . .L . . . $"#    %yXabbbbr?   r-   c                >    || j         j        v rdS || j         j        v S )NF)r   rF   rC   )rn   ra   s     r@   r   z*ToolCallGuardrailController._is_idempotent|  s'    2225DK888r?   r<   )r   r   )rI   r   )rI   r   )ra   r`   rc   rH   rI   rq   )
ra   r`   rc   rH   r   r   r   r   rI   rq   )ra   r`   rI   r-   )rX   rY   rZ   r[   r   r   r   r   r   r   r   r>   r?   r@   r   r      s        MM    A A A A # # # X#*O *O *O *Od #]c ]c ]c ]c ]c ]c~9 9 9 9 9 9r?   r   r   c                b    t          j        | j        |                                 dd          S )zCBuild a synthetic role=tool content string for a blocked tool call.)r   	guardrailF)r   )r   r   rv   ro   )r   s    r@   toolguard_synthetic_resultr     s?    :%!--//	
 	
    r?   c           	         |j         dvs|j        s| S |j         dk    rdnd}d| d|j         d|j         d|j         d		}| pd
|z   S )z;Append runtime guidance to the current tool result content.>   r   rz   r   zTool loop hard stopzTool loop warningz

[z: z; count=z; r   ru   )rs   rv   rt   rw   )r   r   labelsuffixs       r@   append_toolguard_guidancer     s    ...h6F.%-_%>%>!!DWE	H 	H 	H=	H 	H"*.	H 	H4<4D	H 	H 	H  LbF""r?   rH   c                4    t          | t                    r| ni S r<   )rQ   r   r   s    r@   r   r     s    dG,,444"4r?   c                    t          | pd          }|?	 t          j        |dddt                    }n## t          $ r t          |          }Y nw xY w| pd}t          |          S )Nru   FTr   r   )r   r   r   r`   r   rh   )r   parsedri   s      r@   r   r     s    V\r**F		$
"%  II  	$ 	$ 	$FIII	$ Lb	9s   4 AAvaluer   r   r-   c                    | |S t          | t                    r| S t          | t          t          f          rt          |           S t          | t                    r2|                                                                 }|dv rdS |dv rdS |S )N>   1onyestrueenabledT>   0noofffalsedisabledF)rQ   r-   r1   floatr`   stripr   )r   r   lowereds      r@   rS   rS     s    }% %#u&& E{{% ++--%%'';;;4===5Nr?   r1   c                r    | |S 	 t          |           }n# t          t          f$ r |cY S w xY w|dk    r|n|S )Nr   )r1   r   
ValueError)r   r   r   s      r@   rT   rT     sY    }Uz"   q[[66g-s    ,,c                t    t          j        |                     d                                                    S )Nzutf-8)hashlibsha256encode	hexdigest)r   s    r@   rh   rh     s*    >%,,w//00::<<<r?   )rc   r   rI   r`   )ra   r`   r   r   rI   r   )r   rq   rI   r`   )r   r`   r   rq   rI   r`   )rc   rH   rI   r   )r   r   rI   r`   )r   r   r   r-   rI   r-   )r   r   r   r1   rI   r1   )r   r`   rI   r`   )r[   
__future__r   r   r   dataclassesr   r   typingr   r   utilsr    agent.tool_result_classificationr	   	frozensetr=   rE   r,   r_   rq   rg   r   r   r   r   r   r   rS   rT   rh   r>   r?   r@   <module>r      s    # " " " " "   ( ( ( ( ( ( ( (         ! ! ! ! ! ! H H H H H H "	    *  i    , $<
 <
 <
 <
 <
 <
 <
 <
~ $J J J J J J J J  $       >
 
 
 
       F_9 _9 _9 _9 _9 _9 _9 _9D   	# 	# 	# 	#5 5 5 5   $    . . . .= = = = = =r?   