
    jh j                       U d Z ddlmZ ddlZddlZddlZddlmZmZ ddl	m
Z
mZ  ej        e          ZdZdZdd
ZddZddZddZddZddZddZdddZdZdaded<   ddZdd"Zdd$Zd%d&dd+Zdd-Z dd/Z!dd0Z"dd1Z#dd2Z$dd3Z%dd4Z&dd5Z'dd6Z(dd7Z)dd8Z*d9Z+d:Z,dd<Z-d=d>d?d@e+dA e-            dBg dCdDZ.dEdFd?d@dGdAd@g dHdIdJd@dKdAdLdMdAdNdOdA e-            dPg dCdDZ/dQdRd?d@e+dAd@dSdAd?dTdAd@dUdAdVdWd@idXdYdVdWd@idZdY e-            d[g dCdDZ0d\d]d?d@e+dAd@d^dA e-            d_d`gdCdDZ1dadbd?d@e+dAd@dcdA e-            ddg dCdDZ2dedfd?d@dgdAd@dhdA e-            diddjgdCdDZ3dkdld?i dmd@dndAdod@dpdAdjd@dqdAdrdVdWd@idsdYdtd@dudAdvdNdwdAdxd@g dydzdJd{d@d|dAd}dLd~dAdd@ddAddNddAdd@ddgddJddVdWd@iddYddLddAddNddAd e-            dmdogdCdDZ4ddd?d@ddA e-            dBdgdCdDZ5ddd?d@ddAd@ddA e-            dddgdCdDZ6 e
j7        d=de.e"ed            e
j7        dEde/e#ed            e
j7        dQde0e$ed            e
j7        d\de1e%ed            e
j7        dade2e&ed            e
j7        dede3e'ed            e
j7        dkde4e(ed            e
j7        dde5e)ed            e
j7        dde6e*ed           dS )uz  Kanban tools — structured tool-call surface for worker + orchestrator agents.

These tools are registered into the model's schema when the agent is
running under the dispatcher (env var ``HERMES_KANBAN_TASK`` set) or when
the active profile explicitly enables the ``kanban`` toolset for
orchestrator work. A normal ``hermes chat`` session still sees **zero**
kanban tools in its schema unless configured.

Why tools instead of just shelling out to ``hermes kanban``?

1. **Backend portability.** A worker whose terminal tool points at Docker
   / Modal / Singularity / SSH would run ``hermes kanban complete …``
   inside the container, where ``hermes`` isn't installed and the DB
   isn't mounted. Tools run in the agent's Python process, so they
   always reach ``~/.hermes/kanban.db`` regardless of terminal backend.

2. **No shell-quoting footguns.** Passing ``--metadata '{"x": [...]}'``
   through shlex+argparse is fragile. Structured tool args skip it.

3. **Better errors.** Tool-call failures return structured JSON the
   model can reason about, not stderr strings it has to parse.

Humans continue to use the CLI (``hermes kanban …``), the dashboard
(``hermes dashboard``), and the slash command (``/kanban …``) — all
three bypass the agent entirely. The tools are for dispatcher-spawned
worker handoffs and for configured orchestrator profiles that route work
through the board.
    )annotationsN)AnyOptional)registry
tool_error2      returnboolc                 z    	 ddl m}   |             }|                    dg           }d|v S # t          $ r Y dS w xY w)Nr   )load_configtoolsetskanbanF)hermes_cli.configr   get	Exception)r   cfgr   s      7/home/ubuntu/.hermes/hermes-agent/tools/kanban_tools.py_profile_has_kanban_toolsetr   1   sd    111111kmm77:r**8##   uus   ), 
::c                 `    t           j                            d          rdS t                      S )a  Task-lifecycle tools are available when:

    1. ``HERMES_KANBAN_TASK`` is set (dispatcher-spawned worker), OR
    2. The current profile has ``kanban`` in its toolsets config
       (orchestrator profiles like techlead that route work via Kanban).

    Humans running ``hermes chat`` without the kanban toolset see zero
    kanban tools. Workers spawned by the kanban dispatcher (gateway-
    embedded by default) and orchestrator profiles with the kanban
    toolset enabled see the Kanban lifecycle tool surface.
    HERMES_KANBAN_TASKTosenvironr   r        r   _check_kanban_moder   >   s,     
z~~*++ t&(((r   c                 `    t           j                            d          rdS t                      S )a  Board-routing tools (kanban_list, kanban_unblock) are intentionally
    hidden from task workers.

    Dispatcher-spawned workers should close their own task via the
    lifecycle tools (complete/block/heartbeat), not enumerate or unblock
    board state. Profiles that explicitly opt into the kanban toolset
    and are NOT scoped to a single task are the orchestrator surface.
    r   Fr   r   r   r   _check_kanban_orchestrator_moder   O   s,     
z~~*++ u&(((r   argOptional[str]c                P    | r| S t           j                            d          }|pdS )zGResolve ``task_id`` arg or fall back to the env var the dispatcher set.r   N)r   r   r   )r    env_tids     r   _default_task_idr$   a   s-    
 
