Live config loaded — values shown in teal reflect what is currently deployed on this server.
AGS Documentation
Back to Dashboard

Governance System src/pipeline/governance.py

The governance system takes the readiness score and signal context from the previous two stages and answers one question: what should AGS do right now? It evaluates 17 conditions in strict, documented order. The first condition that matches determines the outcome — nothing else runs.

Why a rule list rather than a model or threshold? A simple threshold ("intervene if score < 40") would be easy to implement but hard to audit and impossible to reason about after the fact. A machine learning model would be even harder to explain to an ethics board or publish. The rule list is auditable, reproducible, and explainable — every decision traces to a named rule with a logged reason code. When a governance decision looks wrong in the data, you can find the exact rule that fired and why.

The decision flow

Before any rules are evaluated, four gates run in sequence. Each gate can exit the evaluation early.

Gate 0
condition = control?
WITHHOLD_ALWAYS → stop
Gate 0b
calibration cleared?
exit — no decision yet
Gate 0c
cooldown active?
exit — suppressed
Gate 0d
conflict detected?
Apply score overrides → continue
Confidence gate
signal confidence low?
WITHHOLD (confidence_gate)
R1 – R11 + captcha + FR49
first match wins

The gates explained

Gate 0 — study condition check. If the session's condition is control, governance returns WITHHOLD_ALWAYS immediately, before any computation. The readiness scores are not even calculated. This is intentional — the control condition logs signals only, and we don't want the computational overhead of running the full pipeline for participants who will never receive an adaptation.

Gate 0b — calibration check. No governance decision fires until the participant has completed 3 baseline interactions. Without a personal baseline, we have no reference point for what "slow" or "many errors" means for this individual. A governance decision made against an uncalibrated participant would be comparing them to population averages rather than their own baseline, which is statistically unsound.

Gate 0c — cooldown check. If a background suppression timer is active in Redis (set by a prior governance decision), the system exits without evaluating any rules. This prevents consecutive interventions. The cooldown duration depends on which state triggered it: HIGH state = 15s, MODERATE = 20s, LOW = 30s, OVERLOAD = 45s, worsened outcome = 60s.

Gate 0d — conflict detection. If a conflict pattern was detected (fast-but-wrong, slow-but-correct, etc.), score overrides are applied before proceeding. The governance system then continues with adjusted scores. This is the only gate that doesn't stop evaluation — it modifies state and passes through.

Confidence gate. If detection_confidence is below the configured threshold, the system withholds with reason confidence_gate. Low confidence usually means the event arrived with very few signals (very short step, single click), which makes the readiness estimate unreliable. Better to withhold than to fire a wrong intervention.

The rules

