Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.
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: 9 additions & 3 deletions .github/workflows/notify-monorepo.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
name: Notify Monorepo on Push

# Push trigger PAUSED for the Fox in the Box v0.6.0 upstream-separation
# migration (fox-in-the-box-ai/fox-in-the-box#155). The companion monorepo
# workflow `.github/workflows/sync-submodules.yml` is paused symmetrically
# for the same reason — auto-bumping the submodule pointer mid-migration
# would whipsaw the pin while phases 2-7 are in flight.
#
# Restored when v0.6.0 ships and the submodules re-point at virgin upstream
# (Phase 8). For manual dispatch during the migration, use workflow_dispatch.
on:
push:
branches:
- main
workflow_dispatch:

jobs:
notify:
Expand Down
22 changes: 2 additions & 20 deletions agent/auxiliary_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3046,15 +3046,6 @@ def _resolve_task_provider_model(
if cfg_provider and cfg_provider != "auto":
return cfg_provider, resolved_model, None, None, resolved_api_mode

# provider is "auto" (or unset) but config specified an explicit model
# (e.g. auxiliary.default.model = us.anthropic.claude-haiku-...).
# _resolve_auto ignores the model hint and always uses the main model,
# so we must resolve the provider explicitly here to honour cfg_model.
if resolved_model:
explicit_provider = _read_main_provider() or "auto"
if explicit_provider and explicit_provider != "auto":
return explicit_provider, resolved_model, None, None, resolved_api_mode

return "auto", resolved_model, None, None, resolved_api_mode

return "auto", resolved_model, None, None, resolved_api_mode
Expand All @@ -3064,11 +3055,7 @@ def _resolve_task_provider_model(


def _get_auxiliary_task_config(task: str) -> Dict[str, Any]:
"""Return the config dict for auxiliary.<task>, or {} when unavailable.

Falls back to auxiliary.default when no task-specific config exists.
Task-specific keys win over default keys ({**default_config, **task_config}).
"""
"""Return the config dict for auxiliary.<task>, or {} when unavailable."""
if not task:
return {}
try:
Expand All @@ -3077,13 +3064,8 @@ def _get_auxiliary_task_config(task: str) -> Dict[str, Any]:
except ImportError:
return {}
aux = config.get("auxiliary", {}) if isinstance(config, dict) else {}
if not isinstance(aux, dict):
return {}
default_config = aux.get("default", {})
default_config = default_config if isinstance(default_config, dict) else {}
task_config = aux.get(task, {}) if isinstance(aux, dict) else {}
task_config = task_config if isinstance(task_config, dict) else {}
return {**default_config, **task_config}
return task_config if isinstance(task_config, dict) else {}


def _get_task_timeout(task: str, default: float = _DEFAULT_AUX_TIMEOUT) -> float:
Expand Down
11 changes: 0 additions & 11 deletions cron/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,6 @@ def create_job(
"last_status": None,
"last_error": None,
"last_delivery_error": None,
"failure_history": [], # Rolling last-5 failures: [{at, error, session_id}]
# Delivery configuration
"deliver": deliver,
"origin": origin, # Tracks where job was created for "origin" delivery
Expand Down Expand Up @@ -697,16 +696,6 @@ def mark_job_run(job_id: str, success: bool, error: Optional[str] = None,
job["last_error"] = error if not success else None
# Track delivery failures separately — cleared on successful delivery
job["last_delivery_error"] = delivery_error

# Maintain rolling failure history (last 5 entries).
# On success, clear it so the counter resets.
if not success:
_entry = {"at": now, "error": (error or "unknown")[:200]}
_hist = list(job.get("failure_history") or [])
_hist.append(_entry)
job["failure_history"] = _hist[-5:] # keep last 5
else:
job["failure_history"] = []

# Increment completed count
if job.get("repeat"):
Expand Down
64 changes: 6 additions & 58 deletions cron/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import os
import subprocess
import sys
import traceback

# fcntl is Unix-only; on Windows use msvcrt for file locking
try:
Expand Down Expand Up @@ -1195,52 +1194,22 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
except Exception as e:
error_msg = f"{type(e).__name__}: {str(e)}"
logger.exception("Job '%s' failed: %s", job_name, error_msg)

# Collect agent activity diagnostics if available
_diag_lines: list[str] = []
if agent is not None:
try:
_act = agent.get_activity_summary() if hasattr(agent, "get_activity_summary") else {}
_api_calls = _act.get("api_call_count", "?")
_max_iter = _act.get("max_iterations", "?")
_last_act = _act.get("last_activity_desc", "unknown")
_idle = _act.get("seconds_since_activity", 0)
_diag_lines.append(f"**Iterations:** {_api_calls}/{_max_iter}")
_diag_lines.append(f"**Last activity:** {_last_act} ({int(_idle)}s ago)")
if _act.get("current_tool"):
_diag_lines.append(f"**Stuck on tool:** `{_act['current_tool']}`")
except Exception:
pass

_tb = traceback.format_exc()
_diag_block = ("\n".join(_diag_lines) + "\n\n") if _diag_lines else ""


output = f"""# Cron Job: {job_name} (FAILED)

**Job ID:** {job_id}
**Session:** `{_cron_session_id}`
**Run Time:** {_hermes_now().strftime('%Y-%m-%d %H:%M:%S')}
**Schedule:** {job.get('schedule_display', 'N/A')}

## Error

```
{error_msg}
```

## Agent Diagnostics
## Prompt

{_diag_block}**Session log:** `~/.hermes/sessions/session_{_cron_session_id}.json`
{prompt}

## Traceback
## Error

```
{_tb}
{error_msg}
```

## Prompt

{prompt}
"""
return False, output, "", error_msg

Expand Down Expand Up @@ -1371,28 +1340,7 @@ def _process_job(job: dict) -> bool:
# Deliver the final response to the origin/target chat.
# If the agent responded with [SILENT], skip delivery (but
# output is already saved above). Failed jobs always deliver.
if success:
deliver_content = final_response
else:
# Build a structured failure notification with enough
# context to diagnose without opening logs manually.
_fail_lines = [
f"❌ **Cron job failed:** `{job.get('name', job['id'])}`",
f"**Error:** {error}",
]
# Surface consecutive failure count if available
_history = job.get("failure_history") or []
if len(_history) > 1:
_fail_lines.append(f"**Consecutive failures:** {len(_history)} (first: {_history[0].get('at', '?')[:16]})")
# Include session log path for easy debugging
_session_files = sorted(
(f for f in (Path(_hermes_home) / "sessions").glob(f"session_cron_{job['id']}_*.json") if f.is_file()),
key=lambda p: p.stat().st_mtime,
reverse=True,
)
if _session_files:
_fail_lines.append(f"**Session log:** `{_session_files[0]}`")
deliver_content = "\n".join(_fail_lines)
deliver_content = final_response if success else f"⚠️ Cron job '{job.get('name', job['id'])}' failed:\n{error}"
should_deliver = bool(deliver_content)
if should_deliver and success and SILENT_MARKER in deliver_content.strip().upper():
logger.info("Job '%s': agent returned %s — skipping delivery", job["id"], SILENT_MARKER)
Expand Down
2 changes: 1 addition & 1 deletion hermes_cli/runtime_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -1233,7 +1233,7 @@ def resolve_runtime_provider(
# Dual-path routing: Claude models use AnthropicBedrock SDK for full
# feature parity (prompt caching, thinking budgets, adaptive thinking).
# Non-Claude models use the Converse API for multi-model support.
_current_model = target_model or str(model_cfg.get("default") or "").strip()
_current_model = str(model_cfg.get("default") or "").strip()
if is_anthropic_bedrock_model(_current_model):
# Claude on Bedrock → AnthropicBedrock SDK → anthropic_messages path
runtime = {
Expand Down
201 changes: 0 additions & 201 deletions plugins/memory/mem0_oss/README.md

This file was deleted.

Loading
Loading