
    iX                      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ZddlmZm	Z	m
Z
 ddlmZ ddlmZmZmZmZmZmZ ddlmZ ddlmZ  ej        e          Zd	Zd
ZdZdZd`dZdadZ dadZ!dbdZ"dcdZ#dddZ$dadZ%dddZ&dedZ'dfdZ(ded Z)ded!Z*dgd%Z+dhdid'Z,dhdjd)Z-d*Z.d+Z/d`d,Z0dkd5Z1dld9Z2dmd;Z3	 dhdndAZ4dodLZ5dpdNZ6dqdOZ7	 	 	 drdsdUZ8dtdXZ9dudZZ:ddd[dvd_Z;dS )wu  Curator — background skill maintenance orchestrator.

The curator is an auxiliary-model task that periodically reviews agent-created
skills and maintains the collection. It runs inactivity-triggered (no cron
daemon): when the agent is idle and the last curator run was longer than
``interval_hours`` ago, ``maybe_run_curator()`` spawns a forked AIAgent to do
the review.

Responsibilities:
  - Auto-transition lifecycle states based on derived skill activity timestamps
  - Spawn a background review agent that can pin / archive / consolidate /
    patch agent-created skills via skill_manage
  - Persist curator state (last_run_at, paused, etc.) in .curator_state

Strict invariants:
  - Only touches agent-created skills (see tools/skill_usage.is_agent_created)
  - Never auto-deletes — only archives. Archive is recoverable.
  - Pinned skills bypass all auto-transitions
  - Uses the auxiliary client; never touches the main session's prompt cache
    )annotationsN)datetime	timedeltatimezone)Path)AnyCallableDictListOptionalSetget_hermes_homeskill_usage         Z   returnr   c                 *    t                      dz  dz  S )Nskillsz.curator_stater        2/home/ubuntu/.hermes/hermes-agent/agent/curator.py_state_filer   1   s    x'*:::r   Dict[str, Any]c                     d d d d dddS )NFr   )last_run_atlast_run_duration_secondslast_run_summarylast_report_pathpaused	run_countr   r   r   r   _default_stater%   5   s#    %)    r   c                     t                      } |                                 st                      S 	 t          j        |                     d                    }t          |t                    rCt                                          fd|	                                D                        S n># t          t          j        f$ r%}t                              d|           Y d }~nd }~ww xY wt                      S )Nutf-8encodingc                N    i | ]!\  }}|v s|                     d           ||"S )_)
startswith).0kvbases      r   
<dictcomp>zload_state.<locals>.<dictcomp>H   s5    YYY$!Q!t))q||TWGXGX)A)))r   z Failed to read curator state: %s)r   existsr%   jsonloads	read_text
isinstancedictupdateitemsOSErrorJSONDecodeErrorloggerdebug)pathdataer0   s      @r   
load_staterA   @   s    ==D;;==  <z$..'.::;;dD!! 	!##DKKYYYY$**,,YYYZZZK	 T)* < < <7;;;;;;;;<s   A?B4 4C/
C**C/r?   Nonec                   t                      }	 |j                            dd           t          j        t          |j                  dd          \  }}	 t          j        |dd          5 }t          j	        | |d	dd
           |
                                 t          j        |                                           d d d            n# 1 swxY w Y   t          j        ||           d S # t          $ r( 	 t          j        |           n# t           $ r Y nw xY w w xY w# t"          $ r(}t$                              d|d           Y d }~d S d }~ww xY w)NTparentsexist_okz.curator_state_z.tmp)dirprefixsuffixwr'   r(   r   F)indent	sort_keysensure_asciiz Failed to save curator state: %sexc_info)r   parentmkdirtempfilemkstempstrosfdopenr3   dumpflushfsyncfilenoreplaceBaseExceptionunlinkr:   	Exceptionr<   r=   )r?   r>   fdtmpfr@   s         r   
save_staterb   O   s   ==DK$666"s4;'7'7@QZ`aaaC	2sW555 %	$!t%PPPP			$$$% % % % % % % % % % % % % % % JsD!!!!! 	 	 		#   	  K K K7TJJJJJJJJJKss   AD' C2 0ACC2 CC2 CC2 2
D$=DD$
DD$DD$$D' '
E1EEr#   boolc                d    t                      }t          |           |d<   t          |           d S Nr#   )rA   rc   rb   )r#   states     r   
set_pausedrg   d   s.    LLE6llE(Our   c                 ^    t          t                                          d                    S re   )rc   rA   getr   r   r   	is_pausedrj   j   s"    
  **+++r   c                    	 ddl m}   |             }n4# t          $ r'}t                              d|           i cY d}~S d}~ww xY wt          |t                    si S |                    d          pi }t          |t                    si S |S )zIRead curator.* config from ~/.hermes/config.yaml. Tolerates missing file.r   load_configz%Failed to load config for curator: %sNcurator)hermes_cli.configrm   r^   r<   r=   r6   r7   ri   )rm   cfgr@   curs       r   _load_configrr   r   s    111111kmm   <a@@@						 c4   	
'')


"Cc4   	Js    
A?AAc                 d    t                      } t          |                     dd                    S )z)Default ON when no config says otherwise.enabledT)rr   rc   ri   rp   s    r   
is_enabledrv      s'    
..C	4(()))r   intc                     t                      } 	 t          |                     dt                              S # t          t
          f$ r
 t          cY S w xY w)Ninterval_hours)rr   rw   ri   DEFAULT_INTERVAL_HOURS	TypeError
