Skip to content

Escalation and Standing Authority (the supervised OODA loop)

What happens to a high-risk decision after the safety check pauses it for a human - and nobody answers. This doc specifies the time-bounded escalation ladder that walks an unanswered approval up the on-call chain by impact, and the standing authorization artifact that lets an operator pre-commit a bounded, conditional execution for the case where waiting is more dangerous than acting. Both are framed as a supervised OODA loop layered on the existing single-pass control loop.

Scope reminder. Customer-agnostic. Every rung name, group, threshold, and channel id below is an upstream default; a fork tunes them via config and catalog-as-code (generic-scope.instructions.md).

Safety focus. Nothing here weakens fail toward safety. Standing authorization is a human approval given in advance, bounded by an envelope and re-verified deterministically at execution time - it is never a fail-open path and never lets an LLM grant execution. Every new capability in this doc ships observation-first (architecture.instructions.md § Safety Invariants).

AreaStateEvidenceNotes
Durable observation mode escalation supervisorimplementedescalation_supervisor.py, test_escalation_supervisor.pyBounded scans, delivery claims, and would-escalate observations are implemented without advancing approval or execution authority.
human approval resume and delegated-rung verificationimplementedcoordinator.py, test_delegation.pyResume snapshots and rung eligibility are verified before the typed path continues.
Escalation ladder and urgency catalogsin-progressescalation_ladder.py, test_escalation_ladder_catalog.py, rule-catalog/escalation-ladders/Reviewed ladder and urgency-policy instances ship with a fail-closed loader and pure schedule functions, and focused checks cover expiry, fallback delivery, starvation prevention, and deterministic replay. The supervisor does not yet read the catalog, and no measured urgency-compression evidence from a running cohort exists.
A3-E standing human authorizationin-progressstanding-authorization.json, record.py, evaluator.py, test_evaluator.pyThe catalog schema, the typed record, and the deterministic evaluator exist and reject every constitutional condition with an exact reason code. The evaluator is unwired by design and a focused test fails if a decision path imports it; mode accepts only shadow, so no promotion path exists. Silence never grants authority.
DateStateChangeEvidenceRemaining
2026-08-19in-progressMade the “every constitutional condition has a failing case” claim self-enforcing instead of hand-kept. The suite now reads every reason code the evaluator source can return through an AST scan and fails when one has no case. It immediately found one: action_type_outside_envelope was unreachable in the existing table because the pin check runs first, so an action type the envelope never allowed but a pin does allow had no test. That case now exists. This is the first artifact a promotion review would need; it is an offline decision cohort over synthetic delegations, not runtime observation mode evidence.current change; tests/core/standing_authority passed 40 focused cases; the exhaustiveness test is self-verifying - it failed with reason codes with no case: ['action_type_outside_envelope'] before the missing case was added; task-scoped Ruff and format passed.A governed runtime observation mode cohort with zero envelope escapes, an independent promotion review, and a runtime revocation store all remain absent, so no decision path may consult the evaluator.
2026-08-14in-progressAdopted the implementation ledger without reconstructing earlier provenance and corrected the prior broad status summary.current change; current source, focused tests, and constitutional traceability listed in the scope table.Deliver catalog-backed urgency and standing-authorization evaluation, then retain governed observation mode evidence.
2026-08-14in-progressShipped reviewed escalation-ladder and urgency-policy catalog instances with a fail-closed loader, deterministic first-match selection, and pure schedule functions.current change; escalation_ladder.py, test_escalation_ladder_catalog.py; focused catalog checks passed 37 cases and the whole rule-catalog suite passed 1251 cases; strict mypy passed.Bind the supervisor to the catalog and retain measured urgency-compression evidence from a governed observation mode cohort.
2026-08-18in-progressMade A3-E executable without making it usable. authority/standing-authorization.json is the catalog schema, record.py parses and canonicalizes a document and rejects anything malformed instead of defaulting, and evaluator.py answers eligibility only when every constitutional condition holds, always naming the first failure. Absent, unparsable, or naive-clock input is ineligible rather than permissive. The schema admits only mode: shadow and only resource or resource-group scope, so neither enforce nor a wider impact scope can be expressed. Nothing consumes the evaluator, and a test parses the safety check, executor, human approval-resume, and control-loop trees and fails if any of them imports it.current change; tests/core/standing_authority passed 38 focused cases covering one positive path plus a negative case per condition with its exact reason code; task-scoped Ruff, format, and strict mypy passed; the core import boundary gate passed.Retain a governed observation mode cohort with zero envelope escapes, then take an independent promotion review before any decision path may consult the evaluator. Revocation propagation and renewal-as-new-revision are represented in the schema but have no runtime store.
  • Reviewed escalation-ladder and urgency-policy catalog instances ship with a fail-closed loader, and focused checks cover expiry, fallback delivery, starvation prevention, and deterministic replay.
  • Bind the escalation supervisor to the catalog and retain measured urgency-compression evidence from a running cohort.
  • Implement the A3-E standing-authorization schema and evaluator with quorum, revocation, validity, responder confirmation, exact envelope, and no-self-approval negative tests.
  • Retain a governed observation mode cohort with zero envelope escapes before any independent promotion review; keep the supervisor observe-only until that evidence exists.

