Skip to content

Commit b97d8c5

Browse files
feat: behavioral drift detection (agent-strace drift) (#73)
Adds agent-strace drift — detects when agent behavior shifts across sessions using Jensen-Shannon divergence across six dimensions: tool mix, error rate, retry pattern, blast radius, session duration, and decision depth. No LLM required. All analysis is structural. Behavioral fingerprints are compact JSON files (<2KB) that can be committed as baselines. - drift.py: BehavioralFingerprint, DriftReport, compute_fingerprint(), compute_drift(), print_report(), cmd_drift() - cli.py: drift subcommand wired in with --since, --baseline, --current, --save-baseline, --threshold, --format flags - 27 new tests covering statistical helpers, fingerprint computation, drift scoring, serialization, and end-to-end store integration Closes #70 Co-authored-by: Ona <no-reply@ona.com>
1 parent 4060f22 commit b97d8c5

3 files changed

Lines changed: 870 additions & 0 deletions

File tree

src/agent_trace/cli.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .http_proxy import HTTPProxyServer
2525
from .a2a import cmd_a2a_tree
2626
from .annotate import cmd_annotate
27+
from .drift import cmd_drift
2728
from .oncall import cmd_oncall
2829
from .freshness import cmd_freshness
2930
from .standup import cmd_standup
@@ -654,6 +655,22 @@ def build_parser() -> argparse.ArgumentParser:
654655
p_fresh.add_argument("--scope", default="", help="file glob to limit scope")
655656
p_fresh.add_argument("--repo", default=".", help="path to git repository (default: .)")
656657

658+
# drift (behavioral drift detection)
659+
p_drift = sub.add_parser("drift", help="detect behavioral drift across sessions")
660+
p_drift.add_argument("--since", metavar="Nd", help="analyse sessions from the last N days (e.g. 30d)")
661+
p_drift.add_argument("--baseline", metavar="FILE",
662+
help="path to a saved behavioral fingerprint JSON, or a date range YYYY-MM-DD:YYYY-MM-DD")
663+
p_drift.add_argument("--current", metavar="RANGE",
664+
help="current window as YYYY-MM-DD:YYYY-MM-DD")
665+
p_drift.add_argument("--baseline-range", metavar="RANGE", dest="baseline_range",
666+
help="baseline window as YYYY-MM-DD:YYYY-MM-DD")
667+
p_drift.add_argument("--save-baseline", metavar="FILE", dest="save_baseline",
668+
help="save current fingerprint to FILE and exit")
669+
p_drift.add_argument("--threshold", type=float, default=0.20,
670+
help="drift score above which to alert (default: 0.20)")
671+
p_drift.add_argument("--format", choices=["table", "json"], default="table",
672+
help="output format (default: table)")
673+
657674
# standup (agent standup report)
658675
p_standup = sub.add_parser("standup", help="plain-English summary of what the agent did")
659676
p_standup.add_argument("session_id", nargs="?", help="session ID or prefix (default: latest)")
@@ -714,6 +731,7 @@ def main() -> None:
714731
"curve": cmd_curve,
715732
"inflation": cmd_inflation,
716733
"a2a-tree": cmd_a2a_tree,
734+
"drift": cmd_drift,
717735
"oncall": cmd_oncall,
718736
"freshness": cmd_freshness,
719737
"standup": cmd_standup,

0 commit comments

Comments
 (0)