Mirror trace steps to operational logger
The trace JSONL captures every step of a research call (search, fetch, iteration boundaries, synthesis), but the structured operational log only fired at research_started / research_completed, giving administrators no real-time visibility into agent progress. Have TraceLogger.log_step also emit a structlog event using the same action name, fields, and step counter. trace_id and researcher are already bound in contextvars by WebResearcher.research, so every line carries them automatically — no plumbing needed. Volume control: a curated set of milestone actions logs at INFO (start, iteration_start, synthesis_start/complete/error, budget_- exhausted, complete). Chatty per-tool actions (web_search, fetch_url and their *_complete pairs) log at DEBUG. Default MARCHWARDEN_LOG_LEVEL=INFO shows ~9 lines per call; MARCHWARDEN_LOG_LEVEL=DEBUG shows everything. This keeps dev stderr readable while making full step visibility one env var away — and OpenSearch can ingest at DEBUG always. Verified end-to-end: Utah peak query at INFO produces 9 milestone log lines, at DEBUG produces 13. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
4816b9386e
commit
b510902af3
1 changed files with 29 additions and 0 deletions
|
|
@ -14,6 +14,27 @@ import uuid
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from obs import get_logger
|
||||||
|
|
||||||
|
# Actions that get promoted to INFO in the operational log. Everything
|
||||||
|
# else logs at DEBUG so the default INFO level shows ~6-8 milestones per
|
||||||
|
# research call instead of 20+ chatty per-step events. Set
|
||||||
|
# MARCHWARDEN_LOG_LEVEL=DEBUG to see all steps.
|
||||||
|
_INFO_ACTIONS = frozenset(
|
||||||
|
{
|
||||||
|
"start",
|
||||||
|
"iteration_start",
|
||||||
|
"synthesis_start",
|
||||||
|
"synthesis_complete",
|
||||||
|
"synthesis_error",
|
||||||
|
"synthesis_build_error",
|
||||||
|
"budget_exhausted",
|
||||||
|
"complete",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
_log = get_logger("marchwarden.researcher.trace")
|
||||||
|
|
||||||
|
|
||||||
class TraceLogger:
|
class TraceLogger:
|
||||||
"""Logs research steps to a JSONL file.
|
"""Logs research steps to a JSONL file.
|
||||||
|
|
@ -74,6 +95,14 @@ class TraceLogger:
|
||||||
|
|
||||||
self._writer.write(json.dumps(entry, default=str) + "\n")
|
self._writer.write(json.dumps(entry, default=str) + "\n")
|
||||||
self._writer.flush()
|
self._writer.flush()
|
||||||
|
|
||||||
|
# Mirror the trace step into the operational logger so admins
|
||||||
|
# can watch progress in real time. trace_id and researcher are
|
||||||
|
# already bound in contextvars by WebResearcher.research, so
|
||||||
|
# they automatically appear on every line.
|
||||||
|
log_method = _log.info if action in _INFO_ACTIONS else _log.debug
|
||||||
|
log_method(action, step=self._step_counter, decision=decision, **kwargs)
|
||||||
|
|
||||||
return entry
|
return entry
|
||||||
|
|
||||||
def read_entries(self) -> list[dict]:
|
def read_entries(self) -> list[dict]:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue