···280280 journal_symlink = journal_path / "health" / "echo.log"
281281 assert journal_symlink.is_symlink()
282282 assert journal_symlink.resolve() == log_path.resolve()
283283+284284+285285+def test_process_day_override(journal_path, mock_callosum):
286286+ """Test that day parameter overrides log directory placement."""
287287+ target_day = "20240101"
288288+ managed = ManagedProcess.spawn(["echo", "day test"], day=target_day)
289289+ ref = managed.ref
290290+ managed.wait()
291291+ managed.cleanup()
292292+293293+ # Log should be in target day, not today
294294+ log_path = journal_path / target_day / "health" / f"{ref}_echo.log"
295295+ assert log_path.exists()
296296+ content = log_path.read_text()
297297+ assert "day test" in content
298298+299299+ # Today's health directory should NOT have this log
300300+ from datetime import datetime
301301+302302+ today = datetime.now().strftime("%Y%m%d")
303303+ if today != target_day:
304304+ today_log = journal_path / today / "health" / f"{ref}_echo.log"
305305+ assert not today_log.exists()
306306+307307+ # Day-level symlink in target day
308308+ day_symlink = journal_path / target_day / "health" / "echo.log"
309309+ assert day_symlink.is_symlink()
310310+ assert day_symlink.resolve() == log_path.resolve()
311311+312312+ # Journal-level symlink points to target day
313313+ journal_symlink = journal_path / "health" / "echo.log"
314314+ assert journal_symlink.is_symlink()
315315+ assert journal_symlink.resolve() == log_path.resolve()
316316+317317+318318+def test_run_task_day_override(journal_path, mock_callosum):
319319+ """Test that run_task passes day through to log placement."""
320320+ target_day = "20240201"
321321+ success, exit_code, log_path = run_task(["echo", "task day test"], day=target_day)
322322+323323+ assert success
324324+ assert exit_code == 0
325325+ assert target_day in str(log_path)
326326+ assert log_path.exists()
+2-2
think/dream.py
···8080 cmd_name = cmd_name.replace("-", "_")
81818282 try:
8383- success, exit_code, _log_path = run_task(cmd)
8383+ success, exit_code, _log_path = run_task(cmd, day=day)
8484 if not success:
8585 logging.error(
8686 "Command failed with exit code %s: %s", exit_code, " ".join(cmd)
···127127 listener.start(callback=on_message)
128128129129 try:
130130- _callosum.emit("supervisor", "request", cmd=cmd, ref=ref)
130130+ _callosum.emit("supervisor", "request", cmd=cmd, ref=ref, day=day)
131131132132 if not result_event.wait(timeout=timeout):
133133 logging.error(f"Timeout waiting for {cmd_name} to complete (ref={ref})")
+26-15
think/runner.py
···8888class DailyLogWriter:
8989 """Thread-safe log writer that automatically rolls over at midnight.
90909191+ When ``day`` is provided, the writer is pinned to that day directory
9292+ and midnight rollover is disabled (batch processing of historical days).
9393+9194 Writes to: {JOURNAL_PATH}/{YYYYMMDD}/health/{ref}_{name}.log
92959396 Creates and maintains symlinks:
···97100 When the day changes, automatically closes old file, opens new file, and updates symlinks.
98101 """
99102100100- def __init__(self, ref: str, name: str):
103103+ def __init__(self, ref: str, name: str, day: str | None = None):
101104 self._ref = ref
102105 self._name = name
106106+ self._pinned = day is not None
103107 self._lock = threading.Lock()
104104- self._current_day = _current_day()
108108+ self._current_day = day or _current_day()
105109 self._fh = self._open_log()
106110 self._update_symlinks()
107111···130134 def write(self, message: str) -> None:
131135 """Write message to log, handling day rollover."""
132136 with self._lock:
133133- # Check for day change
134134- day_now = _current_day()
135135- if day_now != self._current_day:
136136- # Close old log
137137- if not self._fh.closed:
138138- self._fh.close()
139139- # Open new log for new day
140140- self._current_day = day_now
141141- self._fh = self._open_log()
142142- # Update symlinks to point to new day's file
143143- self._update_symlinks()
137137+ if not self._pinned:
138138+ # Check for day change
139139+ day_now = _current_day()
140140+ if day_now != self._current_day:
141141+ # Close old log
142142+ if not self._fh.closed:
143143+ self._fh.close()
144144+ # Open new log for new day
145145+ self._current_day = day_now
146146+ self._fh = self._open_log()
147147+ # Update symlinks to point to new day's file
148148+ self._update_symlinks()
144149145150 # Write and flush
146151 self._fh.write(message)
···199204 env: dict | None = None,
200205 ref: str | None = None,
201206 callosum: CallosumConnection | None = None,
207207+ day: str | None = None,
202208 ) -> "ManagedProcess":
203209 """Spawn process with automatic output logging to daily health directory.
204210···207213 env: Optional environment variables (inherits parent env if not provided)
208214 ref: Optional correlation ID (auto-generated if not provided)
209215 callosum: Optional shared CallosumConnection (creates new one if not provided)
216216+ day: Optional day override (YYYYMMDD). When provided, logs are placed
217217+ in that day's health directory instead of today's.
210218211219 Returns:
212220 ManagedProcess instance
···243251 callosum = CallosumConnection()
244252 callosum.start()
245253246246- log_writer = DailyLogWriter(ref, name)
254254+ log_writer = DailyLogWriter(ref, name, day=day)
247255248256 logger.info(f"Starting {name}: {' '.join(cmd)}")
249257···429437 env: dict | None = None,
430438 ref: str | None = None,
431439 callosum: CallosumConnection | None = None,
440440+ day: str | None = None,
432441) -> tuple[bool, int, Path]:
433442 """Run a task to completion with automatic logging (blocking).
434443···442451 env: Optional environment variables
443452 ref: Optional correlation ID (auto-generated if not provided)
444453 callosum: Optional shared CallosumConnection (creates new one if not provided)
454454+ day: Optional day override (YYYYMMDD). When provided, logs are placed
455455+ in that day's health directory instead of today's.
445456446457 Returns:
447458 (success, exit_code, log_path) tuple where success = (exit_code == 0)
···461472 )
462473 # Logs to: {JOURNAL}/{YYYYMMDD}/health/1730476800000_indexer.log
463474 """
464464- managed = ManagedProcess.spawn(cmd, env=env, ref=ref, callosum=callosum)
475475+ managed = ManagedProcess.spawn(cmd, env=env, ref=ref, callosum=callosum, day=day)
465476 log_path = managed.log_writer.path
466477 try:
467478 exit_code = managed.wait(timeout=timeout)