Detection Algorithm
A technical specification of the Behavioral Signal Aggregator (BSA) powering asshole-gc. Version 1.0. Last updated 2026-03-23.
Overview
asshole-gc uses a multi-pass Behavioral Signal Aggregator (BSA) to evaluate subjects across 14 weighted dimensions. The pipeline has four stages:
- Signal collection — passive observation, no active probing
- Dimension scoring — 14 weighted behavioral axes, rolling 72-hour window
- Consecutive day counting — the core threshold mechanism
- False positive suppression — context normalization before flagging
A subject is only flagged when all four conditions are met:
threshold exceeded, confidence above minimum, false-positive score below ceiling,
and at least one manual review pass (the --dry-run step).
--dry-run and review the evidence before proceeding.
Signal Collection
Signals are collected passively across the subject's observable behavioral surface. The system does not intercept private communications or require instrumentation. Observable surface includes:
- Communication tone and word choice in shared channels
- Response patterns: frequency, latency, selectivity
- Credit attribution (or lack thereof)
- Reaction to criticism vs. reaction to praise
- Behavior under low-stakes vs. high-stakes conditions
- Treatment of people with less power than them
- Follow-through on commitments
Signals are timestamped and windowed. The rolling window is 72 hours by default,
configurable via --window. Each signal has a weight, a context tag,
and a decay function.
struct Signal {
timestamp: DateTime,
dimension: Dimension, // which of the 14 axes
weight: f32, // 0.0 – 1.0
context: Context, // WorkStress | Personal | Deadline | Normal
raw_text: Option<String>,
}
14 Behavioral Dimensions
Each dimension is scored 0.0 (clean) to 1.0 (severe). Weights reflect how reliably each dimension predicts chronic vs. situational behavior.
| # | Dimension | Weight | Description |
|---|---|---|---|
| 1 | Condescension | 0.90 | Unsolicited correction, diminishing tone, "well, actually" density |
| 2 | Bad faith | 0.90 | Misrepresenting positions, strawmanning, goal-post movement |
| 3 | Weaponized incompetence | 0.85 | Strategic inability to perform tasks to avoid responsibility |
| 4 | Credit theft | 0.85 | Claiming others' work, omitting contributors, rewriting history |
| 5 | Chronic negativity | 0.70 | Consistent pessimism, complaint without constructive follow-through |
| 6 | Selective empathy | 0.75 | Empathy for peers/superiors, contempt for subordinates or strangers |
| 7 | Commitment failure | 0.65 | Repeated failure to follow through; no-show without acknowledgment |
| 8 | Boundary violation | 0.88 | Ignoring stated limits; re-asking after clear no |
| 9 | DARVO pattern | 0.92 | Deny, Attack, Reverse Victim and Offender when confronted |
| 10 | Energy drain coefficient | 0.60 | Subjective exhaustion reported by proximate observers after interactions |
| 11 | Escalation tendency | 0.72 | Disproportionate responses; minor friction → major conflict |
| 12 | Rules asymmetry | 0.68 | Standards applied to others but not self; double standards |
| 13 | Apology quality | 0.65 | Non-apology apologies ("sorry you feel that way"); conditional remorse |
| 14 | Pattern persistence | 0.95 | Recurrence after acknowledged correction. The defining signal. |
BSA Scoring
The BSA computes a weighted aggregate across all 14 dimensions, normalized by the context weight of each signal. Context weights reduce scores during known stress periods (deadlines, personal crises) to avoid false positives from situational behavior.
fn bsa_score(signals: &[Signal]) -> f32 {
let windowed = signals.filter(|s| s.within_window(72.hours()));
let raw_score: f32 = windowed
.group_by(|s| s.dimension)
.map(|(dim, sigs)| {
let dim_score = sigs.iter()
.map(|s| s.weight * context_factor(s.context))
.sum::<f32>()
.min(1.0);
dim_score * dim.weight
})
.sum();
// Normalize to [0, 1]
raw_score / DIMENSION_WEIGHT_SUM
}
Context factors:
| Context | Factor | Notes |
|---|---|---|
| Normal | 1.00 | Full weight |
| Work deadline | 0.70 | Reduced — stress artifacts |
| Personal crisis | 0.40 | Significantly reduced |
| Bereavement | 0.10 | Almost excluded |
Consecutive Day Counter
The consecutive day counter is the core threshold mechanism. A "bad day" is
defined as any 24-hour period in which the subject's BSA score exceeds
0.55 (configurable via --day-threshold).
The counter increments on bad days and resets on genuinely good days.
A good day requires a score below 0.30 — not just
below threshold. This prevents gaming via minimal compliance.
Default threshold: 3 consecutive bad days. This was chosen because:
- 1 bad day — noise. Everyone has them.
- 2 bad days — pattern forming. Worth noting.
- 3 bad days — this is who they are right now. Time to act.
False Positive Suppression
The FP suppression layer runs after BSA scoring and before flagging. It applies three filters:
- Observer bias correction — if the observer reporting signals has a known conflict with the subject, dimension weights are reduced by 20%.
- Mirroring check — if the subject's flagged behaviors closely mirror behaviors exhibited by the reporting observer, confidence is reduced. Some people bring out the worst in each other.
- Single-source guard — signals from only one observer require a higher threshold (4 consecutive days instead of 3) before flagging.
The FP rate with --dry-run review is estimated at <2%.
Without review, it is estimated at ~8%. Always use --dry-run.
Grace Period Logic
When --grace-period is passed, the subject receives a single
clear notification before collection is executed. The grace period is 7 days
by default (--grace-days N).
During the grace period, the counter continues running. If the subject
demonstrates genuine improvement (score drops below reset threshold for
3+ consecutive days), the flag is cleared and they are moved to
Status::Monitoring instead.
Worked Examples
Example A: Dave (Engineering)
| Dimension | Score | Key signals |
|---|---|---|
| Condescension | 0.91 | 23 unsolicited corrections in 7 days |
| DARVO pattern | 0.88 | 3 incidents of reverting blame on confrontation |
| Pattern persistence | 0.95 | Same behaviors for 14+ months |
| Credit theft | 0.72 | 2 documented omissions in shared presentations |
Verdict: Flag. No false positive indicators. Grace period optional.
Example B: Casey (Slack)
| Dimension | Score | Key signals |
|---|---|---|
| Chronic negativity | 0.68 | High complaint volume this week |
| Escalation tendency | 0.42 | One overreaction in a tense meeting |
Verdict: Monitor. Context: product launch week. Score likely situational. Check in 7 days.