jnn122G?dr   task_idstrOptional[int]c                    t           j                            d          | k    rdS t           j                            d          }|sdS 	 t          |          S # t          $ r Y dS w xY w)zDReturn this worker's dispatcher run id when it is scoped to task_id.r   NHERMES_KANBAN_RUN_ID)r   r   r   int
ValueError)r%   raws     r   _worker_run_idr-   i   st    	z~~*++w66t
*../
0
0C t3xx   tts   
A 
A'&A'metadataOptional[dict]c                    t           j                            d          | k    r|S t           j                            d          }|s|S t          |pi           }||d<   |S )zBAdd trusted worker session id metadata for this worker's own task.r   HERMES_SESSION_IDworker_session_id)r   r   r   dict)r%   r.   
session_idstampeds       r   _stamp_worker_session_metadatar6   v   se     
z~~*++w66 344J 8>r""G#-G Nr   tidc                    t           j                            d          }|sdS | |k    rt          d| d|  d          S dS )u_  Reject worker-driven destructive calls on foreign task IDs.

    A process spawned by the dispatcher has ``HERMES_KANBAN_TASK`` set
    to its own task id. Tools like ``kanban_complete`` / ``kanban_block``
    / ``kanban_heartbeat`` mutate run-lifecycle state, so a buggy or
    prompt-injected worker that passed an explicit ``task_id`` for some
    other task could corrupt sibling or cross-tenant runs (see #19534).

    Orchestrator profiles (kanban toolset enabled but **no**
    ``HERMES_KANBAN_TASK`` in env) aren't subject to this check — their
    job is routing, and they sometimes legitimately close out child
    tasks or reopen blocked ones. Workers are narrowly scoped to their
    one task.

    Returns ``None`` when the call is allowed, or a tool-error string
    when it must be rejected. Callers should ``return`` the error
    verbatim.
    r   Nzworker is scoped to task z; refusing to mutate zf. Use kanban_comment to hand off information to other tasks, or kanban_create to spawn follow-up work.r   r   r   r   )r7   r#   s     r   _enforce_worker_task_ownershipr:      sp    & jnn122G t
g~~@ @ @@ @ @
 
 	

 4r   boardc                >    ddl m} ||                    |           fS )u  Import + connect lazily so the module imports cleanly in non-kanban
    contexts (e.g. test rigs that import every tool module).

    When ``board`` is provided it's forwarded to :func:`kb.connect`, which
    routes the connection to that board's sqlite file. ``None`` (the
    default) preserves the legacy resolution chain
    (``HERMES_KANBAN_DB`` → ``HERMES_KANBAN_BOARD`` env → current symlink
    → ``default``). Per-tool ``board`` lets a Telegram-side agent override
    the env-pinned active board without restarting Hermes.
    r   )	kanban_dbr;   )
hermes_clir=   connect)r;   kbs     r   _connectrB      s/     +*****rzzz&&&&r   g      N@g        float_auto_heartbeat_last_attemptc                    t           j                            d          } | sdS ddl}|                                }|t
          z
  t          k     rdS |a	 t                      \  }}	 t           j                            d          }	 |                    || |           n,# t          $ r t                              dd	           Y nw xY wt           j                            d
          }	 |rt          |          nd}n# t          t          f$ r d}Y nw xY w	 |                    || d|           n,# t          $ r t                              dd	           Y nw xY w	 |                                 n:# t          $ r Y n.w xY w# 	 |                                 w # t          $ r Y w w xY wxY wdS # t          $ r  t                              dd	           Y dS w xY w)u  Best-effort: extend the kanban claim + bump board heartbeat for the
    current dispatcher-spawned worker, using identity from env vars.

    Returns True if a write was attempted (whether or not it succeeded);
    False if the call was skipped (not a kanban worker, rate-limited, or
    swallowed exception). The boolean is informational — callers should
    not branch on it.

    Identity comes from:
      * ``HERMES_KANBAN_TASK`` — task id (required; absence means no-op)
      * ``HERMES_KANBAN_RUN_ID`` — pins the run row so we don't heartbeat
        a stale run that may have already been reclaimed
      * ``HERMES_KANBAN_CLAIM_LOCK`` — claim lock for ``heartbeat_claim``;
        falls back to the default ``_claimer_id()`` for locally-driven
        workers that never went through the dispatcher path

    Rate-limited via the module-level ``_auto_heartbeat_last_attempt``
    timestamp (monotonic clock); not thread-safe in the strict sense, but
    the worst case is one extra DB write per race, which is harmless.
    r   Fr   NHERMES_KANBAN_CLAIM_LOCKclaimerz&auto-heartbeat: heartbeat_claim failedT)exc_infor)   noteexpected_run_idz'auto-heartbeat: heartbeat_worker failedzauto-heartbeat: bridge failed)r   r   r   time	monotonicrD   $_AUTO_HEARTBEAT_MIN_INTERVAL_SECONDSrB   heartbeat_claimr   loggerdebugr*   	TypeErrorr+   heartbeat_workerclose)r7   _timenowrA   conn
claim_lock
run_id_rawrun_ids           r   !heartbeat_current_worker_from_envr\      sN   , *..-
.
.C u
//

C**.RRRu#& ::D	(BCCJV""4j"AAAA V V VEPTUUUUUV(>??J,6@ZDz*   W##D#D&#QQQQ W W WFQUVVVVVW

   

   t   4tDDDuus   F1 &F B F &CF C"F +C? >F ?DF DF D3 2F 3&EF EF  E5 4F1 5