ValueErrorru   s    r   get_interval_hoursr}      sZ    
..C&377+-CDDEEEz" & & &%%%%&   '8 AAfloatc                     t                      } 	 t          |                     dt                              S # t          t
          f$ r
 t          cY S w xY w)Nmin_idle_hours)rr   r   ri   DEFAULT_MIN_IDLE_HOURSr{   r|   ru   s    r   get_min_idle_hoursr      sZ    
..C&SWW-/EFFGGGz" & & &%%%%&r~   c                     t                      } 	 t          |                     dt                              S # t          t
          f$ r
 t          cY S w xY w)Nstale_after_days)rr   rw   ri   DEFAULT_STALE_AFTER_DAYSr{   r|   ru   s    r   get_stale_after_daysr      sZ    
..C(377-/GHHIIIz" ( ( (''''(r~   c                     t                      } 	 t          |                     dt                              S # t          t
          f$ r
 t          cY S w xY w)Narchive_after_days)rr   rw   ri   DEFAULT_ARCHIVE_AFTER_DAYSr{   r|   ru   s    r   get_archive_after_daysr      sZ    
..C*377/1KLLMMMz" * * *))))*r~   tsOptional[str]Optional[datetime]c                d    | sd S 	 t          j        |           S # t          t          f$ r Y d S w xY wN)r   fromisoformatr{   r|   )r   s    r   
_parse_isor      sL     t%b)))z"   tts    //nowc                   t                      sdS t                      rdS t                      }t          |                    d                    }|| t          j        t          j                  } 	 | 	                                |d<   d|d<   t          |           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY wdS | t          j        t          j                  } |j         |                    t          j                  }t!          t#                                }| |z
  |k    S )	u  Return True if the curator should run immediately.

    Gates:
      - curator.enabled == True
      - not paused
      - last_run_at present AND older than interval_hours

    First-run behavior: when there is no ``last_run_at`` (fresh install, or
    install that predates the curator), we DO NOT run immediately. The
    curator is designed to run after at least ``interval_hours`` (7 days by
    default) of skill activity, not on the first background tick after
    ``hermes update``. On first observation we seed ``last_run_at`` to "now"
    and defer the first real pass by one full interval. Users who want to
    run it sooner can always invoke ``hermes curator run`` (with or without
    ``--dry-run``) explicitly — that path bypasses this gate.

    The idle check (min_idle_hours) is applied at the call site where we know
    whether an agent is actively running — here we only enforce the static
    gates.
    Fr   Nuu   deferred first run — curator seeded, will run after one interval; use `hermes curator run --dry-run` to preview nowr!   z&Failed to seed curator last_run_at: %stzinfo)hours)rv   rj   rA   r   ri   r   r   r   utc	isoformatrb   r^   r<   r=   r   r[   r   r}   )r   rf   lastr@   intervals        r   should_run_nowr      sF   * << u{{ uLLEeii..//D| ;,x|,,C	F#&==??E- N $% u 	F 	F 	FLLA1EEEEEEEE	Fu
{l8<(({||8<|00133444H$J8##s   4+B   
C*C

CDict[str, int]c                   ddl m} | t          j        t          j                  } | t          t                                z
  }| t          t                                z
  }ddddd}|	                                D ]q}|dxx         dz  cc<   |d         }|
                    d	          r1t          |
                    d
                    }|p#t          |
                    d                    p| }|j         |                    t          j                  }|
                    d|j                  }	||k    r6|	|j        k    r+|                    |          \  }
}|
r|dxx         dz  cc<   ||k    r8|	|j        k    r-|                    ||j                   |dxx         dz  cc<   5||k    r6|	|j        k    r+|                    ||j                   |dxx         dz  cc<   s|S )zWalk every agent-created skill and move active/stale/archived based on
    the latest real activity timestamp. Pinned skills are never touched.
    Returns a counter dict describing what changed.r   r   N)days)marked_stalearchivedreactivatedcheckedr      namepinnedlast_activity_at
