Compare commits

...

2 Commits

Author SHA1 Message Date
7ac8d0268f test(cron): add tests for cloud-context warning injection
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m13s
2026-04-14 01:18:16 +00:00
2e59f8540d fix(cron): inject cloud-context warning when prompt refs localhost
Fixes #378, Closes #456

When a cron job runs on a cloud endpoint but its prompt references
local services (Ollama, localhost ports, etc.), inject a SYSTEM NOTE
telling the agent it cannot reach localhost so it reports the
limitation instead of wasting iterations on doomed connections.
2026-04-14 01:17:25 +00:00
2 changed files with 153 additions and 0 deletions

View File

@@ -12,6 +12,7 @@ import asyncio
import concurrent.futures
import json
import logging
import re
import os
import subprocess
import sys
@@ -544,6 +545,55 @@ def _run_job_script(script_path: str) -> tuple[bool, str]:
except Exception as exc:
return False, f"Script execution failed: {exc}"
# ---------------------------------------------------------------------------
# Cloud-context warning for local-service references (#378, #456)
# ---------------------------------------------------------------------------
_LOCAL_SERVICE_PATTERNS = [
re.compile(r'localhost:\d+', re.IGNORECASE),
re.compile(r'127\.0\.0\.1:\d+'),
re.compile(r'check\s+ollama', re.IGNORECASE),
re.compile(r'ollama\s+(is\s+)?respond', re.IGNORECASE),
re.compile(r'curl\s+localhost', re.IGNORECASE),
re.compile(r'curl\s+127\.', re.IGNORECASE),
re.compile(r'curl\s+local', re.IGNORECASE),
re.compile(r'ping\s+localhost', re.IGNORECASE),
re.compile(r'poll(ing)?\s+local', re.IGNORECASE),
re.compile(r'check\s+service\s+respond', re.IGNORECASE),
re.compile(r'11434'), # Ollama default port
re.compile(r'11435'), # common alt Ollama port
]
def _detect_local_service_refs(prompt: str) -> list[str]:
"""Return list of local-service reference descriptions found in prompt."""
refs = []
for pat in _LOCAL_SERVICE_PATTERNS:
m = pat.search(prompt)
if m:
refs.append(m.group(0))
return refs
def _inject_cloud_context(prompt: str, refs: list[str], provider: str) -> str:
"""Prepend a SYSTEM NOTE so the agent knows it cannot reach localhost."""
refs_str = ", ".join(f'"{r}"' for r in refs)
warning = (
"[SYSTEM NOTE — cloud endpoint]
"
f"You are running on a cloud inference endpoint ({provider}). "
f"Your prompt references local services: {refs_str}. "
"You CANNOT reach localhost or any local network address from this endpoint. "
"Do NOT attempt curl, ping, SSH, or any network calls to localhost. "
"Instead, report to the user that this job requires a local model endpoint "
"to check local services, and suggest they re-run with a local provider.
"
)
return warning + prompt
def _build_job_prompt(job: dict) -> str:
"""Build the effective prompt for a cron job, optionally loading one or more skills first."""
@@ -817,6 +867,18 @@ def run_job(job: dict) -> tuple[bool, str, str, Optional[str]]:
job_name,
)
# Inject cloud-context warning when prompt references local services (#378)
if _is_cloud:
_local_refs = _detect_local_service_refs(prompt)
if _local_refs:
_provider_name = turn_route["runtime"].get("provider", "cloud")
prompt = _inject_cloud_context(prompt, _local_refs, _provider_name)
logger.info(
"Job '%s': injected cloud-context warning for local refs: %s",
job_name,
_local_refs,
)
_agent_kwargs = _safe_agent_kwargs({
"model": turn_route["model"],
"api_key": turn_route["runtime"].get("api_key"),

View File

@@ -0,0 +1,91 @@
"""Tests for cloud-context warning injection (#378, #456)."""
import pytest
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent))
from cron.scheduler import (
_LOCAL_SERVICE_PATTERNS,
_detect_local_service_refs,
_inject_cloud_context,
)
class TestDetectLocalServiceRefs:
"""Pattern detection for local service references in prompts."""
def test_localhost_with_port(self):
refs = _detect_local_service_refs("Check localhost:11434 is up")
assert len(refs) >= 1
assert any("11434" in r for r in refs)
def test_127_with_port(self):
refs = _detect_local_service_refs("curl http://127.0.0.1:8080/health")
assert len(refs) >= 1
def test_check_ollama(self):
refs = _detect_local_service_refs("Check Ollama is responding")
assert len(refs) >= 1
def test_ollama_responding(self):
refs = _detect_local_service_refs("Verify Ollama responding on this machine")
assert len(refs) >= 1
def test_curl_localhost(self):
refs = _detect_local_service_refs("curl localhost and report status")
assert len(refs) >= 1
def test_ping_localhost(self):
refs = _detect_local_service_refs("ping localhost to check connectivity")
assert len(refs) >= 1
def test_no_false_positive_cloud(self):
refs = _detect_local_service_refs("Check the weather in Paris today")
assert len(refs) == 0
def test_no_false_positive_api(self):
refs = _detect_local_service_refs("Call the OpenRouter API endpoint")
assert len(refs) == 0
def test_multiple_refs(self):
refs = _detect_local_service_refs("curl localhost:11434 then ping localhost")
assert len(refs) >= 2
class TestInjectCloudContext:
"""Cloud-context warning injection."""
def test_prepends_warning(self):
prompt = "Check Ollama is responding"
result = _inject_cloud_context(prompt, ["Check Ollama"], "nous")
assert result.startswith("[SYSTEM NOTE")
assert "nous" in result
assert prompt in result
def test_preserves_original_prompt(self):
prompt = "Check Ollama at localhost:11434"
result = _inject_cloud_context(prompt, ["localhost:11434"], "openrouter")
assert prompt in result
def test_mentions_cannot_reach(self):
prompt = "curl localhost"
result = _inject_cloud_context(prompt, ["curl localhost"], "nous")
assert "CANNOT reach" in result or "cannot reach" in result
def test_suggests_local_provider(self):
prompt = "Check Ollama"
result = _inject_cloud_context(prompt, ["Check Ollama"], "nous")
assert "local" in result.lower()
class TestCloudBypassLocal:
"""Local endpoints should not trigger injection."""
def test_local_endpoint_skips(self):
# The caller checks _is_cloud before calling _detect_local_service_refs
# so this is tested at integration level. Here we verify detection
# still finds refs (the bypass is the caller\'s responsibility).
refs = _detect_local_service_refs("Check Ollama at localhost:11434")
assert len(refs) > 0 # Detection works, caller decides whether to inject