F?F1 FF1 F,FF,
F)&F,(F))F,,F1 1&GGfieldsr   c                 2    t          j        ddi|           S )NokT)jsondumps)r]   s    r   _okrb     s    :tT,V,---r   valuec                    | dS t          |                                           }|r|                                dv rdS |S )zANormalize CLI-compatible assignee sentinels for the tool surface.N>   -nonenull)r&   striplower)rc   texts     r   _normalize_profilerk     sH    }tu::D 4::<<#888tKr   F)defaultargsr3   namerl   c                   |                      |          }||d fS t          |t                    r|d fS t          |                                                                          }|dv rdS |dv rdS || dfS )N>   1yestrue)TN>   0nofalse)FNz$ must be a boolean or 'true'/'false')r   
isinstancer   r&   rh   ri   )rm   rn   rl   rc   rj   s        r   _parse_bool_argrw     s    HHTNNE}}% d{u::##%%D###z###{tAAAAAr   	tool_namec                h    t           j                            d          rt          |  d          S dS )a  Belt-and-suspenders runtime guard for orchestrator-only handlers.

    The check_fn (`_check_kanban_orchestrator_mode`) keeps these tools
    out of the worker schema entirely, but in case a stale registration
    or test harness routes a worker to one of them anyway, return a
    structured tool_error so the model gets a clear refusal instead of
    silently mutating board state from a worker context.
    r   z is orchestrator-only; dispatcher-spawned workers must use kanban_complete, kanban_block, kanban_heartbeat, or kanban_comment for their assigned task.Nr9   )rx   s    r   _require_orchestrator_toolrz   #  sF     
z~~*++ 
 6 6 6
 
 	

 4r   dict[str, Any]c                   |                      ||j                  }|                     ||j                  }i d|j        d|j        d|j        d|j        d|j        d|j        d|j        d|j	        d	|j
        d
|j        d|j        d|j        d|j        d|j        d|d|dt!          |          dt!          |          iS )z+Compact task shape for board-listing tools.idtitleassigneestatusprioritytenantworkspace_kindworkspace_path
created_by
created_at
started_atcompleted_atcurrent_run_idmodel_overrideparentschildrenparent_countchild_count)
parent_idsr}   	child_idsr~   r   r   r   r   r   r   r   r   r   r   r   r   len)rA   rX   taskr   r   s        r   _task_summary_dictr   5  s<   mmD$'**G||D$'**Hdg 	DM 	$+	
 	DM 	$+ 	$- 	$- 	do 	do 	do 	) 	$- 	$- 	7  	H!" 	G#$ 	s8}}%  r   c                   t          |                     d                    }|st          d          S |                     d          }	 t          |          \  }}	 |                    ||          }|'t          d| d          |                                 S |                    ||          }|                    ||          }|                    ||          }	|	                    ||          }
|
                    ||          }d }d	 t          j         ||          |
|d
 |D             d |dd         D             fd|	D             |                    ||          d          |                                 S # |                                 w xY w# t          $ r}t          d|           cY d}~S d}~wt          $ r6}t                               d           t          d|           cY d}~S d}~ww xY w)zsRead a task's full state: task row, parents, children, comments,
    runs (attempt history), and the last N events.r%   :task_id is required (or set HERMES_KANBAN_TASK in the env)r;   r>   Nztask z
 not foundc                   i d| j         d| j        d| j        d| j        d| j        d| j        d| j        d| j        d	| j        d
| j	        d| j
        d| j        d| j        d| j        d| j        d| j        S )Nr}   r~   bodyr   r   r   r   r   r   r   r   r   r   resultr   r   )r}   r~   r   r   r   r   r   r   r   r   r   r   r   r   r   r   )ts    r   
_task_dictz _handle_show.<locals>._task_dicth  s    !$ '28!&
,4ah ah )3AJ %a&6	
 %a&6 !!, 1=al !!, #AN ah %a&6 %a&6 r   c           
     t    | j         | j        | j        | j        | j        | j        | j        | j        | j        d	S )N	r}   profiler   outcomesummaryerrorr.   r   ended_atr   )rs    r   	_run_dictz_handle_show.<locals>._run_dictw  s:    $19h19 y17 !
"#,AJ  r   c                8    g | ]}|j         |j        |j        d S )authorr   r   r   .0cs     r   
<listcomp>z _handle_show.<locals>.<listcomp>  s?          !x#$<1 1  r   c                D    g | ]}|j         |j        |j        |j        d S )kindpayloadr   r[   r   )r   es     r   r   z _handle_show.<locals>.<listcomp>  sE         V	#$<18E E  r   ic                &    g | ]} |          S r   r   )r   r   r   s     r   r   z _handle_show.<locals>.<listcomp>  s!    444!1444r   )r   r   r   commentseventsrunsworker_contextzkanban_show: zkanban_show failed)r$   r   r   rB   get_taskrU   list_commentslist_events	list_runsr   r   r`   ra   build_worker_contextr+   r   rQ   	exception)rm   kwr7   r;   rA   rX   r   r   r   r   r   r   r   r   r   s                 @r   _handle_showr   S  s    488I..
/
/C 
H
 
 	
 HHWE@/%(((D8	;;tS))D|!"9#"9"9"9::j JJLLLLi ''c22H^^D#..F<<c**DmmD#..G||D#..H     :"
4(("$  &  
  $CDD\  
 5444t444
 #%"9"9$"D"D'   , JJLLLLDJJLLLL / / /-!--........ / / /-...-!--......../sU   F  *F 	F  CF 2F  FF   
H*G;HH+G?9H?Hc           
        t          d          }|r|S |                     d          }|                     d          }|                     d          }t          | d          \  }}|rt          |          S |                     d          }|t          }	 t          |          }n&# t          t          f$ r t          d          cY S w xY w|d	k     rt          d
          S |t          k    rt          dt                     S |                     d          }		 t          |	          \  	 
                              }
                    |||||d	z             }t          |          |k    }|d|         }t          j        fd|D             t          |          |||r#|t          k     rt          |dz  t                    nd|
