Skip to content

feat(cli): driftbase trend — continuous per-metric OLS slope to complement epoch detection #1

Description

@nanookclaw

Summary

driftbase history detects behavioral epochs using Jensen-Shannon divergence on behavioral distributions — excellent for catching discrete shifts. The gap is the continuous slope layer: numeric metrics like error_count, latency_ms, and retry_count accumulate in agent_runs_local on every run, but there is no command that computes OLS regression across the ordered run sequence.

These are complementary, not competing:

  • Epoch detection (JSD on outcome/tool distributions) → catches discrete behavioral shifts
  • OLS slope (linear regression on numeric metrics) → catches gradual degradation that never triggers a JSD breakpoint

A 5%→8%→12%→16% error rate drift across 20 runs may never produce a JSD spike large enough to become its own epoch. It shows up as an L-H-L-H-L epoch alternation, or gets absorbed into existing epoch noise. But the slope is telling you something the epoch framing misses.

Proposed Addition: driftbase trend

Data model

@dataclass
class MetricTrend:
    metric: str          # "error_rate", "latency_ms", "retry_rate"
    slope: float         # from statistics.linear_regression (Python 3.10+)
    direction: str       # "worsening" | "improving" | "stable"
    window_runs: int     # how many runs the slope covers
    any_regression: bool

@dataclass
class RunTrendReport:
    agent_id: str
    window: int
    computed_at: str
    trends: list[MetricTrend]
    any_regression: bool    # True if any metric is worsening

Implementation sketch

from driftbase.backends.factory import get_backend
import statistics

def compute_run_trend(window: int = 20) -> RunTrendReport:
    backend = get_backend()
    runs = backend.get_runs(limit=window)  # most-recent-first
    ordered = list(reversed(runs))          # oldest-first for OLS

    n = len(ordered)
    xs = list(range(n))

    # error_rate = error_count / max(tool_call_count, 1)
    error_rates = [r["error_count"] / max(r["tool_call_count"], 1) for r in ordered]
    latencies   = [r["latency_ms"] for r in ordered]
    retry_rates = [r["retry_count"] / max(r["tool_call_count"], 1) for r in ordered]

    def _trend(values):
        slope, _ = statistics.linear_regression(xs, values)
        # error_rate and retry_rate: higher = worsening; latency: higher = worsening
        direction = "worsening" if slope > 0.001 else ("improving" if slope < -0.001 else "stable")
        return slope, direction

    trends = []
    for name, values in [("error_rate", error_rates), ("latency_ms", latencies), ("retry_rate", retry_rates)]:
        slope, direction = _trend(values)
        trends.append(MetricTrend(
            metric=name, slope=slope, direction=direction,
            window_runs=n, any_regression=(direction == "worsening")
        ))

    return RunTrendReport(
        agent_id=..., window=window, computed_at=...,
        trends=trends, any_regression=any(t.any_regression for t in trends)
    )

CLI integration

driftbase trend
driftbase trend --window 20
driftbase trend --format json
driftbase trend --exit-on-regression    # CI gate: exit 1 if any_regression

Output (text):

DRIFTBASE TREND  ·  Last 20 runs

  error_rate    slope=+0.0041  WORSENING  (5.1% → 12.3%)
  latency_ms    slope=+128     WORSENING  (1.2s → 3.8s)
  retry_rate    slope=+0.001   STABLE

  Status: REGRESSION DETECTED  (2/3 metrics worsening)

Natural integration points

  • driftbase diagnose could call this internally and note when OLS slope predates the epoch breakpoint by N runs
  • driftbase diff could annotate whether the current version's metric slope is steeper than the baseline version's
  • CI gating: driftbase trend --exit-on-regression provides a continuous slope gate alongside the existing snapshot-comparison diff gate

Why complement epoch detection

Detection type Catches Misses
JSD epoch detection Sudden distribution shifts Slow linear regressions
OLS slope Gradual per-metric drift Distribution shape changes
Both together Full behavioral slope picture

The data already exists in agent_runs_local. This is purely an analysis layer addition.


Related: PDR in Production v2.8 — §7.6.8 documents the same structural omission across 15 independent agent evaluation and observability frameworks. driftbase is distinctive because it is explicitly focused on behavioral drift — the proposed trend command fills the one complementary gap in an otherwise comprehensive tool.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions