From 45717780fa05751a8001a88c3471db9ae28b274f Mon Sep 17 00:00:00 2001 From: Loma Date: Wed, 8 Jul 2026 23:00:22 +0100 Subject: [PATCH] Fix proactive agent cron duplicating on every server restart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit register_cron() ran unconditionally on every 'jarvis serve' startup and create_task() persists to scheduler.db, so each restart added another copy of the daily proactive cron. On a real install 68 duplicates accumulated; when due they fired back-to-back and monopolized the single-slot local inference queue, stalling interactive chat. register_cron() is now idempotent: an existing active task is reused and surplus duplicates are cancelled. Also stop the notification channel from calling connect() — a second getUpdates poll loop on the same bot token makes Telegram return Conflict and kills the main listener. --- src/openjarvis/agents/proactive_agent.py | 44 +++++++++++++++++++++--- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/src/openjarvis/agents/proactive_agent.py b/src/openjarvis/agents/proactive_agent.py index 2aab5c38..ce75cb81 100644 --- a/src/openjarvis/agents/proactive_agent.py +++ b/src/openjarvis/agents/proactive_agent.py @@ -37,6 +37,7 @@ called from your app startup: from __future__ import annotations import json +import logging from datetime import datetime from pathlib import Path from typing import Any, Dict, List, Optional, Set @@ -46,6 +47,8 @@ from openjarvis.core.config import load_config from openjarvis.core.paths import get_config_dir from openjarvis.core.registry import AgentRegistry from openjarvis.core.types import Message, Role, ToolCall + +logger = logging.getLogger(__name__) from openjarvis.tools.approval_store import ( DECISION_ALWAYS_APPROVE, DECISION_ALWAYS_DENY, @@ -252,11 +255,25 @@ def _build_notification_channel(channel_spec: str) -> Optional[Any]: if ChannelRegistry.contains(channel_type): channel_cls = ChannelRegistry.get(channel_type) - instance = channel_cls() + # Load credentials from config so the channel uses bot_token from + # config.toml rather than falling back to a bare env var. try: - instance.connect() + from openjarvis.core.config import load_config + from openjarvis.system._channel_kwargs import build_channel_kwargs + + _cfg = load_config() + _kwargs = build_channel_kwargs(_cfg.channel, channel_type) except Exception: - pass + _kwargs = {} + instance = channel_cls(**_kwargs) + # NOTE: do NOT call instance.connect() here. + # The notification channel only needs to *send* messages — it must + # NOT start a polling/getUpdates loop. Starting a second poll loop + # (with the same bot token) inside the same process would cause a + # Telegram Conflict error that kills the main channel's listener. + # TelegramChannel.send() works fine without connect() because it + # opens a fresh HTTP connection per message rather than relying on + # the long-poll thread. return instance except Exception: pass @@ -592,8 +609,27 @@ def register_cron( hours_back = hours_back or 24 timezone = timezone or "America/Los_Angeles" + # Idempotent: jarvis serve calls this on every startup, and create_task + # persists to scheduler.db. Without this check each restart added one + # more copy of the daily cron -- 68 duplicates accumulated, and when due + # they all fired back-to-back, monopolizing the (single-slot) Ollama + # queue so real chat messages stalled behind them. + prompt = "Run the proactive agent: collect overnight data, execute approved actions, notify pending approvals." + existing = [ + t for t in scheduler.list_tasks(status="active") + if t.agent == "proactive" and t.prompt == prompt + ] + if existing: + # Keep the first, cancel any extra duplicates from earlier restarts. + for dup in existing[1:]: + try: + scheduler.cancel_task(dup.id) + except Exception: + logger.warning("Failed to cancel duplicate proactive task %s", dup.id) + return existing[0] + return scheduler.create_task( - prompt="Run the proactive agent: collect overnight data, execute approved actions, notify pending approvals.", + prompt=prompt, schedule_type="cron", schedule_value=cron_expr, agent="proactive",