created_atr   rf   r   r   r   )toolsr   r   r   r   r   r   r   r   agent_created_reportri   r   r   r[   STATE_ACTIVESTATE_ARCHIVEDarchive_skill	set_stateSTATE_STALE)r   _ustale_cutoffarchive_cutoffcountsrowr   last_activityanchorcurrentok_msgs               r   apply_automatic_transitionsr      s,    ('''''
{l8<(((<(>(>????L9*@*B*BCCCCNQqQOOF&&(( ' 'yQ6{778 	"377+=#>#>?? J*SWW\-B-B"C"CJs= ^^8<^88F'''2?33^##23D(D(D''--HB (z"""a'"""|##2?(B(BLLr~...>"""a'""""l""w".'@'@LLr///=!!!Q&!!!Mr   u  ═══════════════════════════════════════════════════════════════
DRY-RUN — REPORT ONLY. DO NOT MUTATE THE SKILL LIBRARY.
═══════════════════════════════════════════════════════════════

This is a PREVIEW pass. Follow every instruction below EXCEPT:

  • DO NOT call skill_manage with action=patch, create, delete, write_file, or remove_file.
  • DO NOT call terminal to mv skill directories into .archive/.
  • DO NOT call terminal to mv, cp, rm, or rewrite any file under ~/.hermes/skills/.
  • skills_list and skill_view are FINE — read as much as you need.

Your output IS the deliverable. Produce the exact same human-readable summary and structured YAML block you would produce on a live run — but describe the actions you WOULD take, not actions you took. A downstream reviewer will read the report and decide whether to approve a live run with `hermes curator run` (no flag).

If you accidentally take a mutating action, say so explicitly in the summary so the reviewer can revert it.
═══════════════════════════════════════════════════════════════u  You are running as Hermes' background skill CURATOR. This is an UMBRELLA-BUILDING consolidation pass, not a passive audit and not a duplicate-finder.

The goal of the skill collection is a LIBRARY OF CLASS-LEVEL INSTRUCTIONS AND EXPERIENTIAL KNOWLEDGE. A collection of hundreds of narrow skills where each one captures one session's specific bug is a FAILURE of the library — not a feature. An agent searching skills matches on descriptions, not on exact names; one broad umbrella skill with labeled subsections beats five narrow siblings for discoverability, not the other way around.

The right target shape is CLASS-LEVEL skills with rich SKILL.md bodies + `references/`, `templates/`, and `scripts/` subfiles for session-specific detail — not one-session-one-skill micro-entries.

Hard rules — do not violate:
1. DO NOT touch bundled or hub-installed skills. The candidate list below is already filtered to agent-created skills only.
2. DO NOT delete any skill. Archiving (moving the skill's directory into ~/.hermes/skills/.archive/) is the maximum destructive action. Archives are recoverable; deletion is not.
3. DO NOT touch skills shown as pinned=yes. Skip them entirely.
4. DO NOT use usage counters as a reason to skip consolidation. The counters are new and often mostly zero. Judge overlap on CONTENT, not on use_count. 'use=0' is not evidence a skill is valuable; it's absence of evidence either way.
5. DO NOT reject consolidation on the grounds that 'each skill has a distinct trigger'. Pairwise distinctness is the wrong bar. The right bar is: 'would a human maintainer write this as N separate skills, or as one skill with N labeled subsections?' When the answer is the latter, merge.

How to work — not optional:
1. Scan the full candidate list. Identify PREFIX CLUSTERS (skills sharing a first word or domain keyword). Examples you are likely to find: hermes-config-*, hermes-dashboard-*, gateway-*, codex-*, ollama-*, anthropic-*, gemini-*, mcp-*, salvage-*, pr-*, competitor-*, python-*, security-*, etc. Expect 10-25 clusters.
2. For each cluster with 2+ members, do NOT ask 'are these pairs overlapping?' — ask 'what is the UMBRELLA CLASS these skills all serve? Would a maintainer name that class and write one skill for it?' If yes, pick (or create) the umbrella and absorb the siblings into it.
3. Three ways to consolidate — use the right one per cluster:
   a. MERGE INTO EXISTING UMBRELLA — one skill in the cluster is already broad enough to be the umbrella (example: `pr-triage-salvage` for the PR review cluster). Patch it to add a labeled section for each sibling's unique insight, then archive the siblings.
   b. CREATE A NEW UMBRELLA SKILL.md — no existing member is broad enough. Use skill_manage action=create to write a new class-level skill whose SKILL.md covers the shared workflow and has short labeled subsections. Archive the now-absorbed narrow siblings.
   c. DEMOTE TO REFERENCES/TEMPLATES/SCRIPTS — a sibling has narrow-but-valuable session-specific content. Move it into the umbrella's appropriate support directory:
      • `references/<topic>.md` for session-specific detail OR condensed knowledge banks (quoted research, API docs excerpts, domain notes, provider quirks, reproduction recipes)
      • `templates/<name>.<ext>` for starter files meant to be copied and modified
      • `scripts/<name>.<ext>` for statically re-runnable actions (verification scripts, fixture generators, probes)
      Then archive the old sibling. Use `terminal` with `mkdir -p ~/.hermes/skills/<umbrella>/references/ && mv ... <umbrella>/references/<topic>.md` (or templates/ / scripts/).
4. Also flag skills whose NAME is too narrow (contains a PR number, a feature codename, a specific error string, an 'audit' / 'diagnosis' / 'salvage' session artifact). These almost always belong as a subsection or support file under a class-level umbrella.
5. Iterate. After one consolidation round, scan the remaining set and look for the NEXT umbrella opportunity. Don't stop after 3 merges.

Your toolset:
  - skills_list, skill_view        — read the current landscape
  - skill_manage action=patch      — add sections to the umbrella
  - skill_manage action=create     — create a new umbrella SKILL.md
  - skill_manage action=write_file — add a references/, templates/, or scripts/ file under an existing skill (the skill must already exist)
  - skill_manage action=delete     — archive a skill. MUST pass `absorbed_into=<umbrella>` when you've merged its content into another skill, or `absorbed_into=""` when you're truly pruning with no forwarding target. This drives cron-job skill-reference migration — guessing from your YAML summary after the fact is fragile.
  - terminal                       — mv a sibling into the archive OR move its content into a support subfile

'keep' is a legitimate decision ONLY when the skill is already a class-level umbrella and none of the proposed merges would improve discoverability. 'This is narrow but distinct from its siblings' is NOT a reason to keep — it's a reason to move it under an umbrella as a subsection or support file.

Expected output: real umbrella-ification. Process every obvious cluster. If you end the pass with fewer than 10 archives, you stopped too early — go back and look at the clusters you left alone.

When done, write a human summary AND a structured machine-readable block so downstream tooling can distinguish consolidation from pruning. Format EXACTLY:

## Structured summary (required)
```yaml
consolidations:
  - from: <old-skill-name>
    into: <umbrella-skill-name>
    reason: <one short sentence — why merged, not just 'similar'>
prunings:
  - name: <skill-name>
    reason: <one short sentence — why archived with no merge target>
```

Every skill you moved to .archive/ MUST appear in exactly one of the two lists. If you consolidated X into umbrella Y (patched Y, wrote a references file to Y, or created Y with X's content absorbed), X goes under `consolidations` with `into: Y`. If you archived X with no absorption — truly stale, irrelevant, or obsolete — X goes under `prunings`. Leave a list empty (`consolidations: []`) if none. Do not omit the block. The block comes AFTER your human-readable summary of clusters processed, patches made, and decisions left alone.c                     t                      dz  dz  } 	 |                     dd           n2# t          $ r%}t                              d|           Y d}~nd}~ww xY w| S )ux  Directory where curator run reports are written.

    Lives under the profile-aware logs dir (``~/.hermes/logs/curator/``)
    alongside ``agent.log`` and ``gateway.log`` so it's found by anyone
    looking for operational telemetry, not mixed in with the user's
    authored skill data in ``~/.hermes/skills/``.

    ``ensure_hermes_home()`` pre-creates this dir on every CLI launch and
    the v22→v23 migration backfills it for existing profiles, but we
    still mkdir here as a belt-and-suspenders so the curator works even
    from an odd entry path (e.g. gateway-only install, bare library use)
    that bypasses both.
    logsrn   TrD   z%Curator reports dir create failed: %sN)r   rQ   r:   r<   r=   )rootr@   s     r   _reports_rootr     s     v%	1DA

4$
//// A A A<a@@@@@@@@AKs   . 
AAAremoved	List[str]addedafter_namesSet[str]
tool_callsList[Dict[str, Any]]Dict[str, List[Dict[str, Any]]]c                x   g }g }g }|pg D ]}t          |t                    s|                    d          dk    r2|                    d          pd}i }	t          |t                    r|}	n?t          |t                    r*	 t	          j        |          }	n# t          $ r d|i}	Y nw xY wt          |	t                    s|                    |	           t          |          t          |pg           z  }
| D ]9}|sd}d}||	                    dd          |	                    dd          h}|D ]}	|	                    d          }t          |t                    r|s/||k    r6||
vr;g }d	D ]A}|	                    |          }t          |t                    r|                    |           Bd
}|D ]?}|D ]6}|r2||v r.d}d|	                    dd           d| d| d|dd          } n7|r n@|r|} n|r|                    |||d           "|                    d|i           ;||dS )u  Split ``removed`` into consolidated vs pruned.

    A removed skill is "consolidated" when the curator absorbed its content
    into another skill (an umbrella) during this run — the content still
    lives, just under a different name. A removed skill is "pruned" when the
    curator archived it for staleness/irrelevance without preserving its
    content elsewhere.

    Heuristic: scan this run's ``skill_manage`` tool calls and look for
    ``write_file``/``patch``/``create``/``edit`` actions whose target skill
    (the ``name`` argument) is NOT the removed skill and whose
    ``file_path`` / ``file_content`` / ``content`` arguments reference the
    removed skill's name. That's the textbook "absorbed into umbrella"
    signal. Ties are broken by first-match (earliest tool call wins).

    Returns ``{"consolidated": [{"name", "into", "evidence"}, ...],
               "pruned":       [{"name"}, ...]}``.
    r   skill_manage	arguments _rawN-r+   )	file_pathfile_contentcontent
new_stringr   FTzskill_manage action=action?z on 'z' referenced 'z' in P   )r   intoevidenceconsolidatedpruned)
r6   r7   ri   rT   r3   r4   r^   appendsetr[   )r   r   r   r   r   r   parsed_callstcrawargsdestinationsr   r   r   needlestarget	haystackskeyr/   hithayneedles                         r   _classify_removed_skillsr     s*   0 *,L#%F *,LB " ""d## 	66&>>^++ff[!!'R!c4   	%DDS!! 	%%z# % % % }% $%% 	D!!!!
 {##c%+2&6&66L 0* 0* 	""& c3//c31G1GH  "	 "	DXXf%%Ffc** &  ~~ \)) $&IU ( (HHSMMa%% ($$Q'''C   %  F &C--"-488Hc3J3J - -#)- -9=- -"%crc(- - !
  E   	*t R RSSSSMM64.))))(F;;;s   BB*)B*	llm_finalrT   Dict[str, List[Dict[str, str]]]c           	        g g d}| rt          | t                    s|S ddl}|                    d| |j        |j        z            }|s|S |                    d          }	 ddl}|                    |          }n# t          $ r |cY S w xY wt          |t                    s|S g g d}|                    d          pg }|                    d          pg }	t          |t                    r|D ]}
t          |
t                    s|
                    d          }|
                    d	          }t          |t                    r=|                                r)t          |t                    r|                                s|
                    d
          }|d                             |                                |                                t          |t                    r|pd                                ndd           t          |	t                    r|	D ]}
