-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathdaemon.py
More file actions
107 lines (83 loc) · 3.41 KB
/
daemon.py
File metadata and controls
107 lines (83 loc) · 3.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
"""daemon.py — Main daemon runner for the Agent Memory Kit.
Single asyncio event loop that starts all sub-daemons concurrently.
Mirrors the production agent_memory_daemon.py.
Usage:
python daemon.py # Start all daemons
python daemon.py --dry-run # Log only, skip writer and event processor
Sub-daemons started:
- MemoryReader (TCP :9100) — cached file reads
- MemoryWriter (TCP :9101) — atomic validated writes
- LoopDetector (TCP :9102) — repetition detection
- EventProcessor (background) — JSONL ledger → warm files
"""
from __future__ import annotations
import asyncio
import logging
import signal
import sys
from pathlib import Path
# Ensure project root is on path
sys.path.insert(0, str(Path(__file__).parent))
from config import ensure_dirs
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
handlers=[logging.StreamHandler()],
)
logger = logging.getLogger("agent-daemon")
_shutdown = asyncio.Event()
def _handle_signal(signum, frame):
logger.info("Received signal %d, shutting down...", signum)
_shutdown.set()
async def main(dry_run: bool = False) -> None:
"""Run all background daemons concurrently."""
# Register signal handlers (works on Windows for SIGINT)
signal.signal(signal.SIGINT, _handle_signal)
try:
signal.signal(signal.SIGTERM, _handle_signal)
except (OSError, AttributeError):
pass # SIGTERM not available on Windows
# Ensure all directories exist
ensure_dirs()
logger.info("=" * 60)
logger.info(" ANTIGRAVITY AGENT DAEMON")
logger.info(" Protocol: Logic → Proof → Harden → Ship")
logger.info("=" * 60)
# Import sub-daemons
from daemons.memory_reader import run_reader
from daemons.memory_writer import run_writer
from daemons.loop_detector import run_loop_detector
from daemons.event_processor import run_event_processor
tasks: list[asyncio.Task] = []
async def _guarded(name: str, coro):
"""Run a sub-daemon, log and suppress if it fails to bind."""
try:
await coro
except OSError as e:
logger.error(f"[{name}] Failed to start: {e} — other daemons continue.")
except Exception as e:
logger.error(f"[{name}] Crashed: {e}")
# Reader always runs
tasks.append(asyncio.create_task(_guarded("Reader", run_reader(shutdown_event=_shutdown))))
if dry_run:
logger.info("[DRY RUN] Writer and EventProcessor skipped")
else:
tasks.append(asyncio.create_task(_guarded("Writer", run_writer(shutdown_event=_shutdown))))
tasks.append(asyncio.create_task(_guarded("EventProcessor", run_event_processor(shutdown_event=_shutdown))))
# Loop detector always runs
tasks.append(asyncio.create_task(_guarded("LoopDetector", run_loop_detector(shutdown_event=_shutdown))))
logger.info(f"Started {len(tasks)} sub-daemon(s)")
try:
await asyncio.gather(*tasks)
except asyncio.CancelledError:
pass
logger.info("Agent Daemon stopped (all %d tasks completed).", len(tasks))
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="Antigravity Agent Daemon")
parser.add_argument(
"--dry-run", action="store_true",
help="Log actions without making changes",
)
args = parser.parse_args()
asyncio.run(main(dry_run=args.dry_run))