d                                           S #                                  w xY w# t          $ r}t          d|           cY d}~S d}~wt"          $ r6}t$                              d           t          d|           cY d}~S d}~ww xY w)z:List task summaries with the same core filters as the CLI.kanban_listr   r   r   include_archivedlimitNzlimit must be an integer   zlimit must be >= 1zlimit must be <= r;   r>   )r   r   r   r   r   c                2    g | ]}t          |          S r   )r   )r   r   rX   rA   s     r   r   z _handle_list.<locals>.<listcomp>  s&    IIIa,Rq99IIIr      )taskscountr   	truncated
next_limitpromotedzkanban_list: zkanban_list failed)rz   r   rw   r   KANBAN_LIST_DEFAULT_LIMITr*   rS   r+   KANBAN_LIST_MAX_LIMITrB   recompute_ready
list_tasksr   r`   ra   minrU   r   rQ   r   )rm   r   guardr   r   r   r   
bool_errorr   r;   r   rowsr   r   r   rX   rA   s                  @@r   _handle_listr     s   &}55E xx
##HXXhFXXhF#249K#L#L j &*%%%HHWE})6E

z" 6 6 64555556qyy.///$$$E.CEEFFFHHWE#/%(((D	 ))$//H ==!!1ai !  D D		E)I%LE:IIIII5IIIU& !M%*-B%B%B 	#8999HL$
 
 
 
 JJLLLLDJJLLLL / / /-!--........ / / /-...-!--......../s[   B( ( C
CH /B)G- H -HH 
I*H'!I*'I*4+I%I*%I*c                   t          |                     d                    }|st          d          S t          |          }|r|S |                     d          }|                     d          }|                     d          }|                     d          }|                     d          }|dt	          |t
                    r|g}t	          |t          t          f          s$t          d	t          |          j	                   S d
 |D             }|Vt	          |t
                    r|g}t	          |t          t          f          s$t          dt          |          j	                   S d |D             }|r|i }n9t	          |t                    s$t          dt          |          j	                   S |                    d          }	t	          |	t          t          f          r|g }
t                      }t          |	          |z   D ]S}t          |                                          }|r.||vr*|                    |           |
                    |           T|
|d<   n||d<   |s|st          d          S |9t	          |t                    s$t          dt          |          j	                   S t          ||          }|                     d          }	 t!          |          \  }}	 	 |                    ||||||t%          |                    }nV# |j        $ rI}t          dd                    |j                   d          cY d}~|                                 S d}~ww xY w|s't          d| d          |                                 S |                    ||          }t1          ||r|j        nd          |                                 S # |                                 w xY w# t4          $ r}t          d|           cY d}~S d}~wt6          $ r6}t8                              d           t          d|           cY d}~S d}~ww xY w)z5Mark the current task done with a structured handoff.r%   r   r   r.   r   created_cards	artifactsNz.created_cards must be a list of task ids, got c                    g | ]D}t          |                                          #t          |                                          ES r   r&   rh   r   s     r   r   z$_handle_complete.<locals>.<listcomp>  sI     
 
 
 SVV\\^^
FFLLNN
 
 
r   z,artifacts must be a list of file paths, got c                    g | ]D}t          |                                          #t          |                                          ES r   r   )r   ps     r   r   z$_handle_complete.<locals>.<listcomp>  sI     
 
 
 Q
FFLLNN
 
 
r   z%metadata must be an object/dict, got z4provide at least one of: summary (preferred), resultr;   r>   )r   r   r.   r   rL   zfkanban_complete blocked: the following created_cards do not exist or were not created by this worker: z, z. Your task is still in-flight (no state change). Retry kanban_complete with the same summary/metadata and either drop these ids from created_cards, or pass created_cards=[] to skip the card-claim check entirely.zcould not complete z! (unknown id or already terminal)r%   r[   zkanban_complete: zkanban_complete failed)r$   r   r   r:   rv   r&   listtupletype__name__r3   setrh   addappendr6   rB   complete_taskr-   HallucinatedCardsErrorjoinphantomrU   
latest_runrb   r}   r+   r   rQ   r   )rm   r   r7   ownership_errr   r.   r   r   r   existingmergedseenitemsr;   rA   rX   r_   hall_errrunr   s                        r   _handle_completer     s3   
488I..
/
/C 
H
 
 	
 3377M hhy!!Gxx
##HXXhFHH_--M%%I mS)) 	,*OM-$77 	2&&/2 2  

 
$1
 
 
 i%% 	$"I)dE]33 	.	??+. .  
 
$-
 
 
	  	2$// !1H~~.1 1    ||K00H(T5M22 
2$&!$ NNY6 ) )DD		))A )Qd]]a((((.%%(1% 
v 
B
 
 	
 Jx$>$>MDNN4KMM
 
 	
 .c8<<HHHWE*3%(((D#	%%#!7X"/$23$7$7	 &   ,    "Oyy!122O O O        JJLLLL7(  !P#PPP  JJLLLL --c**CsS+B366dCCCJJLLLLDJJLLLL 3 3 31a1122222222 3 3 312221a11222222223s   #O> 8)L" !O% "
M5,*M0M5O% O> 0M55O% O> !/O% O> %O;;O> >
Q"PQ"Q",+QQ"Q"c                   t          |                     d                    }|st          d          S t          |          }|r|S |                     d          }|r!t	          |                                          st          d          S |                     d          }	 t          |          \  }}	 |                    |||t          |                    }|s't          d| d	          |	                                 S |
                    ||          }	t          ||	r|	j        nd
          |	                                 S # |	                                 w xY w# t          $ r}
t          d|
           cY d
}
~
S d
}
~
wt          $ r6}
t                              d           t          d|
           cY d
}
~
S d
}
~
ww xY w)z?Transition the task to blocked with a reason a human will read.r%   r   reasonu2   reason is required — explain what input you needr;   r>   )r   rL   zcould not block z% (unknown id or not in running/ready)Nr   zkanban_block: zkanban_block failed)r$   r   r   r:   r&   rh   rB   
block_taskr-   rU   r   rb   r}   r+   r   rQ   r   )rm   r   r7   r   r   r;   rA   rX   r_   r   r   s              r   _handle_blockr   V  s   
488I..
/
/C 
H
 
 	
 3377M XXhF PV**,, PNOOOHHWE0%(((D	c .s 3 3   B
  !&s & & &  JJLLLL --c**CsS+B366dCCCJJLLLLDJJLLLL 0 0 0.1..//////// 0 0 0.///.1..////////0sT   $E$ 8:E 2E$ /E 6E$ E!!E$ $
G.F?GG+G=GGc                `   t          |                     d                    }|st          d          S t          |          }|r|S |                     d          }|                     d          }	 t	          |          \  }}	 t
          j                            d          }|                    |||           |                    |||t          |                    }	|	s't          d	| d
          |
                                 S t          |          |
                                 S # |
                                 w xY w# t          $ r}