t          |
t                    s|
                    d          }t          |t                    r|                                sW|
                    d
          }|d                             |                                t          |t                    r|pd                                ndd           |S )ui  Extract the structured YAML block from the curator's final response.

    The curator prompt requires a fenced ```yaml block under
    ``## Structured summary (required)`` with ``consolidations:`` and
    ``prunings:`` lists. This parses it tolerantly:

    - Missing block → returns empty lists (we'll fall back to heuristic).
    - Malformed YAML → returns empty lists and we rely on heuristic.
    - Partial block (e.g. only consolidations) → returns what we could parse.

    Returns ``{"consolidations": [{"from", "into", "reason"}, ...],
               "prunings":       [{"name", "reason"}, ...]}``.
    )consolidationspruningsr   Nz```ya?ml\s*\n(.*?)\n```r   r   r   fromr   reasonr   )r   r   r   r   )r   r   )r6   rT   researchDOTALL
IGNORECASEgroupyaml	safe_loadr^   r7   ri   liststripr   )r   emptyr   matchbodyr   r?   outcons_rawprun_rawentryfrmr   r   r   s                  r   _parse_structured_summaryr   4  s      "r22E Jy#66 
 IIIII"
	BM! E
  ;;q>>D~~d##    dD!! >@b+Q+QCxx())/RHxx
##)rH(D!!  	 	EeT** ))F##C99V$$DsC(( SYY[[ "4--26**,,YYx((F !((		

4>vs4K4KS6<R..000QS* *     (D!!  
	 
	EeT** 99V$$DtS)) djjll YYx((F
O""

4>vs4K4KS6<R..000QS$ $    
 Js   !A; ;B
	B
Dict[str, Dict[str, Any]]c                   i }| pg D ]q}t          |t                    s|                    d          dk    r3|                    d          pd}i }t          |t                    r|}n;t          |t                    r&	 t	          j        |          }n# t          $ r Y w xY wt          |t                    s|                    d          dk    r|                    d          }t          |t                    r|                                sd|vr|                    d          }|.t          |t                    sE|                                d	d
||                                <   s|S )u  Walk this run's tool calls and extract model-declared absorption targets.

    The curator prompt requires every ``skill_manage(action='delete')`` call
    to pass ``absorbed_into=<umbrella>`` when consolidating, or
    ``absorbed_into=""`` when truly pruning. This is the single authoritative
    signal for classification — the model's own declaration at the moment of
    deletion, which beats both post-hoc YAML summary parsing and substring
    heuristics on other tool calls.

    Returns ``{skill_name: {"into": "<umbrella>" | "", "declared": True}}``.
    Entries with ``into == ""`` are explicit prunings.
    Skills without a ``skill_manage(delete)`` call, or with one that omitted
    ``absorbed_into``, are not in the returned dict — caller falls back to
    the existing heuristic/YAML logic for those (backward compat with older
    curator runs and any callers that don't populate the arg).
    r   r   r   r   r   deleteabsorbed_intoNT)r   declared)r6   r7   ri   rT   r3   r4   r^   r   )r   r   r   r   r   r   r   s          r   #_extract_absorbed_into_declarationsr    s   & &(CB G G"d## 	66&>>^++ff[!!'R!c4   	DDS!! 	z#   $%% 	88H))xx$$$ 	DJJLL 	 $&&/**>&#&& 	%+\\^^FFDJJLLJs   B
B#"B#	heuristicmodel_blockr   absorbed_declarations#Optional[Dict[str, Dict[str, Any]]]c                   d |                     dg           D             }d |                     dg           D             }d |                     dg           D             }d |                     dg           D             }|pi }	g }
g }| D ]e}|                     |          }|                     |          }|                     |          }|	                     |          }||                     d
d          }|r\||v rX||d|r|                     d          pdndd}|r |                     d          r|d         |d<   |
                    |           |dk    r4|                    |d|r|                     d          pdndd           |ry|                     d
          |v rb||d
         d|rdndz   |                     d          pdd}|r |                     d          r|d         |d<   |
                    |           |rq|                     d
          |vrZ|r=|
                    ||d
         dd|                     dd          |d
         d           n|                    |ddd           |r7|
                    ||d
         dd|                     dd          d           .|r|                     dd          nd}|                    ||rdnd|d           g|
|dS )uL  Merge heuristic (tool-call evidence) with the model's structured block.

    Rules (evaluated in order; first match wins):
    - **Model-declared `absorbed_into` at delete time is authoritative.** Any
      entry in ``absorbed_declarations`` beats every other signal. This is
      the model telling us directly, at the moment of deletion, what it did.
      ``into != ""`` and target exists → consolidated. ``into == ""`` →
      pruned. ``into != ""`` but target doesn't exist → hallucination; fall
      through to the usual signals.
    - Model-declared consolidation wins when its ``into`` target exists
      in ``destinations`` (survived or newly-created). This gives the
      model authority over intent + rationale.
    - Model-declared consolidation whose ``into`` target does NOT exist is
      downgraded: the model hallucinated an umbrella. We prefer the
      heuristic's finding for that skill, or fall back to pruned.
    - Heuristic-only finding (model didn't mention it, tool calls confirm)
      is preserved as a consolidation, marked ``source="tool-call audit"``.
    - Model-declared pruning is accepted unless the heuristic has
      tool-call evidence that contradicts it (rare — the heuristic would
      have flagged consolidation). In that case we log both.

    Every removed skill is placed in exactly one bucket.
    c                     i | ]}|d          |S r   r   r-   r@   s     r   r1   z-_reconcile_classification.<locals>.<dictcomp>  s    III!6AIIIr   r   c                    h | ]
}|d          S r  r   r  s     r   	<setcomp>z,_reconcile_classification.<locals>.<setcomp>  s    BBB1V9BBBr   r   c                     i | ]}|d          |S )r   r   r  s     r   r1   z-_reconcile_classification.<locals>.<dictcomp>  s    NNN1!F)QNNNr   r   c                     i | ]}|d          |S r  r   r  s     r   r1   z-_reconcile_classification.<locals>.<dictcomp>  s    JJJQAfIqJJJr   r   Nr   r   z(absorbed_into (model-declared at delete)r   )r   r   sourcer   r   z'absorbed_into="" (model-declared prune))r   r  r   modelz+auditz.tool-call audit (model named missing umbrella))r   r   r  r   r   model_claimed_intoz>fallback (model named missing umbrella, no tool-call evidence)z5tool-call audit (model omitted from structured block))r   r   r  r   r   zno-evidence fallbackr   )ri   r   )r   r  r  r   r	  	heur_consheur_pruned
model_consmodel_prunedr  r   r   r   mcmphcdec
into_claimr   r   s                       r   _reconcile_classificationr    s   < JIy}}^R'H'HIIIIBBimmHb&A&ABBBKNN8H"(M(MNNNJJJ+//*b*I*IJJJL$*H)+L#%F U U^^D!!d##]]4  ll4   ?,,J 
jL88 &H:<Drvvh//52"	) )  7"&&,, 7(*:E*%##E***R I:<Drvvh//52"    
   
	"&&..L006
!%;XX<&&**0b	% %E  3bffZ(( 3$&zNj!&&&  	"&&..44 ## vJN  "z2 6 6*,V*% %      ^     
   	6
QFF:r22! !     *,3"%%%!#?gg)?
 
 	 	 	 	 )F;;;r   
started_atr   elapsed_secondsauto_countsauto_summarybefore_reportbefore_namesafter_reportllm_metaOptional[Path]c                   t                      }	 |                    dd           n3# t          $ r&}	t                              d|	           Y d}	~	dS d}	~	ww xY w|                     d          }
||
z  }d}|                                r#|dz  }||
 d| z  }|                                #	 |                    dd           n3# t          $ r&}	t                              d	|	           Y d}	~	dS d}	~	ww xY wd
 |D             }t          |                                          }t          ||z
            }t          ||z
            }d |D             }g }t          ||z            D ]y}|
                    |          pi 
                    d          }|
                    |          pi 
                    d          }|r!|r||k    r|                    |||d           zi }|
                    dg           pg D ]4}|
                    dd          }|
                    |d          dz   ||<   5t          ||||
                    dg           pg           }t          |
                    dd          pd          }t          |          t          |pg           z  }t          |
                    dg           pg           }t          |||||          }|d         }|d         }g ddd}	 d |D             } d |D             }!| s|!rddlm}"  |"| |!          }nH# t          $ r;}	t                              d|	d           g ddt%          |	          d}Y d}	~	nd}	~	ww xY wi d |                                 d!t)          |d"          d#|
                    d#d          d$|
                    d$d          d%|d&t+          |          t+          |          t+          |          t+          |          z
  t+          |          t+          |          t+          |          t+          |          t+          |          t-          |
                    d'd                    t/          |                                          d(
d)|d*|d|d|d+d, |D             d-|d.|d/|d0|
                    dd          d1|
                    d2d          d3|
                    d4          d|
                    dg           i}#	 |d5z                      t5          j        |#d"d6          d7z   d89           n2# t          $ r%}	t                              d:|	           Y d}	~	nd}	~	ww xY w	 t9          |#          }$|d;z                      |$d89           n2# t          $ r%}	t                              d<|	           Y d}	~	nd}	~	ww xY w	 t-          |
                    d'd                    dk    r2|d=z                      t5          j        |d"d6          d7z   d89           n2# t          $ r%}	t                              d>|	           Y d}	~	nd}	~	ww xY w|S )?u   Write run.json + REPORT.md under logs/curator/{YYYYMMDD-HHMMSS}/.

    Returns the report directory path on success, None if the write
    couldn't happen (caller logs and continues — reporting is best-effort).
    TrD   z$Curator report dir create failed: %sNz%Y%m%d-%H%M%Sr   r   Fz!Curator run dir create failed: %sc                d    i | ]-}t          |t                    |                    d           |.S r  r6   r7   ri   r-   rs     r   r1   z%_write_run_report.<locals>.<dictcomp>c  s3    SSS!z!T?R?RSQUU6]]ASSSr   c                d    i | ]-}t          |t                    |                    d           |.S r  r+  r,  s     r   r1   z%_write_run_report.<locals>.<dictcomp>g  s3    UUU1AtATATUaeeFmmQUUUr   rf   )r   r   tor   r   unknownr   )r   r   r   r   finalr   )r   r  r  r   r	  r   r   )rewritesjobs_updatedjobs_scannedc                    i | ]P}t          |t                    |                    d           ,|                    d          A|d          |d         QS )r   r   r+  r  s     r   r1   z%_write_run_report.<locals>.<dictcomp>  sj     
 
 
!T""
 ()uuV}}
 :;v
fIqy
 
 
r   c                r    g | ]4}t          |t                    |                    d           ,|d          5S r  r+  r  s     r   
<listcomp>z%_write_run_report.<locals>.<listcomp>  sN     
 
 
!T""
'(uuV}}
fI
 
 
r   )rewrite_skill_refsr   z%Curator cron skill rewrite failed: %srN   )r2  r3  r4  errorr   duration_secondsr   r  providerauto_transitionsr   r3  )
beforeafterdeltaarchived_this_runadded_this_runconsolidated_this_runpruned_this_runstate_transitionscron_jobs_rewrittentool_calls_totaltool_call_countsr   pruned_namesc                    g | ]
}|d          S r  r   )r-   ps     r   r7  z%_write_run_report.<locals>.<listcomp>  s    333q6333r   r   rD  cron_rewritesr   llm_summarysummary	llm_errorr9  zrun.json)rK   rM   
r'   r(   z!Curator run.json write failed: %sz	REPORT.mdz"Curator REPORT.md write failed: %szcron_rewrites.jsonz+Curator cron_rewrites.json write failed: %s)r   rQ   r^   r<   r=   strftimer2   r   keyssortedri   r   r   r   r  r  	cron.jobsr8  rT   r   roundlenrw   sumvalues
write_textr3   dumps_render_report_markdown)%r   r!  r"  r#  r$  r%  r&  r'  r   r@   stamprun_dirrI   after_by_namer   r   r   before_by_nametransitionsr   s_befores_after	tc_countsr   r  r  r   r	  classificationr   r   rK  consolidated_maprH  _rewrite_cron_refspayloadmds%                                        r   _write_run_reportrh  >  s     ??D

4$
////   ;Q???ttttt 00EUlGF
..

 -!E,,F,,, ..

 -dU3333   8!<<<ttttt
 TS|SSSMm((**++K\K/00G;-..EUUUUUN )+K{\122 P P"&&t,,277@@ $$T**0b55g>> 	P 	PH$7$7hgNNOOO !#Ill<,,2 5 5vvfi((#--a0014	$  )<<b117R	  I ,HLL",E,E,KLLK{##c%+2&6&66L @\2&&,"  /!3  N ".1LH%F 24QXY$Z$ZM

 
!
 
 


 
%
 
 
  	| 	JJJJJJ..-#  M  
 
 
<a$OOOVV	
 

j**,,E/155 	gr** 	HLLR00	
 	K 	,''%%%%L(9(99!$W!%jj%(%6%6"6{{!$[!1!1#&}'8'8'K'K#L#L #I$4$4$6$6 7 7
 
$ 	I%& 	G'( 	)* 	&+, 	33F333-. 	/0 	[12 	34 	X\\'2..56 	x||Ir2278 	X\\'**9: 	hll<44; GB=	:	))Jwqu===D 	* 	
 	
 	
 	
  = = =8!<<<<<<<<=>$W--	;	**2*@@@@ > > >91========>
G}  3344q88++77
=GGG$N  8     G G GBAFFFFFFFFG Ns   ( 
AAA/C 
C7C22C73/L# #
M(-1M##M(	2T< <
U+U&&U+/)V 
W#WWAX& &
Y0YYrJ  c                   g }|                      dd          }|                      dd          pd}t          t          |          d          \  }}|r| d| dn| d}|                    d| d	           |                      d
          pd}|                      d          pd}|                      d          pi }	|                    d| d| d| d|	                     dd           d|	                     dd           d|	                     dd          dd           |                      d          }
|
r|                    d|
 d           |                      d          pi }|                    d           |                    d|                     dd                      |                    d |                     d!d                      |                    d"|                     d#d                      |                    d$|                     d%d                      |                    d           |                      d&          pi }|                    d'           |                    d(|	                     d)d           d*d+                    d, t          |                                          D                       pd- d.           |                    d/|	                     d0d           d1           |                    d2|	                     d3d           d1           |                    d4|	                     d5d           d1           |                    d6|	                     d7d           d1           |                    d           |                      d8          pg }|r|                    d9t          |           d           |                    d:           d;}|d<|         D ]}|                     d=d>          }|                     d?d>          }|                     d@          pd                                }|                     dAd          }dB| dC| dD}|r|dE| z  }|r|	                    dF          r	|dG| dHz  }|                    |           |                     dI          r|                    dJ|dI          dK           t          |          |k    r)|                    dLt          |          |z
   dM           |                    d           |                      dN          pg }|r.|                    dOt          |           d           |                    dP           d;}|d<|         D ]}t          |t                    re|                     d=d>          }|                     d@          pd                                }dB| dD}|r|dE| z  }|                    |           ||                    dB| dD           t          |          |k    r)|                    dLt          |          |z
   dM           |                    d           |                      dQ          pg }|rn|                    dRt          |           d           |                    dS           |D ]}|                    dB| dD           |                    d           |                      d7          pg }|r|                    dTt          |           d           |D ]Y}|                    dB|                     d=           dU|                     dV           d|                     dW                      Z|                    d           |                      dX          pi }|                     dY          pg }|r|                    dZt          |           d           |                    d[           d\}|d<|         D ]&}|                     d]          p|                     d^          pd>}|                     d          pg }|                     d          pg }|                     d_          pi }|                     d`          pg } |                    dB| dad+                    |           dbd+                    |          pdc dD           |                                D ]!\  }!}"|                    dd|! db|" de           "| D ]}|                    dd| df           (t          |          |k    r)|                    dLt          |          |z
   dg           |                    d           |                      dh          pd                                }#|#r@|                    di           |                    |#           |                    d           nZ|