RuleFires whenDecisionReasoning
R12 explicit_help = true (participant pressed Help) INTERVENE Level ≥ 2 Checked first among the numbered rules — before any readiness comparison. A participant who asks for help gets meaningful help, always. A tooltip (L1) is not acceptable on an explicit request. This is a Hard Rule.
captcha step_type = captcha INTERVENE L2b (proactive) CAPTCHAs are reliably difficult for this population. Rather than waiting for struggle signals that are hard to interpret inside a CAPTCHA (there are no meaningful wrong-click signals), AGS delivers help proactively at step entry. This is the only forward-looking rule.
R9 In post-intervention window, outcome improved WITHHOLD The last adaptation worked. The participant is recovering. Don't interrupt a recovery with another intervention. Halves fatigue and frustration scores in Redis as a side effect.
R11 In post-intervention window, outcome worsened or dismissed WITHHOLD_TEMP (60s cooldown) The last adaptation either made things worse or was ignored. Either the content was wrong for this context, or the participant is past the point where an overlay helps. Back off. Log an ADAPTATION_FAILURE row.
R10 In post-intervention window, no change in R10 window REASSESS (or WITHHOLD if L3 budget exhausted) The intervention had no measurable effect. The participant may need a different level or different content. REASSESS triggers content re-evaluation without resetting cooldowns. If L3 has already been delivered ≥ 2 times on this step without the participant completing it, POST_STALLED withholds further L3 repetition instead — the same walkthrough text looping is counter-productive. MODERATE_STALL_HANDOFF then handles eventual escalation.
FR49 abandonment_risk_score ≥ threshold INTERVENE L2b (reassurance) Abandonment risk is a composite enrichment signal — when it crosses the threshold, the participant is at elevated risk of giving up in the next few events. AGS uses a short reassurance message at this point because the participant appears to need confidence support, not another procedural instruction. Fires before the readiness-based rules to catch high-risk participants who might still be in MODERATE state.
R0_PREDICTIVE readiness_trajectory = rapid-declining, readiness still above LOW INTERVENE L2a Intervenes before the score actually drops to LOW, when the trajectory shows it's heading there fast. Catches participants in a fast deterioration before they reach OVERLOAD.
MODERATE_STALL_HANDOFF L3 delivered ≥ 2× on this step without completion AND time on step ≥ 360s AND ≥ 2 prior interventions INTERVENE L4 (human handoff) "Accepts help but keeps failing" for 6+ minutes is a handoff situation regardless of the exact readiness score. HANDOFF_THRESHOLD requires OVERLOAD state; this rule covers the gap where a participant remains at MODERATE but is demonstrably unable to progress despite repeated L3 walkthrough content. worsened_outcome and passive_help_only are intentionally not checked — a suppressed participant who has been stuck for 6 minutes needs a human, not continued silence. Fires before DISMISS_LIMIT so it isn't blocked by the passive-only gate.
R2 HIGH readiness, step completed cleanly WITHHOLD The participant succeeded without help. Preserve autonomy. Don't erode confidence by offering help they didn't need. Bypassed in always_adaptive condition.
R3 MODERATE, no struggle signals DELAY Readiness has dropped into moderate range but no concrete struggle is visible. Watch, don't act. A delay records the concern without interrupting the participant. Bypassed in always_adaptive.
R4 MODERATE, help requested or pausing too long INTERVENE L1 (tooltip) MODERATE state with an observable struggle signal — a help request or a pause that exceeds the step's expected duration. A light tooltip is appropriate: something to nudge, not overwhelm. Bypassed in always_adaptive.
R5 LOW, long time since last intervention INTERVENE L2a (guided prompt) The participant is clearly struggling (LOW state) and enough time has passed since the last attempt to help. Escalate to a guided prompt. Bypassed in always_adaptive.
R6 LOW, recent intervention still in window DELAY We already tried to help and we're still in the window where the participant might be acting on it. Don't pile on. Wait for the outcome. Bypassed in always_adaptive.
REST Fatigue signals above threshold, long idle INTERVENE L2b (rest prompt) Fatigue is distinct from confusion. A participant who is tired needs to stop, not more information. The rest prompt is a dedicated message encouraging a brief break. Uses step complexity fallback (75s threshold) when step_complexity is null in the template.
R7 OVERLOAD, manageable fatigue and error control INTERVENE L3 (walkthrough) OVERLOAD state with signals suggesting the participant could still benefit from targeted guidance. A walkthrough is the most intensive intervention — one to two LLM-generated sentences tuned to the participant's current state and step context. Reserved for OVERLOAD because it's disruptive if delivered prematurely.
R8 OVERLOAD, high fatigue or worsened outcome history WITHHOLD_TEMP OVERLOAD with fatigue so high that more content would make things worse. The participant needs to stop. Withhold everything and wait for the fatigue score to decay (or for them to explicitly ask for help).

What always_adaptive bypasses — and why

The always_adaptive condition bypasses R2, R3, R4, R5, R6, R9, and R10. These are all the rules that withhold or delay intervention — the rules that preserve autonomy. In always_adaptive, AGS fires help whenever it can, without waiting for multiple struggle signals or respecting the "they're recovering" window.

This condition exists to answer a specific research question: does more intervention mean better outcomes, or does appropriate intervention? By comparing governed_adaptive (careful, rule-gated) with always_adaptive (liberal, always fires) and control (never fires), the study can isolate the effect of governance quality from simple intervention quantity.

What every decision logs

Every governance decision — including WITHHOLD and DELAY — writes a row with:

reason_code rule_triggered rules_evaluated JSONB readiness_state overall_readiness cooldown_duration

The rules_evaluated JSONB array lists every rule that was checked before the match, in order. This is what allows post-hoc reconstruction of any governance decision: you can see not just what fired but everything that didn't fire and why.