From 9283877204b08d25e39a24bccdcf8f8f93fc92f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Kaz?= Date: Wed, 11 Mar 2026 13:11:45 +0300 Subject: [PATCH 1/2] fix(cron): pass session_db to AIAgent so cron messages are persisted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cron jobs create AIAgent without passing session_db, so messages from cron runs (and their delegate_task subagents) are never written to the SQLite session store. This means session_search cannot find any cron conversation history — the same class of bug fixed for the gateway in 8aa531c (PR #105). Initialize SessionDB in run_job() and pass it to AIAgent, following the identical pattern used in gateway/run.py. --- cron/scheduler.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/cron/scheduler.py b/cron/scheduler.py index c80122ce8..51d7db430 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -156,6 +156,15 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: """ from run_agent import AIAgent + # Initialize SQLite session store so cron job messages are persisted + # and discoverable via session_search (same pattern as gateway/run.py). + _session_db = None + try: + from hermes_state import SessionDB + _session_db = SessionDB() + except Exception as e: + logger.debug("Job '%s': SQLite session store not available: %s", job.get("id", "?"), e) + job_id = job["id"] job_name = job["name"] prompt = job["prompt"] @@ -260,7 +269,8 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: providers_order=pr.get("order"), provider_sort=pr.get("sort"), quiet_mode=True, - session_id=f"cron_{job_id}_{_hermes_now().strftime('%Y%m%d_%H%M%S')}" + session_id=f"cron_{job_id}_{_hermes_now().strftime('%Y%m%d_%H%M%S')}", + session_db=_session_db, ) result = agent.run_conversation(prompt) From f5cf1f8a459d8ee8f0b3c3f4fb62e015e77b333d Mon Sep 17 00:00:00 2001 From: teknium1 Date: Sat, 14 Mar 2026 00:12:34 -0700 Subject: [PATCH 2/2] fix(cron): tag persisted cron sessions and test wiring - store cron-run sessions with source=cron instead of falling back to cli - close the per-run SessionDB after completion - add regression coverage for cron session_db/platform wiring --- cron/scheduler.py | 6 ++++++ tests/cron/test_scheduler.py | 41 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/cron/scheduler.py b/cron/scheduler.py index 51d7db430..12d355cd1 100644 --- a/cron/scheduler.py +++ b/cron/scheduler.py @@ -269,6 +269,7 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: providers_order=pr.get("order"), provider_sort=pr.get("sort"), quiet_mode=True, + platform="cron", session_id=f"cron_{job_id}_{_hermes_now().strftime('%Y%m%d_%H%M%S')}", session_db=_session_db, ) @@ -325,6 +326,11 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]: # Clean up injected env vars so they don't leak to other jobs for key in ("HERMES_SESSION_PLATFORM", "HERMES_SESSION_CHAT_ID", "HERMES_SESSION_CHAT_NAME"): os.environ.pop(key, None) + if _session_db: + try: + _session_db.close() + except Exception as e: + logger.debug("Job '%s': failed to close SQLite session store: %s", job_id, e) def tick(verbose: bool = True) -> int: diff --git a/tests/cron/test_scheduler.py b/tests/cron/test_scheduler.py index 312e80102..4314b5ac0 100644 --- a/tests/cron/test_scheduler.py +++ b/tests/cron/test_scheduler.py @@ -106,6 +106,47 @@ class TestDeliverResultMirrorLogging: ) +class TestRunJobSessionPersistence: + def test_run_job_passes_session_db_and_cron_platform(self, tmp_path): + job = { + "id": "test-job", + "name": "test", + "prompt": "hello", + } + fake_db = MagicMock() + + with patch("cron.scheduler._hermes_home", tmp_path), \ + patch("cron.scheduler._resolve_origin", return_value=None), \ + patch("dotenv.load_dotenv"), \ + patch("hermes_state.SessionDB", return_value=fake_db), \ + patch( + "hermes_cli.runtime_provider.resolve_runtime_provider", + return_value={ + "api_key": "test-key", + "base_url": "https://example.invalid/v1", + "provider": "openrouter", + "api_mode": "chat_completions", + }, + ), \ + patch("run_agent.AIAgent") as mock_agent_cls: + mock_agent = MagicMock() + mock_agent.run_conversation.return_value = {"final_response": "ok"} + mock_agent_cls.return_value = mock_agent + + success, output, final_response, error = run_job(job) + + assert success is True + assert error is None + assert final_response == "ok" + assert "ok" in output + + kwargs = mock_agent_cls.call_args.kwargs + assert kwargs["session_db"] is fake_db + assert kwargs["platform"] == "cron" + assert kwargs["session_id"].startswith("cron_test-job_") + fake_db.close.assert_called_once() + + class TestRunJobConfigLogging: """Verify that config.yaml parse failures are logged, not silently swallowed."""