-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
156 lines (138 loc) · 5.9 KB
/
main.py
File metadata and controls
156 lines (138 loc) · 5.9 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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
"""Entry point for the Dynamic Reflexive Memory application.
Updates:
v0.1 - 2025-11-06 - Added CLI/GUI bootstrap with configuration loading, logging setup, and sample task execution pipeline.
v0.2 - 2025-11-07 - Routed CLI execution through the LiveTaskLoop orchestrator.
v0.3 - 2025-11-06 - Added startup health checks with warning surface.
v0.4 - 2025-11-07 - Loaded environment variables from .env during startup.
v0.5 - 2025-11-07 - Captured optional human review feedback in CLI workflows.
v0.6 - 2025-11-07 - Persisted workflow preference and window state between sessions.
"""
from __future__ import annotations
import argparse
import logging
import logging.config
from pathlib import Path
from typing import Optional
from dotenv import load_dotenv
from config.settings import AppConfig, get_app_config, resolve_config_path
from core.exceptions import DRMError, HealthCheckError, WorkflowError
from core.health import run_startup_checks
from core.live_loop import LiveTaskLoop
from core.user_settings import UserSettingsManager
from gui.app import launch_gui
def setup_logging(logging_config: Optional[Path] = None) -> None:
"""Configure Python logging using the provided configuration file."""
config_path = logging_config or Path(__file__).parent / "config" / "logging.conf"
if not config_path.exists():
logging.basicConfig(level=logging.INFO)
logging.getLogger("drm").warning(
"Logging configuration %s not found; using basicConfig.", config_path
)
return
logging.config.fileConfig(config_path, disable_existing_loggers=False)
def run_cli(
config: AppConfig,
task: Optional[str] = None,
workflow: Optional[str] = None,
human_feedback: Optional[str] = None,
user_settings: Optional[UserSettingsManager] = None,
) -> None:
"""Run a simple CLI workflow as a fallback when GUI is unavailable."""
logger = logging.getLogger("drm.cli")
task_loop = LiveTaskLoop(config, user_settings=user_settings)
prompt_text = task or "Summarise today's objectives based on existing memory."
workflow_preference = workflow
if workflow_preference is None and user_settings is not None:
workflow_preference = user_settings.settings.last_workflow
try:
outcome = task_loop.run_task(
task=prompt_text,
workflow_override=workflow_preference,
human_feedback=human_feedback,
)
except DRMError as exc:
if isinstance(exc, WorkflowError):
logger.warning(
"Workflow execution unavailable: %s. The prompt can be sent manually.",
exc,
)
else:
logger.error("Task execution failed: %s", exc)
return
logger.info(
"Executed workflow '%s' (reason: %s, score=%.2f)",
outcome.selection.workflow,
outcome.selection.rationale,
outcome.selection.score,
)
logger.info("Compiled prompt:\n%s", outcome.request.prompt)
logger.info("Task result (%s): %s", outcome.result.workflow, outcome.result.content)
logger.info(
"Review verdict: %s (auto=%s, quality=%s)",
outcome.review.verdict,
outcome.review.auto_verdict,
f"{outcome.review.quality_score:.2f}" if outcome.review.quality_score is not None else "n/a",
)
if outcome.review.suggestions:
logger.info("Review suggestions: %s", "; ".join(outcome.review.suggestions))
logger.info("Review notes: %s", outcome.review.notes)
if human_feedback:
logger.info("Human feedback applied: %s", human_feedback)
if outcome.drift_advisory:
logger.warning("Controller advisory: %s", outcome.drift_advisory)
if outcome.mitigation_summary:
logger.info("Mitigation actions: %s", outcome.mitigation_summary)
def main(argv: Optional[list[str]] = None) -> int:
"""Parse CLI arguments and dispatch the selected mode."""
load_dotenv()
parser = argparse.ArgumentParser(description="Dynamic Reflexive Memory runner.")
parser.add_argument("--mode", choices=["gui", "cli"], default="gui")
parser.add_argument("--config", type=Path, help="Path to configuration file.")
parser.add_argument("--task", type=str, help="Task prompt for CLI mode.")
parser.add_argument("--workflow", type=str, help="Workflow override for execution.")
parser.add_argument("--logging-config", type=Path, help="Path to logging configuration.")
parser.add_argument("--feedback", type=str, help="Optional human review feedback for the task.")
args = parser.parse_args(argv)
try:
config_path = resolve_config_path(args.config) if args.config else resolve_config_path()
config = get_app_config(config_path)
setup_logging(args.logging_config)
user_settings = UserSettingsManager()
warnings = run_startup_checks(config)
for warning in warnings:
logging.getLogger("drm").warning("Startup check: %s", warning)
except HealthCheckError as exc:
logging.basicConfig(level=logging.ERROR)
logging.getLogger("drm").error("Startup health check failed: %s", exc)
return 1
except DRMError as exc:
logging.basicConfig(level=logging.ERROR)
logging.getLogger("drm").error("Startup failed: %s", exc)
return 1
if args.mode == "gui":
gui_result = launch_gui(
config,
user_settings=user_settings,
config_path=config_path,
)
if gui_result is not None:
return gui_result
logging.getLogger("drm").info("Falling back to CLI mode.")
run_cli(
config,
args.task,
args.workflow,
args.feedback,
user_settings=user_settings,
)
return 0
run_cli(
config,
args.task,
args.workflow,
args.feedback,
user_settings=user_settings,
)
return 0
if __name__ == "__main__":
raise SystemExit(main())