t          d|
           cY d}
~
S d}
~
wt          $ r6}
t                              d           t          d|
           cY d}
~
S d}
~
ww xY w)u  Signal that the worker is still alive during a long operation.

    Extends the claim TTL via ``heartbeat_claim`` AND records a heartbeat
    event via ``heartbeat_worker``. Without the ``heartbeat_claim`` half,
    a diligent worker that loops this tool while a single tool call
    blocks the agent for >DEFAULT_CLAIM_TTL_SECONDS still gets reclaimed
    by ``release_stale_claims`` — which is exactly the trap that
    ``heartbeat_claim``'s docstring warns against.
    r%   r   rK   r;   r>   rF   rG   rJ   zcould not heartbeat z (unknown id or not running))r%   zkanban_heartbeat: Nzkanban_heartbeat failed)r$   r   r   r:   rB   r   r   rP   rT   r-   rU   rb   r+   r   rQ   r   )rm   r   r7   r   rK   r;   rA   rX   rY   r_   r   s              r   _handle_heartbeatr   |  s    488I..
/
/C 
H
 
 	
 3377M 88FDHHWE4%(((D	 (BCCJtS*===$$ .s 3 3	 %  B  !L3LLL 
 JJLLLL s###JJLLLLDJJLLLL 4 4 42q2233333333 4 4 423332q22333333334sU   2E	 A1D0 7E	 D0 E	 0EE	 	
F-E*$F-*F-7+F("F-(F-c                   |                      d          }|st          d          S |                      d          }|r!t          |                                          st          d          S t          j                             d          pd}|                      d          }	 t          |          \  }}	 |                    |||t          |          	          }t          ||
          |	                                 S # |	                                 w xY w# t          $ r}	t          d|	           cY d}	~	S d}	~	wt          $ r6}	t                              d           t          d|	           cY d}	~	S d}	~	ww xY w)z$Append a comment to a task's thread.r%   uo   task_id is required (use the current task id if that's what you mean — pulls from env but kept explicit here)r   zbody is requiredHERMES_PROFILEworkerr;   r>   )r   r   )r%   
comment_idzkanban_comment: Nzkanban_comment failed)r   r   r&   rh   r   r   rB   add_commentrb   rU   r+   r   rQ   r   )
rm   r   r7   r   r   r;   rA   rX   cidr   s
             r   _handle_commentr     s   
((9

C 
B
 
 	
 88FD .s4yy(( .,--- Z^^,--9FHHWE2%(((D	..s6D		.JJCss333JJLLLLDJJLLLL 2 2 20Q0011111111 2 2 201110Q00111111112sH   %D 96D /D DD 
F'D>8F>F+E<6F<Fc                x	   |                      d          }|r!t          |                                          st          d          S |                      d          }|st          d          S |                      d          }|                      d          pg }|                      d          pt          j                             d          }|                      d	          pt          j                             d
          }|                      d          }|                      d          }	|                      d          }
|	du o|
du }|	d}	t          | d          \  }}|rt          |          S |                      d          }|                      d          }|                      d          pd}|                      d          }t          |t                    r|g}|@t          |t          t          f          s$t          dt          |          j                   S t          | d          \  }}|rt          |          S |                      d          }t          |t                    r|g}t          |t          t          f          s$t          dt          |          j                   S |                      d          }	 t          |          \  }}	 |rNt          j                             d          }|r-|                    ||          }||j        r|j        }	|j        }
|                    |t          |                                          |t          |          t          |          ||t#          |          ndt          |	          |
|||t#          |          nd|||t#          |          ndt          |          t          j                             d          pd|           }|                    ||          }t%          ||r|j        nd!          |                                 S # |                                 w xY w# t*          $ r}t          d"|           cY d}~S d}~wt,          $ r6}t.                              d#           t          d"|           cY d}~S d}~ww xY w)$zCreate a child task. Orchestrator workers use this to fan out.

    ``parents`` can be a list of task ids; dependency-gated promotion
    works as usual.
    r~   ztitle is requiredr   u   assignee is required — name the profile that should execute this task (the dispatcher will only spawn tasks with an assignee)r   r   r   HERMES_TENANTr4   r1   r   r   r   Nscratchtriageidempotency_keymax_runtime_secondsinitial_statusrunningskillsz*skills must be a list of skill names, got 	goal_modegoal_max_turnsz(parents must be a list of task ids, got r;   r>   r   r   r   r   )r~   r   r   r   r   r   r   r   r   r   r  r  r  r  r  r   r4   r%   r   zkanban_create: zkanban_create failed)r   r&   rh   r   r   r   rw   rv   r   r   r   r   rB   r   r   r   create_taskr*   rb   r   rU   r+   r   rQ   r   )rm   r   r~   r   r   r   r   r4   r   r   r   _inherit_workspacer   r   r   r  r  r  r  goal_bool_errorr  r;   rA   rX   	_self_tid