sX|                      dj          pd}$|$r?|                    dk           |                    |$           |                    d           |                    dl           |                    dm           |                    dn           |                    do           |                    d           d	                    |          S )pz!Render the human-readable report.r   r   r:  r   <   zm su   # Curator run — rO  r  z(not resolved)r;  r   zModel: `z` via `u   `  ·  Duration: u     ·  Agent-created skills: r=  u    → r>  z (r?  z+dz)
rN  u   > ⚠ LLM pass error: `z`
r<  z### Auto-transitions (pure, no LLM)
z- checked: r   z- marked stale: r   z0- archived (no LLM, pure time-based staleness): r   z- reactivated: r   rG  z## LLM consolidation pass
z- tool calls: **rF  z** (by name: , c              3  *   K   | ]\  }}| d | V  dS )=Nr   )r-   r.   r/   s      r   	<genexpr>z*_render_report_markdown.<locals>.<genexpr>!  s0      'Y'Ytq!1

q

'Y'Y'Y'Y'Y'Yr   none)z!- consolidated into umbrellas: **rB  z**z%- pruned (archived for staleness): **rC  z- new skills this run: **rA  u7   - state transitions (active ↔ stale ↔ archived): **rD  r   z'### Consolidated into umbrella skills (u$  _These skills were **absorbed into another skill** during this run — their content still lives, just under a different name. The original directory was moved to `~/.hermes/skills/.archive/` for safety and can be restored via `hermes curator restore <name>` if the consolidation was wrong._
2   Nr   r   r   r   r  z- `u   ` → merged into ``u    — ztool-call auditz  _(detected via z)_r  u#     ⚠ The curator's summary named `zg` as the umbrella but that skill doesn't exist post-run; showing the tool-call audit's finding instead.u
   - … and z more (see `run.json`)r   u'   ### Pruned — archived for staleness (z_These skills were archived without being merged into an umbrella (e.g. stale, unused, or judged irrelevant). Directories live under `~/.hermes/skills/.archive/`. Restore any via `hermes curator restore <name>`._