The control loop in architecture.instructions.md § Control Loop is single-pass: an event is normalized, routed, decided, acted on, and audited to a terminal state. That is correct for a discrete event. It does not model a decision that stays pending while the world changes around it:

  • A hil decision fires an approval request with a TTL. Today, TTL expiry is a no-op + audit + A2 alert (channels-and-notifications.md § on-call, escalation, timeouts). Fail-closed, correct - but it stops there.
  • Channel fallback already exists: a failed Teams approval falls to another A1-capable channel, then pages the ops lane (channels-and-notifications.md). That handles delivery failure, not human non-response - a different problem.
  • A forecast detected issue carries a shrinking lead time (`actual_breach_time

The gap is a temporal supervisor: something that re-enters the loop on a timer for as long as a decision is pending, re-reads the situation (approver still silent? ETA closer? impact scope of inaction grown?), and escalates or, if pre-authorized, acts. That is an OODA loop.

The existing pantheon control loop already maps cleanly onto OODA (see the mapping in architecture.instructions.md § Trust Routing). The addition here is a second, slower loop that supervises one pending decision and ticks until it reaches a terminal state.

flowchart LR
  subgraph OBSERVE["Observe (per tick)"]
    O1["approval still pending?"]
    O2["forecast ETA now?<br/>(lead time recomputed)"]
    O3["inaction impact scope?"]
  end
  subgraph ORIENT["Orient"]
    R1["recompute urgency<br/>= f(impact, ETA, rung age)"]
    R2["which ladder rung<br/>should hold this now?"]
  end
  subgraph DECIDE["Decide"]
    D1{"standing authorization<br/>matches + envelope holds<br/>+ deadline passed?"}
  end
  subgraph ACT["Act"]
    A1["escalate to next rung"]
    A2["trip standing action<br/>-> re-enter typed pipeline"]
    A3["terminal no-op<br/>(ladder exhausted)"]
  end
  OBSERVE --> ORIENT --> DECIDE
  D1 -->|no, rung TTL left| A1
  D1 -->|yes| A2
  D1 -->|no, ladder done| A3
  A1 -. next tick .-> OBSERVE
  A2 --> AUD["audit (Saga)"]
  A3 --> AUD
  • The supervisor never mutates substrate directly. Its only privileged outcome (A2) is to re-enter the typed pipeline so the action is re-judged and executed through the normal principals. A supervisor that called an executor directly would be a defect (same rule as the conversational port in architecture.instructions.md § Agent Pantheon).
  • The loop is bounded: it has a maximum number of rungs and a hard overall deadline. It cannot tick forever.

An escalation ladder is an ordered list of human-authority rungs. It is distinct from channel fallback: channel fallback answers “the message did not get delivered - try another pipe for the same person”; the ladder answers “the message was delivered but nobody with authority acted - widen who is asked, by impact.”

ConceptAnswersFails over onLives in
Channel fallbackdelivery failurechannel unreachable / send errorchannels-and-notifications.md § 6
Escalation ladderhuman non-responserung TTL elapsed with no decisionthis doc

Each rung declares: who (an Entra group, resolved outside the control plane exactly like approver groups today), a per-rung TTL, and the notification category it may use (A1 for the decision-carrying rung, A2 paging for awareness). The ladder is selected by impact tier - a resource-scoped detected issue may only ever reach the primary on-call; a subscription-adjacent impact recruits the incident commander quickly.

rule-catalog/escalation-ladders/prod-forecast-breach.yaml
# Shipped catalog-as-code artifact (shadow-first; see Rollout).
version: 1
kind: escalation_ladder
id: prod-forecast-breach
priority: 10 # unique; first-match in ascending order
select_when:
environment: prod
finding_class: forecast.breach
impact_at_least: resource_group
rungs:
- rung: on_call_primary
audience_group: aw-oncall-primary # placeholder; fork supplies real group
ttl_seconds: 300
category: hil_approval
- rung: on_call_secondary
audience_group: aw-oncall-secondary
ttl_seconds: 300
category: hil_approval
also_page: [pagerduty-primary] # A2 awareness, non-deciding
- rung: incident_commander
audience_group: aw-incident-commander
ttl_seconds: 600
category: hil_approval
also_page: [pagerduty-primary, sms-oncall]
overall_deadline_seconds: 1500 # hard cap; on expiry -> terminal no-op unless
# a standing authorization trips first

Durations are integer seconds rather than 5m strings, so a replayed schedule never depends on a duration parser. priority makes first-match selection a total order; the loader refuses a duplicate, because two ladders sharing a priority would make selection depend on directory order. The loader also refuses a ladder whose rung TTLs do not fit inside overall_deadline_seconds, so a ladder cannot name an audience the deadline silently makes unreachable, and refuses a rung that pages its own deciding audience, because paging is awareness and never approval authority.

  • No self-approval survives escalation. A later rung is a different principal; the approver-of-record is whoever actually decides, and the executor is still a separate principal (Var approves, Thor executes - agent-pantheon.md).
  • Every rung transition is audited and, when the fingerprint repeats, feeds the existing HandoffEscalation -> GitHub issue path so chronic non-response becomes a tracked signal, not a silent loss (agent-pantheon.md § 6.4 Handoff escalation protocol).

The ladder above uses fixed TTLs for clarity, but urgency is not fixed when a breach is forecast. The supervisor recomputes, each tick, an urgency signal and uses it to compress rung TTLs and to raise the starting rung:

  • Inputs (all already produced upstream, no new collection): impact / impact scope from the safety check, breach ETA from the forecaster (observability-and-detection.md § 3), and rung age (how long the current rung has been silent).
  • Rule of thumb: effective_ttl = min(rung.ttl, k * remaining_lead_time). As the forecast ETA closes, the window each human gets shrinks, and the loop climbs the ladder faster - it never lengthens a TTL past the declared value.
  • Confidence still gates. A forecast only drives urgency when its prediction-interval band clears the configured confidence level (observability-and-detection.md § 3); a noisy point-estimate breach does not get to compress deadlines.
  • A floor prevents starvation. Compression is clamped to [min_effective_ttl_seconds, rung.ttl_seconds], so an imminent breach cannot shrink a rung to a window no human could answer in.
rule-catalog/escalation-ladders/urgency.default.yaml
version: 1
kind: urgency_policy
id: default-forecast-urgency
lead_time_factor: 0.5 # the k above
min_forecast_confidence: 0.9 # below this, nothing compresses
min_effective_ttl_seconds: 60 # starvation floor

The schedule is computed by a pure function that takes the remaining lead time and the forecast confidence as arguments rather than reading a clock, so a recorded escalation replays to the same timeline. An absent policy, an absent forecast, or a forecast below min_forecast_confidence all leave every rung at its declared TTL: an unproven urgency signal never shortens a human’s window.

Urgency changes how fast the ladder is walked; it never changes whether an unattended approved execution is allowed. That gate is standing authorization.

Standing authorization (pre-authorized conditional execution)

Section titled “Standing authorization (pre-authorized conditional execution)”

This is the mechanism behind “the operator configured an automatic action in advance.” A standing authorization is an operator-authored, policy-as-code artifact that says:

Under condition C, for actions inside envelope E, if the escalation ladder reaches its deadline unanswered, the pre-recorded human Approval may satisfy the action’s hil requirement - and only then.

The crucial design property: a standing authorization is not a new decision engine and not a bypass. It is a deterministic input to the existing risk gate. When the supervisor’s Decide step asks “can this proceed unattended?”, the safety check answers by checking a standing authorization the same way it checks any other rule - and execution eligibility is still granted by that deterministic verification, never by a model (architecture.instructions.md § LLM Quality Gate).

# Proposed catalog-as-code artifact (shadow-first; see Rollout).
# rule-catalog/standing-authority/<name>.yaml
version: 1
id: sa-scale-out-before-quota-breach
authorization_revision: <content-digest>
requested_by: <normalized-human-principal>
approved_by: # distinct normalized human principals; min 2
- <accountable-service-owner>
- <owner-level-approver>
quorum_required: 2
valid_from: <rfc3339-timestamp>
valid_until: <rfc3339-timestamp> # expires unless renewed by the accountable owner
status: active # active | revoked | expired | superseded
revocation_ref: null
service_ref: <service-id>
target_revision: <inventory-and-operating-model-revision>
policy_digest: <risk-and-approval-policy-digest>
action_type_versions: [remediate.scale-out.compute@<version>]
incident_classes: [forecast.breach]
responders:
primary: <on-call-primary>
backup: <on-call-backup>
resolved_at: <rfc3339-timestamp>
evidence:
history_review_ref: <governed-evidence-ref>
scenario_evidence_ref: <dr-chaos-or-simulation-ref>
handover_confirmation_ref: <current-owner-confirmation-ref>
scope: # MUST be resource-group-equivalent or narrower
environment: prod # (same bound as a human override)
resource_group: <rg-name> # placeholder; fork supplies real scope
precondition: # all must hold, deterministically checked
finding_class: forecast.breach
min_forecast_confidence: 0.90
min_lead_time: 3m # do not act on a breach already upon us
envelope: # the action MUST fall entirely inside this
action_types: [remediate.scale-out.compute]
max_blast_radius: resource_group
max_duration_seconds: <bounded-duration>
reversible: true # only reversible actions may be pre-authorized
rollback_contract: scripted # a tested undo path is mandatory
trigger:
after: ladder_unanswered # only after the ladder deadline, never before
mode: shadow # judge-and-log until explicitly promoted

What makes this safe (the non-negotiables):

  • Bounded like a human override. Scope MUST be resource-group-equivalent or narrower - the same ceiling the human-override mechanism enforces (architecture.instructions.md § Human Override). There is no subscription-wide standing authorization.
  • Non-destructive and reversible only. A destructive or irreversible: true action can never be pre-authorized; it always routes human approval+quorum (coding-conventions.instructions.md § Safety). A standing authorization requires a declared, tested rollback_contract.
  • Ladder-first, never ladder-instead. The trigger is after: ladder_unanswered. Channel fallback must first confirm delivery; an unreachable person is not recorded as silent. A standing authorization can only fire once real humans were asked and the deadline passed - it shortens the tail, it does not replace the human.
  • Distinct human quorum is the approver-of-record. At least two normalized, distinct human principals approve: the accountable service owner and an Owner-level authority. The requester and executor are ineligible. Var carries their signed revision as the standing Approval, so approve-vs-execute separation holds with no model-as-approver.
  • Operational evidence is current. The owner reviews applicable service logs, incidents, and audit history and records whether a precedent exists. When no adequate precedent exists, a current DR drill, bounded Chaos experiment, or simulation supplies scenario evidence.
  • Ownership handover suspends until reconfirmed. Every ownership handover requires the new accountable owner to confirm the service, responders, envelope, evidence, and expiry. Missing, stale, or declined confirmation makes the authorization ineligible.
  • Validity and revocation are monotonic. valid_from <= now < valid_until and status=active are required. Revocation is immediate and blocks pending re-decisions. Renewal creates a new immutable revision with fresh quorum, evidence, and responder confirmation; it never extends the old record in place.
  • Execution fits the validity window. The safety check requires now + max_duration_seconds <= valid_until before dispatch. It uses trusted UTC for persisted instants and monotonic elapsed time for the running deadline. Clock unavailability or excessive skew makes the authorization ineligible.
  • Responders are current. Eligibility requires a time-aware OnCallSchedule receipt or explicit primary and backup identities resolved with an expiry no later than valid_until.
  • Version-bound and revocable. The authorization pins its revision, policy digest, target revision, ActionType and workflow versions, and evidence revisions. Any mismatch, revocation, policy change, target drift, or catalog change requires independent re-approval.
  • Chaos injection is excluded. A standing authorization never approves fault injection. A separately human-approved experiment may pre-authorize only its bounded stop and recovery path.
  • All seven autonomous-action safeguards still apply (architecture.instructions.md § Seven Autonomous-Action Safeguards).
  • Prefer safe-degradation over the risky action. When possible, the pre-authorized action is a reversible mitigation (scale out, open a circuit breaker, extend a quota) that buys time, not the destructive fix itself. Buying time re-arms the human loop rather than ending it.

When a standing authorization trips, the supervisor does not execute. It re-injects the pending action into the typed pipeline as a fresh decision:

flowchart LR
  SUP["escalation supervisor<br/>(ladder deadline + SA match)"] -->|re-enter| RG["safety check<br/>re-evaluates"]
  RG -->|"SA precondition + envelope verified"| V["Var<br/>standing Approval"]
  V --> EX["Thor<br/>executes approved human approval action"]
  EX --> DEL["delivery<br/>fix-PR / direct-api"]
  DEL --> AUD["audit (Saga)<br/>reason: standing-authority sa-...id"]
  RG -->|"SA invalid / envelope exceeded"| NO["terminal no-op<br/>+ A2 alert"]
  NO --> AUD
  • Forseti re-judges without raising risk. The original hil baseline remains. The safety check verifies a valid, unexpired, scope-matching standing authorization whose precondition and envelope still hold; Var materializes its pre-recorded human Approval. Judge, approver, and executor remain distinct.
  • Thor executes, Vidar remains the rollback principal, Saga audits with an explicit standing-authority reason and the authorization id - a replayable, attributable record (architecture.instructions.md § Idempotency, Ordering, and Replay).
  • Envelope violation fails closed. If the pending action does not fit the envelope (wrong action type, impact scope grew, inventory went stale), the standing authorization does not apply and the loop terminates as a no-op.

The pantheon is fork-locked - no agent is added, removed, or renamed (agent-pantheon.instructions.md). The supervised loop is expressed with existing agents and their existing topics:

OODA stepAgent(s)Existing responsibility used
ObserveHeimdall, Huginnre-read forecast detected issue + pending-approval state (sensing, deterministic-first)
OrientOdinimpact arbitration; which rung and urgency hold now
DecideForseti (+ safety check)re-judge without raising the original hil baseline
Act (escalate)Varcarry the A1 request to the next rung; approver-of-record
Act (execute)Var, ThorVar supplies standing Approval; Thor remains sole executor
RecoveryVidarrollback path for the executed mitigation
Audit / handoffSagaappend audit + HandoffEscalation on chronic non-response

The supervisor itself is a lifecycle behavior of the pending decision, not a sixteenth agent: it is the timer-driven re-entry of the same typed pipeline, owned by the approval lifecycle (Var) and arbitrated by Odin.

Every path ends in an audited terminal state - the loop cannot leak:

TerminalWhenResult
approvedany rung decides approveexecute via Thor, audit
rejectedany rung decides rejectno-op, audit
standing-authority executedladder deadline passed, SA valid, envelope holdsre-decide -> standing Approval -> execute, audit with SA id
terminal no-opladder exhausted, no valid SAno action, A2 alert, audit, HandoffEscalation if fingerprint repeats

Fail-closed remains the default. Absent a valid standing authorization, an unanswered ladder still ends in no-op - exactly today’s behavior, just after a wider, impact-tiered, time-decaying set of humans were given the chance to act.

  1. Ladder in observation mode. Ship the escalation ladder judging-and-logging only: it records which rung it would have escalated to and when, mutating nothing. Promote per-ladder once the escalation timing is validated against real non-response incidents.
  2. Standing authorization in observation mode. Every standing authorization declares mode: shadow and a measurable promotion gate (e.g. “N observation mode trips, zero envelope escapes, zero policy-violation escapes”). Promotion to enforcement mode is a separate, Owner-reviewed change, never bundled with the authoring PR (coding-conventions.instructions.md § Safety).
  3. Metrics (fold into the existing KPI stream, goals-and-metrics.md): rung-response latency, escalation depth distribution, ladder-exhaustion (no-op) rate, standing- authority trip rate, and - the guard metric - envelope-escape count, which must stay zero.
  • Rung membership source. Reuse the Entra-group binding used for approver groups, or introduce an on-call schedule integration (PagerDuty/Opsgenie schedule read) so “who is primary” is time-aware? Leaning group-first for the upstream, schedule integration as a fork seam.
  • Urgency function shape. The k * remaining_lead_time compression is a starting heuristic; the exact curve is a tuning parameter to backtest against historical forecast-to-breach series before enforce.
To learn aboutRead
The single-pass control loop this supervisesarchitecture.instructions.md § Control Loop
How an action is classified auto / human approval / denyrisk-classification.md
Forecast lead time and the prediction-interval bandobservability-and-detection.md § 3
Channel fallback vs this human-authority ladderchannels-and-notifications.md
Which agent escalates, judges, and executesagent-pantheon.md
The bounded human-override mechanism this mirrorsarchitecture.instructions.md § Human Override