Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions hermes_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -1700,6 +1700,18 @@ def _ensure_hermes_home_managed(home: Path):
"extra_body": {},
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
},
# Goal judge — evaluates whether a /goal run's latest response
# satisfies the goal/contract, and drafts goal contracts. Short
# structured-JSON calls; a fast cheap model is fine.
"goal_judge": {
"provider": "auto",
"model": "",
"base_url": "",
"api_key": "",
"timeout": 60,
"extra_body": {},
"reasoning_effort": "", # per-task thinking level: none|minimal|low|medium|high|xhigh|max|ultra (empty = provider default)
},
# Curator — skill-usage review fork. Timeout is generous because the
# review pass can take several minutes on reasoning models (umbrella
# building over hundreds of candidate skills). "auto" = use main chat
Expand Down
36 changes: 10 additions & 26 deletions hermes_cli/goals.py
Original file line number Diff line number Diff line change
Expand Up @@ -878,20 +878,11 @@ def judge_goal(
return "continue", "empty response (nothing to evaluate)", False, None

try:
from agent.auxiliary_client import get_auxiliary_extra_body, get_text_auxiliary_client
from agent.auxiliary_client import call_llm
except Exception as exc:
logger.debug("goal judge: auxiliary client import failed: %s", exc)
return "continue", "auxiliary client unavailable", False, None

try:
client, model = get_text_auxiliary_client("goal_judge")
except Exception as exc:
logger.debug("goal judge: get_text_auxiliary_client failed: %s", exc)
return "continue", "auxiliary client unavailable", False, None

if client is None or not model:
return "continue", "no auxiliary client configured", False, None

# Build the prompt. Priority: contract > subgoals > plain. When both a
# contract and subgoals exist, the subgoals are appended into the
# contract block as extra criteria so the judge sees a single source of
Expand Down Expand Up @@ -935,16 +926,18 @@ def judge_goal(
)

try:
resp = client.chat.completions.create(
model=model,
# Route through call_llm so auxiliary.goal_judge.* config
# (provider/model/base_url, extra_body, reasoning_effort, retries)
# all apply — the direct-create path dropped extra_body (#35566).
resp = call_llm(
task="goal_judge",
messages=[
{"role": "system", "content": JUDGE_SYSTEM_PROMPT},
{"role": "user", "content": prompt},
],
temperature=0,
max_tokens=_goal_judge_max_tokens(),
timeout=timeout,
extra_body=get_auxiliary_extra_body() or None,
)
except Exception as exc:
logger.info("goal judge: API call failed (%s) — falling through to continue", exc)
Expand Down Expand Up @@ -999,31 +992,22 @@ def draft_contract(objective: str, *, timeout: float = DEFAULT_JUDGE_TIMEOUT) ->
return None

try:
from agent.auxiliary_client import get_auxiliary_extra_body, get_text_auxiliary_client
from agent.auxiliary_client import call_llm
except Exception as exc:
logger.debug("goal draft: auxiliary client import failed: %s", exc)
return None

try:
client, model = get_text_auxiliary_client("goal_judge")
except Exception as exc:
logger.debug("goal draft: get_text_auxiliary_client failed: %s", exc)
return None

if client is None or not model:
return None

try:
resp = client.chat.completions.create(
model=model,
# Route through call_llm — same #35566 fix as the judge call above.
resp = call_llm(
task="goal_judge",
messages=[
{"role": "system", "content": DRAFT_CONTRACT_SYSTEM_PROMPT},
{"role": "user", "content": f"Objective:\n{_truncate(objective, 4000)}"},
],
temperature=0,
max_tokens=_goal_judge_max_tokens(),
timeout=timeout,
extra_body=get_auxiliary_extra_body() or None,
)
except Exception as exc:
logger.info("goal draft: API call failed (%s)", exc)
Expand Down
23 changes: 7 additions & 16 deletions hermes_cli/kanban_decompose.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,23 +298,11 @@ def decompose_task(
roster, valid_names = _build_roster()

try:
from agent.auxiliary_client import ( # type: ignore
get_auxiliary_extra_body,
get_text_auxiliary_client,
)
from agent.auxiliary_client import call_llm # type: ignore
except Exception as exc:
logger.debug("decompose: auxiliary client import failed: %s", exc)
return DecomposeOutcome(task_id, False, "auxiliary client unavailable")

try:
client, model = get_text_auxiliary_client("kanban_decomposer")
except Exception as exc:
logger.debug("decompose: get_text_auxiliary_client failed: %s", exc)
return DecomposeOutcome(task_id, False, "auxiliary client unavailable")

if client is None or not model:
return DecomposeOutcome(task_id, False, "no auxiliary client configured")

user_msg = _USER_TEMPLATE.format(
task_id=task.id,
title=_truncate(task.title or "", 400),
Expand All @@ -324,16 +312,19 @@ def decompose_task(
)

try:
resp = client.chat.completions.create(
model=model,
# Route through call_llm so auxiliary.kanban_decomposer.* config
# (provider/model/base_url, extra_body, reasoning_effort, retries)
# all apply — the previous direct client.chat.completions.create()
# path dropped auxiliary.<task>.extra_body entirely (#35566).
resp = call_llm(
task="kanban_decomposer",
messages=[
{"role": "system", "content": _SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
temperature=0.3,
max_tokens=4000,
timeout=timeout or 180,
extra_body=get_auxiliary_extra_body() or None,
)
except Exception as exc:
logger.info(
Expand Down
21 changes: 6 additions & 15 deletions hermes_cli/kanban_specify.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,39 +162,30 @@ def specify_task(
)

try:
from agent.auxiliary_client import get_auxiliary_extra_body, get_text_auxiliary_client
from agent.auxiliary_client import call_llm
except Exception as exc: # pragma: no cover — import smoke test
logger.debug("specify: auxiliary client import failed: %s", exc)
return SpecifyOutcome(task_id, False, "auxiliary client unavailable")

try:
client, model = get_text_auxiliary_client("triage_specifier")
except Exception as exc:
logger.debug("specify: get_text_auxiliary_client failed: %s", exc)
return SpecifyOutcome(task_id, False, "auxiliary client unavailable")

if client is None or not model:
return SpecifyOutcome(
task_id, False, "no auxiliary client configured"
)

user_msg = _USER_TEMPLATE.format(
task_id=task.id,
title=_truncate(task.title or "", 400),
body=_truncate(task.body or "(no body)", 4000),
)

try:
resp = client.chat.completions.create(
model=model,
# Route through call_llm so auxiliary.triage_specifier.* config
# (provider/model/base_url, extra_body, reasoning_effort, retries)
# all apply — the direct-create path dropped extra_body (#35566).
resp = call_llm(
task="triage_specifier",
messages=[
{"role": "system", "content": _SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
temperature=0.3,
max_tokens=HERMES_KANBAN_SPECIFY_MAX_TOKENS,
timeout=timeout or 120,
extra_body=get_auxiliary_extra_body() or None,
)
except Exception as exc:
logger.info(
Expand Down
22 changes: 6 additions & 16 deletions hermes_cli/profile_describer.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,23 +210,11 @@ def describe_profile(
model, provider = None, None

try:
from agent.auxiliary_client import ( # type: ignore
get_auxiliary_extra_body,
get_text_auxiliary_client,
)
from agent.auxiliary_client import call_llm # type: ignore
except Exception as exc:
logger.debug("describe: auxiliary client import failed: %s", exc)
return DescribeOutcome(canon, False, "auxiliary client unavailable")

try:
client, aux_model = get_text_auxiliary_client("profile_describer")
except Exception as exc:
logger.debug("describe: get_text_auxiliary_client failed: %s", exc)
return DescribeOutcome(canon, False, "auxiliary client unavailable")

if client is None or not aux_model:
return DescribeOutcome(canon, False, "no auxiliary client configured")

user_msg = _USER_TEMPLATE.format(
name=canon,
model=(model or "(unset)"),
Expand All @@ -237,16 +225,18 @@ def describe_profile(
)

try:
resp = client.chat.completions.create(
model=aux_model,
# Route through call_llm so auxiliary.profile_describer.* config
# (provider/model/base_url, extra_body, reasoning_effort, retries)
# all apply — the direct-create path dropped extra_body (#35566).
resp = call_llm(
task="profile_describer",
messages=[
{"role": "system", "content": _SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
temperature=0.3,
max_tokens=400,
timeout=timeout or 60,
extra_body=get_auxiliary_extra_body() or None,
)
except Exception as exc:
logger.info("describe: API call failed for %s (%s)", canon, exc)
Expand Down
Loading
Loading