_self_tasknew_tidnew_taskr   s                                r   _handle_creater    s    HHWE /E

((** /-...xx
##H 
K
 
 	
 88FDhhy!!'RGXXhB2:>>/#B#BF ,''N2:>>:M+N+NJxx
##H XX.//NXX.//N'4/JNd4J"(x88FJ &*%%%hh011O((#899XX.//<9NXXhF&# *VdE]"C"CPf9NPP
 
 	
 "1{!C!CI +/***XX.//N'3 )ge}-- 
OtG}}7MOO
 
 	
 HHWE01%(((D)	 " CJNN+?@@	 C!#T9!=!=J!-*2K-)3)B)3)Bnn%jj&&((Xg*2*>XA">22- / +6 +,,,<@#+9+EC'''4">22:>>*:;;Gx%/ %  G2 {{411H*2<x  
 JJLLLLDJJLLLL 1 1 1/A//00000000 1 1 1/000/A//000000001sI   Q EP< 'Q <QQ 
R9Q60R96R9+R4.R94R9c                   t          d          }|r|S |                     d          }|st          d          S t          t	          |                    }|r|S |                     d          }	 t          |          \  }}	 |                    |t	          |                    }|s't          d| d          |                                 S t          t	          |          d	          |                                 S # |                                 w xY w# t          $ r}	t          d
|	           cY d}	~	S d}	~	wt          $ r6}	t                              d           t          d
|	           cY d}	~	S d}	~	ww xY w)z(Transition a blocked task back to ready.kanban_unblockr%   ztask_id is requiredr;   r>   zcould not unblock z (not blocked or unknown)readyr  zkanban_unblock: Nzkanban_unblock failed)rz   r   r   r:   r&   rB   unblock_taskrU   rb   r+   r   rQ   r   )
rm   r   r   r7   r   r;   rA   rX   r_   r   s
             r   _handle_unblockr  B  s   &'788E 