r   z### New skills this run (zX_Usually these are new class-level umbrellas created via `skill_manage action=create`._
z### State transitions (z`: r   r/  rK  r2  z)### Cron job skill references rewritten (z_Cron jobs that referenced a consolidated or pruned skill were updated in-place so they keep loading the right instructions on their next run. See `cron_rewrites.json` for the full record._
   job_namejob_idmappeddroppedz`: `u   ` → `z(none)z    - `z` (consolidated)z` dropped (pruned)z  more (see `cron_rewrites.json`)r   z## LLM final summary
rL  z## LLM summary
z## Recovery
z<- Restore an archived skill: `hermes curator restore <name>`zR- All archives live under `~/.hermes/skills/.archive/` and are recoverable by `mv`zH- See `run.json` in this directory for the full machine-readable record.)ri   divmodrw   r   joinrR  r9   rU  r   r,   r6   r7   )%rJ  linesstarteddurationminssecs	dur_labelr  provr   r9  autorb  r   SHOWr   r   r   r   r  liner   r   ntranstcron_rwcron_rewrites_listru  r=  r>  rw  rx  oldnewr1  llm_sums%                                        r   rZ  rZ    s4   EeeL"%%Guu'++0qHHr**JD$&*:4""4""""4


I	LL1g111222EE'NN..E550 0DUU8__"F	LL	+5 	+ 	+ 	+ 	+	 	+ 	+!'Ha!8!8	+ 	+?Ezz'ST?U?U	+ 	+JJw""*	+ 	+ 	+   EE+E ;9u999::: 55#$$*D	LL7888	LL7txx	15577888	LLADHH^Q$?$?AABBB	LL]DHHZYZD[D[]]^^^	LL?488M1#=#=??@@@	LL ())/RI	LL.///	LL gFJJ/A1$E$E g g"ii'Y'YviooFWFW?X?X'Y'Y'YYYc]cg g g h h h	LL_VZZ@WYZ5[5[___```	LL]DUWX9Y9Y]]]^^^	LLPVZZ8H!-L-LPPPQQQ	LL =jj!4a88= = = > > >	LL 55((.BL Us<?P?PUUUVVV*	
 	
 	
 !%4%( 	 	E99VS))D99VS))Dii))/R6688FYYx,,F999$999D )(((( 7&++,=>> 7 6F6666LLyy-.. E%@T:U E E E  
 |t##LLVc,&7&7$&>VVVWWWR UU8__"F Os6{{OOOPPPB	
 	
 	
 ETE] 	- 	-E %&& -yy--))H--3::<<$T}}} -,F,,,DT""""^5^^^,,,,v;;LLPc&kkD&8PPPQQQR EE'NN bE @U@@@AAApqqq 	% 	%ALLq$$$$R EE%&&,"E >s5zz>>>??? 	T 	TALLRquuV}}RRvRRQUU4[[RRSSSSR
 eeO$$*G Z006B ]EWAXAX]]]^^^R	
 	
 	

 '. 	A 	AEyy,,J		(0C0CJsHYYx((.BFIIg&&,"EYYx((.BFii	**0bGLL]h]]DIIf$5$5]]dii>N>N>ZRZ]]]   #LLNN J JSHsHH3HHHIIII A A?t???@@@@A!""T))LL-S!344t; - - -   	R UU;%2,,..E 	-...UR %%&&," 	LL+,,,LL!!!LL 
LL!!!	LLOPPP	LLefff	LL[\\\	LL99Ur   c                    t          j                    } | sdS dt          |            dg}| D ]}|                    d|d          d|d          d|                    d	          rd
nd d|                    dd           d|                    dd           d|                    dd           d|                    dd           d|                    d          pd            d                    |          S )zCHuman/agent-readable list of agent-created skills with usage stats.z"No agent-created skills to review.zAgent-created skills (z):
z- r   z  state=rf   z	  pinned=r   yesnoz  activity=activity_countr   z  use=	use_countz  view=
view_countz
  patches=patch_countz  last_activity=r   neverrO  )r   r   rU  r   ri   rz  )rowsr{  r-  s      r   _render_candidate_listr    sb   +--D 4335c$ii5556E 

 

D6 D DwZD D uuX8eeDD D .22D D 55a((	D D
 EE,**D D uu]A..D D UU#566A'D D		
 		
 		
 		
 99Ur   F
on_summaryOptional[Callable[[str], None]]synchronousdry_runc                    t          j        t          j                  r@	 t	          j                    }t          |          ddddn# t          $ r
 dddddY nw xY w	 ddlm	} |
                    d          }|( r&	   d|j         d           n# t          $ r Y nw xY wn4# t          $ r'}t                              d	|d
           Y d}~nd}~ww xY wt                    g }d         r|                    d          d           d         r|                    d          d           d         r|                    d          d           |rd                    |          ndt#                      }s@                                |d<   t'          |                    dd                    dz   |d<   rdnd  |d<   t+          |            fd}	|r |	             n+t-          j        |	d
d          }
|
                                                                 dS )u}  Execute a single curator review pass.

    Steps:
      1. Apply automatic state transitions (pure, no LLM).
      2. If there are agent-created skills, spawn a forked AIAgent that runs
         the LLM review prompt against the current candidate list.
      3. Update .curator_state with last_run_at and a one-line summary.
      4. Invoke *on_summary* with a user-visible description.

    If *synchronous* is True, the LLM review runs in the calling thread; the
    default is to spawn a daemon thread so the caller returns immediately.

    If *dry_run* is True, the automatic stale/archive transitions are SKIPPED
    and the LLM review pass is instructed to produce a report only — no
    skill_manage mutations, no terminal archive moves. The REPORT.md still
    gets written and ``state.last_report_path`` still records it so users
    can read what the curator WOULD have done.
    r   )r   r   r   r   )curator_backupzpre-curator-run)r   Nzcurator: snapshot created (rq  z#Curator pre-run snapshot failed: %sTrN   )r   r   z marked staler   z	 archivedr   z reactivatedrl  z
no changesr   r$   r   zdry-run auto: zauto: r!   c            
        	 t          j                    } n# t          $ r g } Y nw xY wd | D             }i }	 t                      }d|v r  d}ddddg d d}nOrt           dt
           d| }nt
           d| }t          |          }  d|                    d	d
           }nX# t          $ rK}t          	                    d|d             d| d}dd| dddg t          |          d}Y d }~nd }~ww xY wt          j        t          j                  z
                                  }t!                      }||d<   ||d<   	 t          j                    }	n# t          $ r g }	Y nw xY w	 t#          || ||	|          }
|
t          |
          |d<   n4# t          $ r'}t          	                    d|d           Y d }~nd }~ww xY wt%          |           r"	  d|            d S # t          $ r Y d S w xY wd S )Nc                b    h | ],}t          |t                    |                    d           -S r  r+  r,  s     r   r  z8run_curator_review.<locals>._llm_pass.<locals>.<setcomp>   s1    TTT!
1d@S@STfTTTr   zNo agent-created skillsz; llm: skipped (no candidates)r   zskipped (no candidates)r1  rM  r  r;  r   r9  z

z; llm: rM  	no changezCurator LLM pass failed: %sTrN   z; llm: error (rq  zerror (r    r!   )r   r!  r"  r#  r$  r%  r&  r'  r"   zCurator report write failed: %sz	curator: )r   r   r^   r  CURATOR_DRY_RUN_BANNERCURATOR_REVIEW_PROMPT_run_llm_reviewri   r<   r=   rT   r   r   r   r   total_secondsrA   rh  rb   )r$  r%  r'  candidate_listfinal_summarypromptr@   elapsedstate2r&  report_pathr#  r   r  r  rH   starts              r   	_llm_passz%run_curator_review.<locals>._llm_pass  s/   	'<>>MM 	 	 	MMM	TT}TTT#%#	355N(N::#) W< W W W8 ""$!   L1 , ,0, ,), , F !6KK>KKF*622Z|ZZHLLK4X4XZZ   
	 
	 
	LL6DLIII%G|GG1GGGM)Q>>> Q HHHHHH
	 <--5DDFF.5*+%2!"
	&;==LL 	 	 	LLL		N+  '")+))!	 	 	K &-0-=-=)* 	N 	N 	NLL:ALMMMMMMMM	N 	6 	
6}6677777   	 	sa    &&A2B+ +
D 5AC;;D E# #E21E26+F" "
G,GG(G8 8
HHzcurator-review)r   daemonr   )r   r<  summary_so_far)r   r   r   r   r   r   rU  r^   agentr  snapshot_skillsr   r<   r=   r   r   rz  rA   r   rw   ri   rb   	threadingThreadr  )r  r  r  reportr  snapr@   auto_summary_partsrf   r  r  r#  r   rH   r  s   ` `        @@@@r   run_curator_reviewr    s:   . L&&E 8		X 577Fv;; ! 	 FF  	X 	X 	X!"A1UVWWFFF	X		R,,,,,,!119J1KKDJJITYIIIJJJJ    D 	R 	R 	RLL>DLQQQQQQQQ	R,777n L!!VN%;"J"J"JKKKj D!!VJ%7"B"B"BCCCm J!!VM%:"H"H"HIII4FX499/000LL LLE @$00m ;!:!:;;a?k!(6hF#) 9< 9 9E
uQ Q Q Q Q Q Q Q Q Qf  	IdAQRRR				 oo''"&  sM   'A A$#A$( B/ 	B B/ 
B+(B/ *B++B/ /
C 9CC rp   tuple[str, str]c                x   t          |                     d          t                    r|                     di           ni }|                    d          pd}|                    d          p|                    d          pd}t          |                     d          t                    r|                     di           ni }t          |                    d          t                    r|                    di           ni }|                    d          pd                                pd}|                    d          pd                                pd}|r|dk    r|r||fS t          |                     d          t                    r|                     di           ni }t          |                    d          t                    r|                    di           ni }	|	                    d          pd}
|	                    d          pd}|
r |rt                              d	           |
|fS ||fS )
u*  Pick (provider, model) for the curator review fork.

    Curator is a regular auxiliary task slot — ``auxiliary.curator.{provider,model}``
    — so it participates in the canonical aux-model plumbing (``hermes model`` →
    auxiliary picker, the dashboard Models tab, ``auxiliary.curator.{timeout,
    base_url,api_key,extra_body}``). ``provider: "auto"`` with an empty model
    means "use the main chat model" — same default as every other aux task.

    Legacy fallback: users who configured ``curator.auxiliary.{provider,model}``
    under the previous one-off schema still work. Precedence:
      1. ``auxiliary.curator.{provider,model}`` when both are set non-auto
      2. Legacy ``curator.auxiliary.{provider,model}`` when both are set
      3. Main ``model.{provider,default/model}`` pair
    r  r;  r  defaultr   	auxiliaryrn   Nu|   curator: using deprecated curator.auxiliary.{provider,model} config — please migrate to auxiliary.curator.{provider,model})r6   ri   r7   r   r<   info)rp   _main_main_provider_main_model_aux	_cur_task_task_provider_task_model_cur_legacy_legacy_provider_legacy_models               r   _resolve_review_modelr  y  s)    %/swww/?/?$F$FNCGGGR   BEYYz**4fN))I&&B%))G*<*<BK (2#''+2F2F'M'MU377;###SUD+5dhhy6I6I4+P+PXB'''VXImmJ//52<<>>F$N==))/R6688@DK +.F22{2{** &0	0B0BD%I%IQ3779b!!!rD+5dhh{6K6KT+R+RZdhh{B'''XZG{{:..6$KK((0DM /M /N	
 	
 	
  .. ;&&r   r  c                   ddl }ddddg dd}	 ddlm} n,# t          $ r}d| |d<   |d         |d<   |cY d}~S d}~ww xY wd}d}d}d}d}		 dd	lm}
 dd
lm}  |
            }t          |          \  }}	 |||	          }|	                    d          }|	                    d          }|	                    d          }|	                    d          p|}n4# t          $ r'}t                              d|d           Y d}~nd}~ww xY w|	|d<   |pd|d<   d}	  ||	||||ddddd
  
        }d|_        d|_        t          t          j        d          5 }|                    |          5  |                    |          5  |                    |           }ddd           n# 1 swxY w Y   ddd           n# 1 swxY w Y   ddd           n# 1 swxY w Y   d}t)          |t*                    r6t-          |	                    d          pd                                          }||d<   t1          |          dk    r|dd         dz   n|pd|d<   g }t3          |dg           pg D ]}t)          |t*                    s|	                    d          pg }|D ]}t)          |t*                    s|	                    d           pi }|	                    d!          pd}|	                    d"          pd}t)          |t,                    r t1          |          d#k    r|dd#         dz   }|                    ||d$           ||d<   n*# t          $ r}d%| |d<   |d         |d<   Y d}~nd}~ww xY w|&	 |                                 n># t          $ r Y n2w xY wn-# |&	 |                                 w # t          $ r Y w w xY ww xY w|S )&a/  Spawn an AIAgent fork to run the curator review prompt.

    Returns a dict with:
      - final: full (untruncated) final response from the reviewer
      - summary: short summary suitable for state file (240-char cap)
      - model, provider: what the fork actually ran on
      - tool_calls: list of {name, arguments} for every tool call made during
        the pass (arguments may be truncated for readability)
      - error: set if the pass failed mid-run; final/summary may still be empty

    Never raises; callers get a structured failure instead.
    r   Nr   r  )AIAgentzAIAgent import failed: r9  rM  rl   )resolve_runtime_provider)	requestedtarget_modelapi_keybase_urlapi_moder;  z&Curator provider resolution failed: %sTrN   r  i'  rn   )
r  r;  r  r  r  max_iterations
quiet_modeplatformskip_context_filesskip_memoryrJ   )user_messagefinal_responser1     u   …r  _session_messagesr   functionr   r   i  )r   r   zerror: )
contextlib	run_agentr  r^   ro   rm   hermes_cli.runtime_providerr  r  ri   r<   r=   _memory_nudge_interval_skill_nudge_intervalopenrU   devnullredirect_stdoutredirect_stderrrun_conversationr6   r7   rT   r   rU  getattrr   close)r  r  result_metar  r@   _api_key	_base_url	_api_mode_resolved_provider_model_namerm   r  _cfg	_provider_rpreview_agent_devnullconv_resultr1  _callsmsgtcsr   fnr   args_raws                             r   r  r    s-    # #K%%%%%%%   <<<G!,W!5I  HIIKQ111111HHHHHH{}}!6t!<!<	;&&k
 
 
 779%%GGJ''	GGJ''	 WWZ00=I Q Q Q=q4PPPPPPPPQ 'K06BK
LAw'  #
 
 
$ /0+-.* "*c"" 	Mh''11	M 	M''11	M 	M '77V7LLK	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M 	M
 k4(( 	I(899?R@@FFHHE$G:=e**s:J:J%+"5"5QVQeZeI (*<)<bAAGR 	E 	ECc4(( '',''-2C E E!"d++ VVJ''-2vvf~~+66+..4"h,, 6X1D1D'~5Ht(CCDDDDE %+L!! 6 6 6,}}G!,W!5I6 #""$$$$    $<#""$$$$    $
 s    
?:??BC 
D
#DD
=M G/F?F(F?(F,,F?/F,0F?3G?G	GG	G
M GM GE3M N% 
M9M4/N% 4M99N% ?N 
N! N!%O)N>=O>
OO
OO)idle_for_secondsr  r  Optional[float]Optional[Dict[str, Any]]c                    	 t                      sdS | t                      dz  }| |k     rdS t          |          S # t          $ r(}t                              d|d           Y d}~dS d}~ww xY w)z~Best-effort: run a curator pass if all gates pass. Returns the result
    dict if a pass was started, else None. Never raises.Ng      @)r  zmaybe_run_curator failed: %sTrN   )r   r   r  r^   r<   r=   )r  r  
min_idle_sr@   s       r   maybe_run_curatorr  -  s     	4'+--6J*,,t!Z8888   3QFFFttttts   = = = 
A/A**A/)r   r   )r   r   )r?   r   r   rB   )r#   rc   r   rB   )r   rc   )r   rw   )r   r   )r   r   r   r   r   )r   r   r   rc   )r   r   r   r   )
r   r   r   r   r   r   r   r   r   r   )r   rT   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#  rT   r$  r   r%  r   r&  r   r'  r   r   r(  )rJ  r   r   rT   )r   rT   )NFF)r  r  r  rc   r  rc   r   r   )rp   r   r   r  )r  rT   r   r   )r  r  r  r  r   r  )<__doc__
__future__r   r3   loggingrU   rR   r  r   r   r   pathlibr   typingr   r	   r
   r   r   r   hermes_constantsr   r   r   	getLogger__name__r<   rz   r   r   r   r   r%   rA   rb   rg   rj   rr   rv   r}   r   r   r   r   r   r   r  r  r   r   r   r  r  rh  rZ  r  r  r  r  r  r   r   r   <module>r     s   * # " " " " "   				      2 2 2 2 2 2 2 2 2 2       ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; , , , , , ,      		8	$	$      ; ; ; ;      K K K K*   , , , ,    * * * *& & & && & & &( ( ( (* * * *   2$ 2$ 2$ 2$ 2$r( ( ( ( (`D 8qM t   ,i< i< i< i<XN N N Nb3 3 3 3v BF@< @< @< @< @<F} } } }@t t t tv   * 37i i i i iX(' (' (' ('VB B B BV )-26       r   