diff --git a/clk_harness/orchestration/agent.py b/clk_harness/orchestration/agent.py index 81a2e1e..3b647b0 100644 --- a/clk_harness/orchestration/agent.py +++ b/clk_harness/orchestration/agent.py @@ -802,6 +802,9 @@ def _progress(kind: str, message: str) -> None: provider=provider.describe(), run_id=run_id, ) + self._observer_log( + f"consensus :: {name} :: sample #{sample} dispatching ({agent_name})" + ) if self.observer is not None: self.observer.begin(label, sample_objective) self.observer.prompt_sent(label, prompt) @@ -823,6 +826,10 @@ def _progress(kind: str, message: str) -> None: self._record(arun, prompt, provider.describe()) if self.observer is not None: self.observer.end(label, arun) + self._observer_log( + f"consensus :: {name} :: sample #{sample} done " + f"({'ok' if resp.ok else 'error: ' + (resp.error or '?')})" + ) log_event( self.paths, "consensus_sample_response", @@ -935,6 +942,9 @@ def meta_draft_dispatch_prompt( f"Worker system prompt preview: {system_preview or '(missing)'}\n\n" f"Original objective:\n{base_objective}\n" ) + self._observer_log( + f"meta :: drafting dispatch prompt for {agent_name} via chief" + ) run = self.run( "chief", objective, @@ -995,6 +1005,9 @@ def meta_draft_role_prompt( "Keep it under 50 lines. Make the role's distinct ownership explicit\n" "compared with existing roles." ) + self._observer_log( + f"meta :: drafting role prompt for {role_name} via chief" + ) run = self.run( "chief", objective, diff --git a/clk_harness/orchestration/autoresearch_loop.py b/clk_harness/orchestration/autoresearch_loop.py index b8af2fd..a8e2ade 100644 --- a/clk_harness/orchestration/autoresearch_loop.py +++ b/clk_harness/orchestration/autoresearch_loop.py @@ -68,10 +68,21 @@ def run(self, *, dry_run: bool = False) -> List[Experiment]: def _robustness_cfg(self) -> Dict[str, Any]: return dict(self.runner.clk_cfg.get("robustness") or {}) + def _observer_log(self, line: str) -> None: + """Mirror a status line to both the log file and the TUI status pane.""" + log(line) + obs = getattr(self.runner, "observer", None) + if obs is not None: + try: + obs.log(line) + except Exception: + pass + def _step(self, idx: int, *, dry_run: bool) -> Experiment: started = datetime.now().isoformat(timespec="seconds") before = head_sha(self.paths.root) min_chars = int(self._robustness_cfg().get("min_response_chars") or 40) + self._observer_log(f"autoresearch #{idx} :: survey :: dispatching ralph") survey = self.runner.run( "ralph", f"Autoresearch step #{idx}: survey state and propose next experiment.", @@ -107,12 +118,14 @@ def _step(self, idx: int, *, dry_run: bool) -> Experiment: f"Open question #{idx}", ) + self._observer_log(f"autoresearch #{idx} :: analyst :: investigating: {question[:60]}") analyst = self.runner.run( "analyst", f"Investigate: {question}", extra={"iteration": idx, "loop": "autoresearch"}, dry_run=dry_run, ) + self._observer_log(f"autoresearch #{idx} :: critic :: reviewing findings") critic = self.runner.run( "critic", f"Critique findings on: {question}", diff --git a/clk_harness/orchestration/ralph_loop.py b/clk_harness/orchestration/ralph_loop.py index 68465c3..a1d06bd 100644 --- a/clk_harness/orchestration/ralph_loop.py +++ b/clk_harness/orchestration/ralph_loop.py @@ -69,6 +69,16 @@ def __init__( self.evaluator = evaluator self.max_iterations = max_iterations + def _observer_log(self, line: str) -> None: + """Mirror a status line to both the log file and the TUI status pane.""" + log(line) + obs = getattr(self.runner, "observer", None) + if obs is not None: + try: + obs.log(line) + except Exception: + pass + def run(self, *, dry_run: bool = False) -> List[IterationOutcome]: outcomes: List[IterationOutcome] = [] plateau_streak = 0 @@ -158,8 +168,15 @@ def _handle_plateau(self, idx: int, streak: int, *, dry_run: bool) -> None: # Escalation is already wired via _adaptive_extra setting # careful=true on the next iteration; just record the intent. log_event(self.paths, "ralph_plateau_escalate", agent="ralph", iteration=idx) + self._observer_log( + f"ralph #{idx} :: plateau escalate :: enabling consensus fan-out (streak={streak})" + ) if action in ("reframe_only", "escalate_then_reframe") and streak >= 2 and not dry_run: try: + self._observer_log( + f"ralph #{idx} :: plateau reframe :: dispatching chief to re-cast workflow " + f"(streak={streak})" + ) self.runner.run( "chief", ( @@ -184,6 +201,7 @@ def _handle_plateau(self, idx: int, streak: int, *, dry_run: bool) -> None: def _handle_regression(self, idx: int, *, dry_run: bool) -> None: log_event(self.paths, "ralph_regression_detected", agent="ralph", iteration=idx) log(f"ralph: regression detected at iteration {idx}", level="WARN") + self._observer_log(f"ralph #{idx} :: regression detected :: dispatching critic") if dry_run: return # Ask the critic to look at what just broke before we plan the @@ -251,6 +269,7 @@ def _iterate( base_extra.update(adaptive_extra or {}) # 1. Plan with Ralph + self._observer_log(f"ralph #{idx} :: plan :: dispatching ralph") plan = self.runner.run( "ralph", objective, @@ -300,6 +319,7 @@ def _iterate( # 2. Engineer one slice engineer_extra = dict(base_extra) engineer_extra["from"] = "ralph" + self._observer_log(f"ralph #{idx} :: engineer :: {eng_obj_text[:60]}") engineer = self.runner.run( "engineer", eng_obj_text, @@ -308,6 +328,7 @@ def _iterate( ) # 3. QA pass + self._observer_log(f"ralph #{idx} :: qa :: dispatching audit pass") qa = self.runner.run( "qa", f"Audit changes from iteration #{idx} and validate.", diff --git a/clk_harness/tui.py b/clk_harness/tui.py index 14c651a..1aa32f0 100644 --- a/clk_harness/tui.py +++ b/clk_harness/tui.py @@ -520,7 +520,14 @@ def begin_agent(self, name: str, objective: str) -> None: card.live_rss_kb = "" card.live_idle_s = 0.0 card.live_elapsed_s = 0.0 - self.add_log(f"{name} :: start :: {objective[:80]}", level="INFO") + # Take the first non-empty line of the objective so that multi-line + # objectives (e.g. recovery dispatches that start with a blank line + # after the header) don't produce a stray fragment in the log pane. + _obj_first = next( + (l for l in (objective or "").splitlines() if l.strip()), + (objective or ""), + ) + self.add_log(f"{name} :: start :: {_obj_first[:80]}", level="INFO") def report_progress(self, name: str, kind: str, message: str) -> None: """Capture streaming progress from a provider's subprocess.