((9

C 1/00023s88<<M HHWE2%(((D	s3xx00B W!"Us"U"U"UVV JJLLLL s3xx888JJLLLLDJJLLLL 2 2 20Q0011111111 2 2 201110Q00111111112sT   0D 7D ;D D -D DD 
E?%D<6E?<E?	+E:4E?:E?c                J   |                      d          }|                      d          }|r|st          d          S |                      d          }	 t          |          \  }}	 |                    |||           t	          ||          |                                 S # |                                 w xY w# t          $ r}t          d|           cY d}~S d}~wt          $ r6}t          	                    d	           t          d|           cY d}~S d}~ww xY w)
u4   Add a parent→child dependency edge after the fact.	parent_idchild_idz(both parent_id and child_id are requiredr;   r>   )r  r  zkanban_link: Nzkanban_link failed)
r   r   rB   
link_tasksrb   rU   r+   r   rQ   r   )rm   r   r  r  r;   rA   rX   r   s           r   _handle_linkr  ^  sR   %%Ixx
##H FH FDEEEHHWE/%(((D	MM$)hMGGGX>>>JJLLLLDJJLLLL / / /-!--........ / / /-...-!--......../sH   B> ((B% B> %B;;B> >
D"CD"D",+DD"D"zrTask id. If omitted, defaults to HERMES_KANBAN_TASK from the env (the task the dispatcher spawned you to work on).uX  Kanban board slug to target. When omitted, the call resolves the active board the usual way: HERMES_KANBAN_DB env → HERMES_KANBAN_BOARD env → the 'current' symlink under the kanban home → 'default'. Pass an explicit slug only when the caller (e.g. a Telegram routing layer) needs to override the env-pinned active board for this one call.dict[str, str]c                     dt           dS )zSchema fragment for the optional ``board`` parameter.

    Centralised so a future tweak to the description / validation hint
    only has to land in one place.
    stringr   description)_DESC_BOARDr   r   r   _board_schema_propr     s     [999r   kanban_showuO  Read a task's full state — title, body, assignee, parent task handoffs, your prior attempts on this task if any, comments, and recent events. Use this to (re)orient yourself before starting work, especially on retries. The response includes a pre-formatted ``worker_context`` string suitable for inclusion verbatim in your reasoning.objectr  r  )r%   r;   )r   
propertiesrequired)rn   r  
parametersr   u  List Kanban task summaries so an orchestrator profile can discover work to route. Supports the same core filters as the CLI: assignee, status, tenant, include_archived, and limit. Returns compact rows with ids, title, status, assignee, priority, parent/child ids, and counts. Bounded to 50 rows by default, 200 max, with truncation metadata. Also recomputes ready tasks before listing, matching the CLI. Orchestrator-only — dispatcher-spawned task workers never see this tool.z!Optional assignee/profile filter.)r   todor  r  blockeddonearchivedzOptional task status filter.)r   enumr  z)Optional tenant/project namespace filter.booleanz*Include archived tasks. Defaults to false.integerz6Optional maximum rows to return (default 50, max 200).)r   r   r   r   r   r;   kanban_completeuh  Mark your current task done with a structured handoff for downstream workers and humans. Prefer ``summary`` for a human-readable 1-3 sentence description of what you did; put machine-readable facts in ``metadata`` (changed_files, tests_run, decisions, findings, etc). At least one of ``summary`` or ``result`` is required. If you created new tasks via ``kanban_create`` during this run, list their ids in ``created_cards`` — the kernel verifies them so phantom references are caught before they leak into downstream automation. If you produced deliverable files (charts, PDFs, spreadsheets, generated images), list their absolute paths in ``artifacts`` — the gateway notifier will upload them as native attachments to the human who subscribed to the task, so the deliverable lands in their chat alongside the summary instead of being a path they have to fetch by hand.zrHuman-readable handoff, 1-3 sentences. Appears in Run History on the dashboard and in downstream workers' context.u   Free-form dict of structured facts about this attempt — {"changed_files": [...], "tests_run": 12, "findings": [...]}. Surfaced to downstream workers alongside ``summary``.zShort result log line (legacy field, maps to task.result). Use ``summary`` instead when possible; this exists for compatibility with callers that still set --result on the CLI.arrayr   u  Optional structured manifest of task ids you created via ``kanban_create`` during this run. The kernel verifies each id exists and was created by this worker's profile; any phantom id blocks the completion with an error listing what went wrong (auditable in the task's events). Only list ids you got back from a successful ``kanban_create`` call — do not invent or remember ids from prose. Omit the field if you did not create any cards.)r   itemsr  u1  Optional list of absolute paths to deliverable files you produced during this run — generated charts, PDFs, spreadsheets, images, archives. Examples: ["/tmp/q3-revenue.png", "/tmp/report.pdf"]. The gateway notifier uploads each path as a native attachment to the subscribed chat (images embed inline, everything else uploads as a file) so the deliverable lands with the completion notification. Skip intermediate scratch files and references that are not the deliverable. The path must exist on disk when the notifier runs; missing files are silently skipped.)r%   r   r.   r   r   r   r;   kanban_blocku   Transition the task to blocked because you need human input to proceed. ``reason`` will be shown to the human on the board and included in context when someone unblocks you. Use for genuine blockers only — don't block on things you can resolve yourself.zWhat you need answered, in one or two sentences. Don't paste the whole conversation; the human has the board and can ask follow-ups via comments.)r%   r   r;   r   kanban_heartbeatu   Signal that you're still alive during a long operation (training, encoding, large crawls). Call every few minutes so humans see liveness separately from PID checks. Pure side effect — no work changes.zHOptional short note describing current progress. Shown in the event log.)r%   rK   r;   kanban_commentu   Append a comment to a task's thread. Use for durable notes that should outlive this run (questions for the next worker, partial findings, rationale). Ephemeral reasoning doesn't belong here — use your normal response instead.uW   Task id. Required (may be your own task or another's — comment threads are per-task).z Markdown-supported comment body.)r%   r   r;   r   kanban_createuc  Create a new kanban task, optionally as a child of the current one (pass the current task id in ``parents``). Used by orchestrator workers to fan out — decompose work into child tasks with specific assignees, link them into a pipeline, then complete your own task. The dispatcher picks up the new tasks on its next tick and spawns the assigned profiles.r~   zShort task title (required).r   u   Profile name that should execute this task (e.g. 'researcher-a', 'reviewer', 'writer'). Required — tasks without an assignee are never dispatched.zkOpening post: full spec, acceptance criteria, links. The assigned worker reads this as part of its context.r   zParent task ids. The new task stays in 'todo' until every parent reaches 'done'; then it auto-promotes to 'ready'. Typical fan-in: list all the researcher task ids when creating a synthesizer task.r   zUOptional namespace for multi-project isolation. Defaults to HERMES_TENANT env if set.r   zZDispatcher tiebreaker. Higher = picked sooner when multiple ready tasks share an assignee.r   )r   dirworktreezWorkspace flavor: 'scratch' (fresh tmp dir, default), 'dir' (shared directory, requires absolute workspace_path), 'worktree' (git worktree).r   zYAbsolute path for 'dir' or 'worktree' workspace. Relative paths are rejected at dispatch.r   u   If true, task lands in 'triage' instead of 'todo' — a specifier profile is expected to flesh out the body before work starts.r   zIf a non-archived task with this key already exists, return that task's id instead of creating a duplicate. Useful for retry-safe automation.r  zxPer-task runtime cap. When exceeded, the dispatcher SIGTERMs the worker and re-queues the task with outcome='timed_out'.r  r  r'  zInitial card status. Use 'blocked' for tasks that require immediate human ops (R3 gate) to skip the brief running-to-blocked transition. Defaults to 'running', which preserves the usual dispatch path.r  u4  Skill names to force-load into the dispatched worker (in addition to the built-in kanban-worker skill). Use this to pin a task to a specialist context — e.g. ['translation'] for a translation task, ['github-code-review'] for a reviewer task. The names must match skills installed on the assignee's profile.r  a  Run the dispatched worker in a goal loop. When true, after each turn an auxiliary judge checks the worker's response against this card's title/body; if the work isn't done and budget remains, the worker keeps going in the same session until the judge agrees it's complete (or the goal-turn budget is exhausted, which blocks the task for human review). Use this for open-ended cards where one shot rarely finishes the work. Defaults to false (classic single-shot worker).r  zTurn budget for goal_mode workers. Caps how many continuation turns the worker may take before the task is blocked for review. Ignored unless goal_mode is true. Defaults to the goal-engine default (20).r  u   Move a blocked Kanban task back to ready. Orchestrator-only — only profiles with the kanban toolset can unblock routed work; dispatcher-spawned task workers never see this tool.z#Blocked task id to return to ready.kanban_linku   Add a parent→child dependency edge after both tasks already exist. The child won't promote to 'ready' until all parents are 'done'. Cycles and self-links are rejected.zParent task id.zChild task id.)r  r  r;   r  r  r   u   📋)rn   toolsetschemahandlercheck_fnemojiu   ✔u   ⏸u   💓u   💬u   ➕u   ▶u   🔗)r
   r   )r    r!   r
   r!   )r%   r&   r
   r'   )r%   r&   r.   r/   r
   r/   )r7   r&   r
   r!   )N)r;   r!   )r]   r   r
   r&   )rc   r   r
   r!   )rm   r3   rn   r&   rl   r   )rx   r&   r
   r!   )r
   r{   )rm   r3   r
   r&   )r
   r  )8__doc__
