Skip to content

feat(usage): opt-in local observability — usage events, health surfacing, effectiveness measurement - #482

Merged
HumanBean17 merged 19 commits into
masterfrom
feat/local-observability
Sep 13, 2026
Merged

HumanBean17 merged 19 commits into
masterfrom
feat/local-observability

Conversation

@HumanBean17

Copy link
Copy Markdown
Owner

What

Opt-in, strictly-local observability for jrag so the owner can answer three questions that were previously unanswerable: how is jrag being used (by autonomous agents), was it useful, and were there problems.

One switch gates the whole layer — usage.enabled (default off; env JAVA_CODEBASE_RAG_USAGE_ENABLED or YAML usage.enabled). Disabled, jrag is byte-identical to today: no files, no extra state, no event_id on envelopes.

  • Spec: docs/superpowers/specs/active/2026-09-13-local-observability-design.md · Plan: docs/superpowers/plans/active/2026-09-13-local-observability.md

The pieces

  • New stdlib-only usage/ package (paths, events, writer, summarize): versioned surface-neutral event schema (v1), day-sharded capped JSONL in a durable per-project state dir (never the index dir — jrag erase can't wipe history), single-os.write O_APPEND appends (synchronous: both main() paths os._exit() past buffered flushes), 30-day retention, 5 MiB/day cap with drop accounting, swallow-everything guard — telemetry never changes stdout/stderr/exit codes.
  • Taps: agent-CLI main() funnel (per-invocation event: verb, capped query, duration, rc, envelope facts, index_age_s, served_by hot/cold, ppid+cwd for sessions; error + usage-error paths record too), operator-CLI mirror, watch-daemon _record (reindex lifecycle with stderr tails, consecutive_errors / per-phase last-success / heartbeat / live queries_served).
  • jrag usage [--days N] — summary rollup: per-verb calls/outcome mix/latency split hot-vs-cold, miss rate binned by index age, sessions, struggle signals (repeats, reformulations, abandoned), absence-term mining, watch health, feedback labels, storage status. Zero-states when disabled/empty (rc 0, prime-style never-nag).
  • jrag feedback <event_id> --good|--bad [--note] — sparse owner labels anchored to the 10-char event_id envelopes carry while telemetry is on; joined in jrag usage with orphan reporting.
  • Health surfacing (gated): jrag status daemon-health node + warnings (dead / failing ≥3 / heartbeat >120s), jrag prime "running, reindex failing since X". Ungated: jrag watch --status renders the already-recorded last_error (previously persisted but never shown anywhere).
  • eval --reuse-index: measure the live index without a rebuild (cron-friendly nightly recall/MRR); snapshots graph.meta() into report.json so index health and recall are jointly analyzable.
  • Local-only enforced, not promised: import-lint test pins usage/ to stdlib non-network roots; "What jrag records locally" table in docs/CONFIGURATION.md §6; identifiers and outcomes only — never file contents.

Design decisions worth reviewing

  • JSONL over SQLite (concurrent one-shot writers, torn line = one lost event, no query surface beyond one aggregator; llm CLI's unbounded SQLite log is the cautionary tale).
  • Opt-in default-off per owner decision; feedback rides the same switch; watch --status last_error and eval mode stay ungated (display of existing state / explicit operator action).
  • Line cap 1 KiB (not 512 B): measured real events exceed 512; O_APPEND atomicity on regular files doesn't depend on PIPE_BUF.

Tests

  • 60+ new tests in tests/usage/ (paths, schema, writer caps/retention/swallow, pure aggregation, taps incl. error paths, verbs, gating, health renders, import lint) + tests/eval/test_reuse_index.py.
  • Full suite: 2195 passed, 8 failed — all 7 failures match the master baseline on this machine (6 publish-skill runbook + 1 bench judge needing a judge subprocess), plus test_detach_stale_socket_cleaned_before_bind, an intermittent load-timing flake of a real-subprocess readiness wait: passes standalone 4/4 and in 3/3 watch-suite runs; the telemetry-off daemon path is unchanged code.
  • Usage tests pin INDEX_DIR/SOURCE_ROOT per-test (tests/usage/conftest.py) — the full-suite order otherwise inherits an os.environ write run_eval never restores.
  • i18n: all new strings in en + ru catalogs.

Notes

  • Branch rebased onto current origin/master (ced115f, fix(cli): clean usage-error envelope; flow/outline accept FQN roots (#473 #474 #475) #481) — the usage-error envelope refactor and this PR's tap touch adjacent code in jrag.py::main; rebased cleanly, overlap zone re-verified.
  • Local master has diverged from origin/master by one local commit (091cfbd, the .agents/worktrees/ gitignore chore) vs origin's ced115f — left unpushed for the owner to reconcile.

…by, index age

Line cap 512B→1KiB: measured real events (cwd+facts+query) exceed 512;
O_APPEND atomicity on regular files does not depend on PIPE_BUF.
@HumanBean17
HumanBean17 merged commit 6163048 into master Sep 13, 2026
5 checks passed
@HumanBean17
HumanBean17 deleted the feat/local-observability branch September 13, 2026 16:01
@HumanBean17

Copy link
Copy Markdown
Owner Author

Code review response

Four parallel reviewers (usage package core / CLI taps+verbs / watch daemon / config+eval+docs) reviewed ced115f..b230fda. Verdicts: all "with fixes". Every Critical and Important finding is fixed in the latest commit; the substantive minors are fixed too.

Critical (fixed)

  • Double config resolution duplicated YAML degradation warnings on stderr — a reproducible byte-diff with telemetry off. Telemetry-only resolves are now stderr-silenced (redirect_stderr), restoring the byte-identical-when-disabled contract.

Important (all fixed)

  • Char-based 500-trim vs byte-based 1 KiB line cap: non-ASCII stderr (e.g. Cyrillic tracebacks) escaped to \uXXXX silently dropped the entire error event. Now: daemon trims to a ≤400-byte budget, and the writer sheds detail.stderr_tail as a rung before ever dropping an event. Regression-tested with Cyrillic tails.
  • Watcher wrote stderr_tail into the daemon state file with telemetry off — a disabled-mode schema change. Now gated; disabled detail stays {phase, returncode} (pinned by test).
  • Day-shard naming/retention used local time while ts is UTC (±14 h skew possible) — now UTC, settled before any real files exist under the naming scheme.
  • _emit telemetry stash and _debug_drop were unguarded holes in the never-raise contract — both wrapped.
  • watch_health was missing the plan-promised last_error passthrough and duration stats — now includes both (durations from started→terminal ts deltas, p50/max).
  • eval: an unreadable GraphMeta was silently swallowed — now recorded as {"error": ...} (that corruption signal is the point of the snapshot), plus a reuse_index mode marker on EvalReport so graph_meta: null is unambiguous.
  • Tests: fixed-ts time bomb (would fail 7 days after commit) replaced with relative timestamps; --days window filtering + clamping now covered; the heartbeat method is now real code (_maybe_heartbeat_locked) driving a real test instead of a reimplementation.

Minors fixed: math.ceil percentile edge; absent durations excluded rather than coerced to 0; orphan labels without ids filtered; import lint is recursive + flags relative imports + ast.AST typo; the pure-argparse eval flag test no longer skips on stack-less venvs; feedback help names --format json as the event_id source (prime template synced); trailing-dash render wart; relative XDG_STATE_HOME ignored per spec; best-effort counter semantics documented at the code.

Acknowledged, not changed (noted for the record): ambiguous counts as a miss in staleness/absence/struggle metrics (documented in _is_miss — defensible and now explicit); readers are line-buffered rather than strictly streaming (bounded by caps; acceptable at 30-day scale); two minor exit paths (bare jrag, operator argparse-error) remain uninstrumented — follow-up material, not this PR.

Full suite after fixes: 1 failure — test_judge_answer_returns_grade, which fails identically on master (needs the bench judge subprocess); 2218 passed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant