Systematic rename of all semantic anchors (#region, [DEF], @RELATION) across 1400+ files — backend Python, frontend Svelte/TS, specs, docs: - Flat anchors become Namespace.Module.Entity - @RELATION references updated to match new anchor paths - Zero business logic changes
5.4 KiB
[DEF:Doc.Adr.ADR0017:ADR]
@STATUS ACCEPTED — Updated 2026-07-12
@PURPOSE Make the logging system produce agent-understandable traces by editing logging statements at call sites in business modules, and by consolidating all logging infrastructure into shared/ as single source of truth.
@RELATION DEPENDS_ON -> [Doc.Adr.ADR0002]
@RELATION IMPLEMENTS -> [molecular-cot-logging skill]
@RELATION REFINES -> [Std.Semantics.Python]
@RATIONALE Repeated low-value statements (e.g. "User principal resolved", "Fetching user by username", "Reusing cached...", "Validated ENCRYPTION_KEY") appear on nearly every request and destroy signal-to-noise for an agent trying to reconstruct execution and decisions. Adding suppression, extra derive logic, or more special cases inside the logger core (as attempted in LOG-00x iterations) masks the problem, increases complexity of the logging infrastructure, and makes the system harder to reason about. The correct fix is to remove or elevate the intent at the source so that what remains in the trace is a high-level narrative of decisions (REASON / REFLECT / EXPLORE) with qualified src and relevant payload.
@REJECTED "Only patch the logger" approach (heavy suppression lists, always derive, special-casing every noisy phrase) — rejected because it treats symptoms, leads to fragile matching, and violates the principle that a log statement should be meaningful at the call site without relying on post-processing magic.
@REJECTED Leaving all noisy statements as-is and relying only on pretty-printer / UI filters — rejected because raw logs (the primary artifact for agents, debugging, and post-mortems) would remain polluted.
@REJECTED Keeping separate backend copy of cot_logger.py — rejected because skip lists diverged (backend had "gunicorn", shared didn't), cot_span existed only in backend, suppression lists had 4 copies.
@CONSEQUENCES
- Call sites must be reviewed and cleaned; new code must produce high-level intents.
- Suppression logic in shared/cot_logger.py is the SINGLE suppression list, not 4 copies across backend/logger.py, shared/logger.py, pretty_cot.py.
- derive_src and explicit src= remain as light infrastructure to guarantee qualified src without forcing every call site to hardcode strings.
- "Handle API request" + "API request completed" framing is kept as a useful boundary (see implementation in app.py).
- Agents and humans should be able to follow a trace_id and see a story, not a dump of implementation details.
- Frontend trace_id is generated immediately at module init (crypto.randomUUID), never "no-trace".
- Frontend sends X-Trace-ID header for backend correlation; backend accepts it.
- Frontend API wrappers emit REASON/REFLECT/EXPLORE with elapsed_ms timing.
- BeliefFormatter removed — text-based markers violate Molecular CoT protocol.
- MarkerLogger removed — deprecated, zero usages.
- belief_scope REFLECT changed from "Coherence OK" to "{anchor}: completed" with elapsed_ms payload.
- Trois listes de suppression distinctes (backend/logger.py, shared/logger.py × 3, pretty_cot.py) sont désormais une seule dans shared/cot_logger.py.
Decision
We adopt the following principles for agent-centric logging (in addition to the Molecular CoT protocol defined in the skill):
-
Edit at the source. When a log statement adds noise on hot paths (per-request auth resolution, cache hits, init checks), either remove it, move it to DEBUG, or replace it with a higher-level intent that describes the decision or state change.
-
Qualified src. Use explicit
src="..."on logger calls when the automatic derive_src would land on a wrapper (middleware, anyio task, etc.). The logger provides derive_src as a convenience, not as the only mechanism. -
Single suppression list in shared/cot_logger.py. All four previous copies (backend/logger.py _ROUTINE_INFRA_PHRASES, shared/logger.py CotJsonFormatter.format._routine, shared/logger.py reason/reflect._routine, pretty_cot.py intent filter) are merged into one tuple in shared. Backend's configure_logger() calls set_routine_suppression() to configure it at runtime.
-
All CoT primitives in shared/cot_logger.py — ContextVars, seed_trace_id, derive_src, log, cot_span. Backend deleted its copy (backend/src/core/cot_logger.py) and imports from shared.
-
CotJsonFormatter imported from shared/logger.py — no suppression in the formatter (formatters format, they don't filter).
-
Frontend trace_id never "no-trace". Generated via crypto.randomUUID() at module init. resetTraceId() called on SPA navigation. X-Trace-ID header sent to backend.
-
Mandatory REFLECT/EXPLORE after REASON in API wrappers. Every fetchApi/postApi/deleteApi/requestApi emits REASON before fetch and REFLECT (success) or EXPLORE (failure) after, with elapsed_ms timing.
-
Global error handlers on frontend. window.addEventListener('error') and window.addEventListener('unhandledrejection') capture otherwise invisible errors as structured EXPLORE markers.
-
Preserve useful framing. "Handle API request" / "API request completed" (with method, path, status) provides valuable boundaries and is kept.
-
Semantic markup. All logging-related changes are documented with @ADR, @RATIONALE, @REJECTED tags.
Enforcement happens through code review and by loading the molecular-cot-logging and semantics-python skills before touching logging statements.