Skip to content

Commit eedfb72

Browse files
committed
refine start time and end time
1 parent d1a3c7d commit eedfb72

File tree

8 files changed

+71
-39
lines changed

8 files changed

+71
-39
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,11 +280,12 @@ The agent automatically monitors your Claude Code Pro plan usage and prevents ta
280280
**Visibility:**
281281
- Dashboard: Shows `Pro Usage: X% / Y% limit` in `sle check` (Y = 20% daytime, 80% nighttime)
282282
- Logs: Each usage check logs Pro plan usage with time-based threshold
283-
- Config: Adjustable via `claude_code.pause_threshold_day` and `claude_code.pause_threshold_night` in `config.yaml`
283+
- Config: Adjustable via `claude_code.threshold_day` and `claude_code.threshold_night` in `config.yaml`
284284

285285
**Time-Based Thresholds:**
286286
- **Daytime (8 AM - 8 PM):** Pause at 20% (saves quota for your manual usage)
287287
- **Nighttime (8 PM - 8 AM):** Pause at 80% (agent works aggressively while you sleep)
288+
- Time ranges are configurable via `claude_code.night_start_hour` and `claude_code.night_end_hour` in `config.yaml`
288289
- ⏸️ New task generation pauses at threshold
289290
- ✅ Running tasks complete normally
290291
- 📋 Pending tasks wait in queue
@@ -417,7 +418,7 @@ launchctl list | grep sleepless
417418
|-------|----------|
418419
| Bot not responding | Check `.env` tokens, verify Socket Mode enabled, check logs: `tail -f workspace/data/agent.log` |
419420
| Tasks not executing | Verify Claude Code CLI installed: `npm list -g @anthropic-ai/claude-code`, check workspace permissions |
420-
| Tasks paused (threshold reached) | Usage has reached time-based threshold (20% daytime / 80% nighttime). Wait for window reset (check logs for reset time), or adjust thresholds in `config.yaml` (`claude_code.pause_threshold_day` / `claude_code.pause_threshold_night`). Run `sle check` to see current usage. |
421+
| Tasks paused (threshold reached) | Usage has reached time-based threshold (20% daytime / 80% nighttime). Wait for window reset (check logs for reset time), or adjust thresholds in `config.yaml` (`claude_code.threshold_day` / `claude_code.threshold_night`). Run `sle check` to see current usage. |
421422
| Git commits fail | Install `gh` CLI and authenticate: `gh auth login` |
422423
| Out of credits | Wait for 5-hour window refresh. Review scheduler logs: `tail -f workspace/data/agent.log | grep credit` |
423424
| Database locked | Close other connections, try: `rm workspace/data/tasks.db && python -m sleepless_agent.daemon` |

src/sleepless_agent/config.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
claude_code:
22
binary_path: claude
33
model: claude-sonnet-4-5-20250929
4-
pause_threshold_day: 80.0
5-
pause_threshold_night: 80.0
4+
night_start_hour: 1
5+
night_end_hour: 9
6+
threshold_day: 100.0
7+
threshold_night: 100.0
68
usage_command: claude /usage
79

810
git:

src/sleepless_agent/core/daemon.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,10 @@ def __init__(self) -> None:
7070
daily_budget_usd=10.0,
7171
night_quota_percent=90.0,
7272
usage_command=self.config.claude_code.usage_command,
73-
pause_threshold_day=self.config.claude_code.pause_threshold_day,
74-
pause_threshold_night=self.config.claude_code.pause_threshold_night,
73+
threshold_day=self.config.claude_code.threshold_day,
74+
threshold_night=self.config.claude_code.threshold_night,
75+
night_start_hour=self.config.claude_code.night_start_hour,
76+
night_end_hour=self.config.claude_code.night_end_hour,
7577
)
7678

7779
self.auto_generator = AutoTaskGenerator(
@@ -80,8 +82,10 @@ def __init__(self) -> None:
8082
budget_manager=self.budget_manager,
8183
default_model=self.config.claude_code.model,
8284
usage_command=self.config.claude_code.usage_command,
83-
pause_threshold_day=self.config.claude_code.pause_threshold_day,
84-
pause_threshold_night=self.config.claude_code.pause_threshold_night,
85+
threshold_day=self.config.claude_code.threshold_day,
86+
threshold_night=self.config.claude_code.threshold_night,
87+
night_start_hour=self.config.claude_code.night_start_hour,
88+
night_end_hour=self.config.claude_code.night_end_hour,
8589
)
8690

