The gateway (gateway/run.py) does not invoke _post_turn_goal_continuation() after each assistant turn. As a result, the goal judge (evaluate_after_turn) is never called, turns_used never increments past 0, and the /goal loop is effectively dead despite all configuration being correct.
Reproduction Steps
- Set a goal: /goal Respond "hello" and stop.
- Wait for the assistant to respond with "hello"
- Check status: /goal status → shows 0/20 turns
- The goal judge never evaluates the response, turns never increment
Expected Behavior
Per the documentation, after each assistant response the goal judge should fire, evaluate the response against the goal, increment turns_used, and either continue or mark the goal as done.
Root Cause
In gateway/run.py, after the agent produces a response:
_agent_result = await self._handle_message_with_agent(event, source, _quick_key, _run_generation)
The code immediately after (lines ~10272-10288) checks if _final_text.strip(): and then calls self.session_store.get_or_create_session(source) → _post_turn_goal_continuation(...). However, this section is never reached in actual message processing — the function returns before executing the goal continuation hook.
Evidence
The goal system itself works perfectly. Calling evaluate_after_turn() directly from a Python script:
mgr = GoalManager(session_id="<active_session_id>")
result = mgr.evaluate_after_turn(
last_response="Test completed.",
user_initiated=True,
)
→ verdict="done", turns_used=1, status="done", persisted to DB correctly
The auxiliary client resolves correctly:
get_text_auxiliary_client("goal_judge") → OpenAI client
Model: nvidia/nemotron-3-super-120b-a12b
Chat completions: available
The goal judge model responds with valid JSON:
{"done": true, "reason": "Goal completed."}
DB evidence:
- Querying state_meta for goal:<session_id> shows turns_used=0, last_verdict=None for ALL goals ever created via the gateway
- After direct evaluate_after_turn() call: turns_used=2, last_verdict="done"
Config Used
goals:
enabled: true
max_turns: 20
allow_early_stop: true
send_result_reply: true
auxiliary:
goal_judge:
provider: nvidia
model: nvidia/nemotron-3-super-120b-a12b
max_tokens: 1024
timeout: 120
Workaround
Until this is fixed, a cron-based watchdog can replicate the goal behavior:
Runs every 60s, detects new assistant messages, calls evaluate_after_turn
Only notifies on goal completion
See: /data/.hermes/scripts/goal_watchdog.py
Install: hermes cron create --no-agent --script goal_watchdog.py --schedule "every 1m"
Additional Context
The gateway (gateway/run.py) does not invoke _post_turn_goal_continuation() after each assistant turn. As a result, the goal judge (evaluate_after_turn) is never called, turns_used never increments past 0, and the /goal loop is effectively dead despite all configuration being correct.
Reproduction Steps
Expected Behavior
Per the documentation, after each assistant response the goal judge should fire, evaluate the response against the goal, increment turns_used, and either continue or mark the goal as done.
Root Cause
In gateway/run.py, after the agent produces a response:
_agent_result = await self._handle_message_with_agent(event, source, _quick_key, _run_generation)
The code immediately after (lines ~10272-10288) checks if _final_text.strip(): and then calls self.session_store.get_or_create_session(source) → _post_turn_goal_continuation(...). However, this section is never reached in actual message processing — the function returns before executing the goal continuation hook.
Evidence
The goal system itself works perfectly. Calling evaluate_after_turn() directly from a Python script:
mgr = GoalManager(session_id="<active_session_id>")
result = mgr.evaluate_after_turn(
last_response="Test completed.",
user_initiated=True,
)
→ verdict="done", turns_used=1, status="done", persisted to DB correctly
The auxiliary client resolves correctly:
get_text_auxiliary_client("goal_judge") → OpenAI client
Model: nvidia/nemotron-3-super-120b-a12b
Chat completions: available
The goal judge model responds with valid JSON:
{"done": true, "reason": "Goal completed."}
DB evidence:
Config Used
goals:
enabled: true
max_turns: 20
allow_early_stop: true
send_result_reply: true
auxiliary:
goal_judge:
provider: nvidia
model: nvidia/nemotron-3-super-120b-a12b
max_tokens: 1024
timeout: 120
Workaround
Until this is fixed, a cron-based watchdog can replicate the goal behavior:
Runs every 60s, detects new assistant messages, calls evaluate_after_turn
Only notifies on goal completion
See: /data/.hermes/scripts/goal_watchdog.py
Install: hermes cron create --no-agent --script goal_watchdog.py --schedule "every 1m"
Additional Context