__future__r   r`   loggingr   typingr   r   tools.registryr   r   	getLoggerr   rQ   r   r   r   r   r   r$   r-   r6   r:   rB   rO   rD   __annotations__r\   rb   rk   rw   rz   r   r   r   r   r   r   r   r  r  r  _DESC_TASK_ID_DEFAULTr  r   KANBAN_SHOW_SCHEMAKANBAN_LIST_SCHEMAKANBAN_COMPLETE_SCHEMAKANBAN_BLOCK_SCHEMAKANBAN_HEARTBEAT_SCHEMAKANBAN_COMMENT_SCHEMAKANBAN_CREATE_SCHEMAKANBAN_UNBLOCK_SCHEMAKANBAN_LINK_SCHEMAregisterr   r   r   <module>rN     s
    8 # " " " " "   				                 / / / / / / / /		8	$	$   
 
 
 
) ) ) )") ) ) )$   
 
 
 
      @' ' ' ' 'H (, $&)  ) ) ) )8 8 8 8v. . . .    ?D B B B B B B   $   <I/ I/ I/ I/X:/ :/ :/ :/zw3 w3 w3 w3t#0 #0 #0 #0L04 04 04 04f!2 !2 !2 !2Hl1 l1 l1 l1^2 2 2 28/ / / /68  : : : : 	&  !4  ('))
 
 
 
  0 	  !B 
 !    >  !J 
 "K! !
 "W  ('))3
 
6 ; + + \ 	>"  !4 
 !(  !5  !B    (+	0	 "   (+,	 & ('))EC
 C
H MG G'[ [ | 	  !4 
 !E  ('))
 
 J#   > 	&  !4 
 !.  ('))
 
 !   : 	<  !C  !A  ('))
 
 '!   : 	C F
 = F

  " F
  # F
,  (+(	
 
-F
B  < CF
P !C QF
^  666K	 _F
p  ? qF
~ !3 F
N  E   OF
^ "!5$ $_F
n  "I.J		 	oF
B  (+*	 CF
\ !L ]F
x !F yF
J ''))KF
N j)SJ JU U p 	?
  !D  ('))
 
 K
 
  * 	:
 "*;LMM"*;KLL''))
 

 !*-   .  	
     	,
     	!
     	
     	"
     	 
     	
     	 ,
     	
     r   