AGENTS.md@src/logging · git:20261004.dae2303 · 2026-10-04 · sha256 6b041739d687eabb

AGENTS.md@src/logging git:20261004.dae2303A

Immutable. This exact content never changes and is served at /api/v1/blob/6b041739d687eabb.

# Logging — Quick Reference

**Full implementation details: `.claude/skills/walnut-ops/SKILL.md`** (incident investigation
playbook, always-on sentinels, levels, redaction patterns, browser log persistence
architecture, investigation commands).

## Essentials

- `import { log } from '../logging/index.js'` → `log.<subsystem>.info('msg', { fields })`;
  child loggers via `log.agent.child('loop')`. `initLogging()` once at startup.
- All lines pass `redactSensitiveText()` before hitting disk (API keys, AWS creds, bearer
  tokens, PEM blocks, `password=`/`token=` values → `[REDACTED]`).
- Browser `console.log/warn/error` are persisted to the same disk log with
  `subsystem: 'browser'` (`console.debug` is NOT — never rely on it). Investigate frontend
  issues with `walnut logs -s browser` — the disk log survives page refresh.
- **A frozen event loop names its culprit** (`src/core/stall-recorder.ts`, on by default,
  `WALNUT_STALL_RECORDER=0` turns it off, `WALNUT_STALL_PROFILE=0` keeps only the counters).
  Every probe-late line of 1 s or more carries `hold`: the loop thread's own CPU against the
  hold (user and system), GC inside it, faults, context switches, heap and RSS, load, and a
  `verdict` (`cpu`, `starved`, `gc`, `paging`, `off-cpu`). `cpu` is Walnut code holding the
  loop, also under machine load: the flight record weighs the kept profile (`profileHold`: the
  share of the hold with code on top, and the top frames) and the thread's own CPU, and adds
  `loadStretched: true` when load made the hold longer. `starved` means runnable but not run
  with little of its own to run (machine load), and then the profile only shows where the
  thread was parked. A rolling
  sampling CPU profile is kept only for a stall of 2 s or more, under
  `<log dir>/stall-profiles/` (newest 40, 3 days, 256 MB), and its
  `event-loop stall flight record` line adds the machine's paging counters, this process's
  compressed bytes and the kernel memory pressure level. Read them with
  `scripts/walnut-logs.sh stalls` and `node scripts/stall-profile-summary.mjs [file]`; never
  attach a profiler to production first.
  Windows rotate make-before-break (the next titled profile starts before the old one ends,
  `src/core/stall-recorder-profiler.ts`). Do not turn that back into `Profiler.stop` +
  `Profiler.start`: a start with no profile running walks the whole heap, measured 0.4 to 2 s
  on a 430 MB heap at load 300, which would make the recorder freeze the loop every window.
- **Memory pressure sheds background memory** (`src/core/memory-pressure.ts`): the kernel level
  (macOS `kern.memorystatus_vm_pressure_level`, Linux PSI) is read every 30 s off the loop, with
  two earlier signals that count as warn because the kernel level comes late
  (`src/core/memory-signals.ts`): the swap-out rate over 2 minutes and the compressor's share of
  RAM, each with an enter mark and a lower exit mark. At warn or critical the server releases the
  embedding model (about 2.2 GB of footprint per worker lane on the default model), holds the
  vector backfill and the changes prewarmer, and cuts the parsed history cache to a quarter,
  until every signal has read normal for the hold. The hold is 10 minutes, and doubles (20, 40,
  60) each time pressure comes back within 10 minutes of a clear, since reloading the model can be
  what brings it back; a later return starts again at 10. Log lines: `memory
  pressure: shedding background memory` and `memory pressure cleared`, both with `cause`
  (`kernel`, `swapout`, `compressor`, `forced`), `holdMs`, `compressorPct` and `swapoutMbPerMin`;
  stall flight records carry the same fields under `memoryPressure`.
  `WALNUT_MEMORY_PRESSURE_SHED=0` never sheds; `WALNUT_MEMORY_PRESSURE_EARLY=0` listens to the
  kernel alone; `WALNUT_MEMORY_PRESSURE_BACKOFF=0` keeps the hold at 10 minutes;
  `WALNUT_MEMORY_PRESSURE=warn` forces the level.