8791
self.claude = ClaudeCodeExecutor(

src/sleepless_agent/core/executor.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -995,7 +995,7 @@ async def _execute_evaluator_phase(
995995
)
996996

997997
# Use time-based threshold
998-
threshold = config.claude_code.pause_threshold_night if is_nighttime() else config.claude_code.pause_threshold_day
998+
threshold = config.claude_code.threshold_night if is_nighttime(night_start_hour=config.claude_code.night_start_hour, night_end_hour=config.claude_code.night_end_hour) else config.claude_code.threshold_day
999999
should_pause, reset_time = checker.check_should_pause(
10001000
threshold_percent=threshold
10011001
)

src/sleepless_agent/interfaces/cli.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,11 @@ def build_context(args: argparse.Namespace) -> CLIContext:
5252

5353
config = get_config()
5454

55-
db_path = Path(config.agent.db_path)
56-
results_path = Path(config.agent.results_path)
55+
# Infer paths from workspace_root, similar to WorkspaceSetup._apply_workspace_root
56+
workspace_root = Path(config.agent.workspace_root).expanduser().resolve()
57+
data_dir = workspace_root / "data"
58+
db_path = data_dir / "tasks.db"
59+
results_path = data_dir / "results"
5760
logs_dir = db_path.parent # Use same directory as db_path for metrics
5861

5962
db_path.parent.mkdir(parents=True, exist_ok=True)
@@ -146,7 +149,7 @@ def command_check(ctx: CLIContext) -> int:
146149
command=config.claude_code.usage_command,
147150
)
148151
usage_percent, _ = checker.get_usage()
149-
threshold = config.claude_code.pause_threshold_night if is_nighttime() else config.claude_code.pause_threshold_day
152+
threshold = config.claude_code.threshold_night if is_nighttime(night_start_hour=config.claude_code.night_start_hour, night_end_hour=config.claude_code.night_end_hour) else config.claude_code.threshold_day
150153
pro_plan_usage_info = f" • Pro Usage: {usage_percent:.0f}% / {threshold:.0f}% limit"
151154
except Exception as exc:
152155
logger.debug(f"Could not fetch Pro plan usage for dashboard: {exc}")

src/sleepless_agent/scheduling/auto_generator.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,21 @@ def __init__(
4242
budget_manager: BudgetManager,
4343
default_model: str,
4444
usage_command: str,
45-
pause_threshold_day: float,
46-
pause_threshold_night: float,
45+
threshold_day: float,
46+
threshold_night: float,
47+
night_start_hour: int = 20,
48+
night_end_hour: int = 8,
4749
):
4850
"""Initialize auto-generator with database session and config"""
4951
self.session = db_session
5052
self.config = config
5153
self.budget_manager = budget_manager
5254
self.default_model = default_model
5355
self.usage_command = usage_command
54-
self.pause_threshold_day = pause_threshold_day
55-
self.pause_threshold_night = pause_threshold_night
56+
self.threshold_day = threshold_day
57+
self.threshold_night = threshold_night
58+
self.night_start_hour = night_start_hour
59+
self.night_end_hour = night_end_hour
5660

5761
# Track generation metadata
5862
self.last_generation_time: Optional[datetime] = None
@@ -82,7 +86,7 @@ def _should_generate(self) -> bool:
8286

8387
try:
8488
checker = ProPlanUsageChecker(command=self.usage_command)
85-
threshold = self.pause_threshold_night if is_nighttime() else self.pause_threshold_day
89+
threshold = self.threshold_night if is_nighttime(night_start_hour=self.night_start_hour, night_end_hour=self.night_end_hour) else self.threshold_day
8690
should_pause, _ = checker.check_should_pause(threshold_percent=threshold)
8791

8892
# Generate only when NOT paused (i.e., usage < threshold)

