151 lines
4.2 KiB
Python
151 lines
4.2 KiB
Python
"""Patch upstream Hermes auto-decomposition to respect execution history."""
|
|
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
def replace_once(source: str, before: str, after: str, label: str) -> str:
|
|
"""Replace one anchored upstream fragment and fail closed on source drift."""
|
|
count = source.count(before)
|
|
if count != 1:
|
|
raise SystemExit(
|
|
f"Hermes {label} patch context changed: expected 1, found {count}"
|
|
)
|
|
return source.replace(before, after, 1)
|
|
|
|
|
|
source_root = Path(os.environ.get("HERMES_SOURCE_ROOT", "/opt/hermes"))
|
|
decompose_path = source_root / "hermes_cli/kanban_decompose.py"
|
|
decompose = decompose_path.read_text(encoding="utf-8")
|
|
|
|
helper_anchor = ''' if chosen not in valid_names:
|
|
return default_assignee
|
|
return chosen
|
|
|
|
|
|
'''
|
|
helper_replacement = helper_anchor + '''def _has_execution_history(task_id: str) -> bool:
|
|
"""Return whether a task has ever entered a worker run."""
|
|
with kb.connect_closing() as conn:
|
|
return bool(kb.list_runs(conn, task_id))
|
|
|
|
|
|
'''
|
|
decompose = replace_once(
|
|
decompose,
|
|
helper_anchor,
|
|
helper_replacement,
|
|
"auto-decompose execution-history helper",
|
|
)
|
|
|
|
signature_before = '''def decompose_task(
|
|
task_id: str,
|
|
*,
|
|
author: Optional[str] = None,
|
|
timeout: Optional[int] = None,
|
|
) -> DecomposeOutcome:
|
|
'''
|
|
signature_after = '''def decompose_task(
|
|
task_id: str,
|
|
*,
|
|
author: Optional[str] = None,
|
|
timeout: Optional[int] = None,
|
|
automatic: bool = False,
|
|
) -> DecomposeOutcome:
|
|
'''
|
|
decompose = replace_once(
|
|
decompose,
|
|
signature_before,
|
|
signature_after,
|
|
"auto-decompose function signature",
|
|
)
|
|
|
|
status_before = ''' if task.status != "triage":
|
|
return DecomposeOutcome(
|
|
task_id, False, f"task is not in triage (status={task.status!r})"
|
|
)
|
|
|
|
cfg = _load_config()
|
|
'''
|
|
status_after = ''' if task.status != "triage":
|
|
return DecomposeOutcome(
|
|
task_id, False, f"task is not in triage (status={task.status!r})"
|
|
)
|
|
if automatic and _has_execution_history(task_id):
|
|
return DecomposeOutcome(
|
|
task_id,
|
|
False,
|
|
"task has execution history and requires deliberate manual triage",
|
|
)
|
|
|
|
cfg = _load_config()
|
|
'''
|
|
decompose = replace_once(
|
|
decompose,
|
|
status_before,
|
|
status_after,
|
|
"auto-decompose preflight guard",
|
|
)
|
|
|
|
single_before = ''' with kb.connect_closing() as conn:
|
|
ok = kb.specify_triage_task(
|
|
'''
|
|
single_after = ''' if automatic and _has_execution_history(task_id):
|
|
return DecomposeOutcome(
|
|
task_id,
|
|
False,
|
|
"task gained execution history and requires deliberate manual triage",
|
|
)
|
|
with kb.connect_closing() as conn:
|
|
ok = kb.specify_triage_task(
|
|
'''
|
|
decompose = replace_once(
|
|
decompose,
|
|
single_before,
|
|
single_after,
|
|
"auto-decompose single-task commit guard",
|
|
)
|
|
|
|
fanout_before = ''' try:
|
|
with kb.connect_closing() as conn:
|
|
child_ids = kb.decompose_triage_task(
|
|
'''
|
|
fanout_after = ''' if automatic and _has_execution_history(task_id):
|
|
return DecomposeOutcome(
|
|
task_id,
|
|
False,
|
|
"task gained execution history and requires deliberate manual triage",
|
|
)
|
|
try:
|
|
with kb.connect_closing() as conn:
|
|
child_ids = kb.decompose_triage_task(
|
|
'''
|
|
decompose = replace_once(
|
|
decompose,
|
|
fanout_before,
|
|
fanout_after,
|
|
"auto-decompose fanout commit guard",
|
|
)
|
|
decompose_path.write_text(decompose, encoding="utf-8")
|
|
|
|
|
|
watcher_path = source_root / "gateway/kanban_watchers.py"
|
|
watcher = watcher_path.read_text(encoding="utf-8")
|
|
call_before = ''' outcome = _decomp.decompose_task(
|
|
tid, author="auto-decomposer",
|
|
)
|
|
'''
|
|
call_after = ''' outcome = _decomp.decompose_task(
|
|
tid,
|
|
author="auto-decomposer",
|
|
automatic=True,
|
|
)
|
|
'''
|
|
watcher = replace_once(
|
|
watcher,
|
|
call_before,
|
|
call_after,
|
|
"gateway automatic-decomposition call",
|
|
)
|
|
watcher_path.write_text(watcher, encoding="utf-8")
|