📄 run_shadow.py 5,563 bytes Monday 15:16 📋 Raw

!/usr/bin/env python3

"""Shadow Mode Runner — Silent observer for Family Logistics group.

This script runs the shadow mode bot that:
1. Observes messages from the Family Logistics Telegram group
2. Runs tripwire detection on every message
3. Extracts entities using LLM when tripwire fires
4. Logs everything to shadow.db
5. NEVER SENDS MESSAGES (hard-disabled)

Usage:
cd /home/hoffmann_admin/.openclaw/workspace/services/icarus
python run_shadow.py

Environment:
Requires shadow.env to be loaded (ENV=shadow)

Safety:
- SPEAK_ENABLED=False is hard-coded
- Database is completely isolated (shadow.db)
- No writes to staging or production
- Bot is invisible to family members
"""

import os
import sys
import asyncio
import logging
from pathlib import Path

Load shadow.env BEFORE anything else reads env vars

This ensures TELEGRAM_BOT_TOKEN is set to the shadow bot token (8442671054)

and NOT inherited from the system env (8705120995 = Wadsworth)

from dotenv import load_dotenv
SCRIPT_DIR = Path(file).parent
env_path = SCRIPT_DIR / "shadow.env"
if env_path.exists():
load_dotenv(env_path, override=True)
print(f"[run_shadow] Loaded env from {env_path}")
else:
print(f"[run_shadow] WARNING: {env_path} not found, using system env")

Add services directory to path for icarus package imports

The icarus package is at /home/hoffmann_admin/.openclaw/workspace/services/icarus

We add services/ so that 'import icarus.core...' works

sys.path.insert(0, '/home/hoffmann_admin/.openclaw/workspace/services')

Set up logging before anything else

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - [SHADOW] - %(levelname)s - %(message)s",
)

Verify shadow environment

env = os.environ.get("ENV", "").lower()
if env != "shadow":
logging.error("=" * 60)
logging.error("CRITICAL: ENV must be 'shadow' to run this script")
logging.error("Current ENV=%r", env)
logging.error("Run with: ENV=shadow python run_shadow.py")
logging.error("=" * 60)
sys.exit(1)

Verify we're not accidentally in wrong directory

if "icarus" not in str(Path.cwd()):
logging.error("CRITICAL: Must run from icarus directory")
sys.exit(1)

Now import shadow modules

from icarus.core.observer import ShadowDatabase, ShadowBot
from icarus.core.observer.shadow_polling import shadow_polling_loop
from icarus.core.config import shadow as shadow_config

Import family context

import yaml

def load_family_context():
"""Load family context from YAML."""
config_path = Path(shadow_config.FAMILY_CONFIG_PATH)
if not config_path.exists():
logging.error("Family config not found: %s", config_path)
return {}

try:
    with open(config_path) as f:
        return yaml.safe_load(f)
except Exception as e:
    logging.error("Failed to load family context: %s", e)
    return {}

async def main():
"""Main shadow mode entry point."""
logging.info("=" * 60)
logging.info("SHADOW MODE INITIALIZING")
logging.info("=" * 60)

# Safety checks
assert not shadow_config.SPEAK_ENABLED, "CRITICAL: SPEAK_ENABLED is True"
assert shadow_config.SHADOW_MODE, "CRITICAL: SHADOW_MODE is False"

logging.info("Safety check passed:")
logging.info("  SPEAK_ENABLED = %s", shadow_config.SPEAK_ENABLED)
logging.info("  SHADOW_MODE = %s", shadow_config.SHADOW_MODE)
logging.info("  DATA_DIR = %s", shadow_config.DATA_DIR)
logging.info("  DB_PATH = %s", shadow_config.SHADOW_DB_PATH)

# Initialize database
logging.info("Initializing shadow database...")
db = ShadowDatabase(shadow_config.SHADOW_DB_PATH)

# Load family context
logging.info("Loading family context...")
family_context = load_family_context()
if family_context:
    members = family_context.get("family", {}).get("members", [])
    logging.info("  Loaded %d family members", len(members))
    for m in members:
        logging.info("    - %s (%s)", m.get("name"), m.get("role"))
else:
    logging.warning("  No family context loaded!")

# Initialize shadow bot
logging.info("Initializing shadow bot...")
verbose_dm = os.environ.get("VERBOSE_ADMIN_DM", "false").lower() == "true"
bot = ShadowBot(
    db=db,
    bot_token=shadow_config.TELEGRAM_BOT_TOKEN,
    family_context=family_context,
    llm_url=shadow_config.LLM_URL,
    llm_model=shadow_config.LLM_MODEL,
    admin_chat_id=shadow_config.ADMIN_CHAT_ID,
    verbose_admin_dm=verbose_dm,
)

logging.info("Shadow bot initialized")
logging.info("  VERBOSE_ADMIN_DM = %s", verbose_dm)
logging.info("Bot will observe messages from TELEGRAM_GROUP_ID=%s", 
             shadow_config.TELEGRAM_GROUP_ID)

# Safety confirmation
logging.info("=" * 60)
logging.info("SHADOW MODE ACTIVE")
logging.info("The bot is now OBSERVING ONLY.")
logging.info("It will NOT send any messages.")
logging.info("It is INVISIBLE to family members.")
logging.info("=" * 60)

# Start polling
try:
    await shadow_polling_loop(bot)
except KeyboardInterrupt:
    logging.info("=" * 60)
    logging.info("SHADOW MODE SHUTTING DOWN")
    logging.info("=" * 60)
except Exception as e:
    logging.exception("Shadow mode error: %s", e)
    raise

if name == "main":
try:
asyncio.run(main())
except Exception as e:
logging.exception("Fatal error: %s", e)
sys.exit(1)