src/sleepless_agent/scheduling/scheduler.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Smart task scheduler with usage tracking and time-based quotas"""
22

3-
from datetime import datetime, timedelta
3+
from datetime import datetime, timedelta, timezone
44
from decimal import Decimal
55
from typing import Any, Dict, List, Optional, Tuple
66

@@ -192,8 +192,10 @@ def __init__(
192192
daily_budget_usd: float = 10.0,
193193
night_quota_percent: float = 90.0,
194194
usage_command: str = "/usage",
195-
pause_threshold_day: float = 20.0,
196-
pause_threshold_night: float = 80.0,
195+
threshold_day: float = 20.0,
196+
threshold_night: float = 80.0,
197+
night_start_hour: int = 20,
198+
night_end_hour: int = 8,
197199
):
198200
"""Initialize scheduler
199201
@@ -203,14 +205,18 @@ def __init__(
203205
daily_budget_usd: Daily budget in USD (default: $10)
204206
night_quota_percent: Percentage for night usage (default: 90%)
205207
usage_command: CLI command to check usage (default: "/usage")
206-
pause_threshold_day: Pause threshold during daytime (default: 20%)
207-
pause_threshold_night: Pause threshold during nighttime (default: 80%)
208+
threshold_day: Pause threshold during daytime (default: 20%)
209+
threshold_night: Pause threshold during nighttime (default: 80%)
210+
night_start_hour: Hour when night starts (default: 20 for 8 PM)
211+
night_end_hour: Hour when night ends (default: 8 for 8 AM)
208212
"""
209213
self.task_queue = task_queue
210214
self.max_parallel_tasks = max_parallel_tasks
211215
self.usage_command = usage_command
212-
self.pause_threshold_day = pause_threshold_day
213-
self.pause_threshold_night = pause_threshold_night
216+
self.threshold_day = threshold_day
217+
self.threshold_night = threshold_night
218+
self.night_start_hour = night_start_hour
219+
self.night_end_hour = night_end_hour
214220

215221
# Budget management with time-based allocation
216222
session = self.task_queue.SessionLocal()
@@ -280,7 +286,7 @@ def _check_scheduling_allowed(self) -> Tuple[bool, Dict[str, Any]]:
280286
context = {
281287
"event": "scheduler.pause.pending",
282288
"reason": "usage_pause",
283-
"resume_at": self.usage_pause_until.astimezone().strftime('%Y-%m-%d %H:%M:%S %Z'),
289+
"resume_at": self.usage_pause_until.replace(tzinfo=timezone.utc).astimezone().strftime('%Y-%m-%d %H:%M:%S %Z'),
284290
"detail": self._format_remaining(remaining),
285291
"decision_logic": "Pausing: in pause window, waiting for resume time",
286292
}
@@ -308,7 +314,7 @@ def _check_scheduling_allowed(self) -> Tuple[bool, Dict[str, Any]]:
308314
"reason": "usage_threshold",
309315
"usage_percent": usage_percent,
310316
"threshold_percent": effective_threshold,
311-
"resume_at": pause_until.astimezone().strftime('%Y-%m-%d %H:%M:%S %Z'),
317+
"resume_at": pause_until.replace(tzinfo=timezone.utc).astimezone().strftime('%Y-%m-%d %H:%M:%S %Z'),
312318
"detail": self._format_remaining(remaining),
313319
"decision_logic": f"Pausing: usage {usage_percent}% >= threshold {effective_threshold}% ({time_period})",
314320
}
@@ -337,10 +343,10 @@ def _get_effective_threshold(self) -> float:
337343
338344
Returns:
339345
Threshold percentage (0-100) from config:
340-
- Daytime: pause_threshold_day
341-
- Nighttime: pause_threshold_night
346+
- Daytime: threshold_day
347+
- Nighttime: threshold_night
342348
"""
343-
return self.pause_threshold_night if is_nighttime() else self.pause_threshold_day
349+
return self.threshold_night if is_nighttime(night_start_hour=self.night_start_hour, night_end_hour=self.night_end_hour) else self.threshold_day
344350

345351
@staticmethod
346352
def _format_remaining(delta: timedelta) -> str:

src/sleepless_agent/scheduling/time_utils.py

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,40 @@
99
NIGHT_END_HOUR = 8 # 8 AM
1010

1111

12-
def is_nighttime(dt: Optional[datetime] = None) -> bool:
12+
def is_nighttime(
13+
dt: Optional[datetime] = None,
14+
night_start_hour: int = NIGHT_START_HOUR,
15+
night_end_hour: int = NIGHT_END_HOUR,
16+
) -> bool:
1317
"""Return True when the provided datetime falls within the night window."""
1418
if dt is None:
1519
dt = datetime.now()
1620
hour = dt.hour
17-
return hour >= NIGHT_START_HOUR or hour < NIGHT_END_HOUR
21+
return hour >= night_start_hour or hour < night_end_hour
1822

1923

20-
def get_time_label(dt: Optional[datetime] = None) -> str:
24+
def get_time_label(
25+
dt: Optional[datetime] = None,
26+
night_start_hour: int = NIGHT_START_HOUR,
27+
night_end_hour: int = NIGHT_END_HOUR,
28+
) -> str:
2129
"""Return a human-readable label for the current time period."""
22-
return "night" if is_nighttime(dt) else "daytime"
30+
return "night" if is_nighttime(dt, night_start_hour, night_end_hour) else "daytime"
2331

2432

25-
def current_period_start(dt: Optional[datetime] = None) -> datetime:
33+
def current_period_start(
34+
dt: Optional[datetime] = None,
35+
night_start_hour: int = NIGHT_START_HOUR,
36+
night_end_hour: int = NIGHT_END_HOUR,
37+
) -> datetime:
2638
"""Return the local timestamp marking the start of the current period."""
2739
dt = dt or datetime.now()
2840
today = dt.replace(hour=0, minute=0, second=0, microsecond=0)
2941

30-
if is_nighttime(dt):
31-
night_start = today.replace(hour=NIGHT_START_HOUR)
32-
if dt.hour < NIGHT_END_HOUR:
33-
night_start = (today - timedelta(days=1)).replace(hour=NIGHT_START_HOUR)
42+
if is_nighttime(dt, night_start_hour, night_end_hour):
43+
night_start = today.replace(hour=night_start_hour)
44+
if dt.hour < night_end_hour:
45+
night_start = (today - timedelta(days=1)).replace(hour=night_start_hour)
3446
return night_start
3547

36-
return today.replace(hour=NIGHT_END_HOUR)
48+
return today.replace(hour=night_end_hour)

0 commit comments

Comments
 (0)