GitHub Agentic Workflows Now Ask a Policy Server Before the Agent Starts

·9 min read·Evergreen Tools Team

On October 27, 2026, GitHub's gh-aw team published a design note about a small gap with large consequences. A scheduled workflow is ready to run: its trigger matches, its authorization checks pass, its prompt is compiled. But should it start another agent session right now? That question can depend on more than one repository — an organization-wide budget, a workflow's priority, or a centrally managed allowance. Their answer is an activation gate, called admission in gh-aw configuration and enabled with on.admission: true. The boundary they draw is worth noticing: gh-aw implements only the workflow-side client and compiler wiring, while a separately deployed admission authority owns policy, identity verification and trusted usage accounting.

Abstract wiring and data flows on a dark background

The gate decides before inference, not during it: policy, identity and allowance all resolve upstream.

1. Where the gate sits: before inference, not during it

For an opted-in workflow with a configured endpoint, the compiler-owned admission job runs before activation. It requests a GitHub Actions OIDC identity token for the authority's audience, then posts the invocation identity and the compiled workflow hash to /api/admission. The authority verifies the identity, evaluates its policy, and returns an approval or a denial. Approval means the run may start with a specific allowance. Denial means the agent never starts. That placement is the whole point. This is not a mid-run circuit breaker; it is admission before the clock starts, so the first minutes of spend, the runaway session, and the half-finished work that you would otherwise have to reason about never happen.

# Opt in per workflow. Nothing else changes about your triggers.
on:
  workflow_dispatch:
  schedule:
    - cron: "0 6 * * *"
  admission: true          # <- the new key

permissions:
  contents: read
  # id-token: write is NOT added here. The compiler grants it to the
  # isolated admission and completion-callback jobs, not to the agent.

# Or opt in repository-wide, preserving the existing object:
# .github/workflows/aw.json
# { "admission": true, "other_setting": "..." }
# A workflow-level `admission: false` cannot override a repo opt-in.

2. Three allowance numbers, and three rules that are easy to misread

An approval carries per-component maximums: max_ai_credits for the primary agent, max_ai_credits_threat_detection, and max_ai_credits_evals. The documentation is explicit about three rules because getting them wrong is expensive. Each supplied allowance replaces that component's runtime limit, even when it is higher than the workflow's own configured limit. Omitted optional allowances leave the existing component limit unchanged. And the three values are not a shared pool. There is a fourth semantic worth pinning down: a valid approval supersedes the local daily-credit check for that activation, which means any shared-budget decision belongs in the authority rather than in the workflow. If you want the local check to remain a fallback, you have to be deliberate about which one wins.

# What the compiler-owned admission job does before activation.

# 1) mint a GitHub Actions OIDC identity token for the authority's audience
export TOKEN=$(curl -s \
  -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
  "$ACTIONS_ID_TOKEN_REQUEST_URL&audience=$GH_AW_DEFAULT_ADMISSION_AUDIENCE" \
  | jq -r .value)

# 2) post invocation identity + the compiled workflow hash
curl -sS -X POST "$GH_AW_DEFAULT_ADMISSION_URL" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"workflow_hash\":\"$COMPILED_HASH\",\"run_id\":\"$GITHUB_RUN_ID\",\"repo\":\"$GITHUB_REPOSITORY\"}"

# 3) approval -> start with allowances. denial -> never start.
# On outage the behaviour is your choice: ignore (fall back to local
# daily-credit check) or block (do not activate).

3. Wiring it up: one key, three variables, one recompile

The workflow-side surface is tiny. Add admission: true next to the existing triggers, or add "admission": true to the existing .github/workflows/aw.json object for a repository-wide opt-in, preserving its other settings. A workflow-level false cannot override that repository opt-in, which is the right direction: otherwise a modified workflow could opt itself out of governance. Then recompile with gh aw compile and commit both the source configuration and the generated .lock.yml files. One trap is called out explicitly: admission jobs are omitted by default, so setting an endpoint alone generates nothing. Runtime configuration comes from organization or repository Actions variables — GH_AW_DEFAULT_ADMISSION_URL, which must be HTTPS and end in /api/admission; GH_AW_DEFAULT_ADMISSION_AUDIENCE, which defaults to the endpoint URL; and GH_AW_DEFAULT_ADMISSION_ON_OUTAGE, set to ignore by default or block. An empty endpoint skips admission and keeps existing local checks, repository variables take precedence over organization variables, and changing these values does not require recompilation.

Server racks in a data center

The admission authority is not part of gh-aw. You have to run it.

# An approval supplies per-component maximums.
# These are illustrative allowances, not built-in tiers.
{
  "max_ai_credits": 1000,                 # primary agent
  "max_ai_credits_threat_detection": 100,
  "max_ai_credits_evals": 50
}

# Rules that matter in practice:
# - each supplied value REPLACES that component's runtime limit,
#   even when it is higher than the workflow's configured limit
# - omitted optional values leave the existing component limit alone
# - the three numbers are NOT a shared pool
# - a valid approval supersedes the local daily-credit check for
#   that activation, so any shared-budget decision belongs upstream

4. Identity is OIDC, not a personal access token

This detail deserves its own section. Authentication uses GitHub Actions OIDC rather than a PAT or a provider credential, and the feature grants id-token: write to its isolated admission and completion-callback jobs — not to the agent merely because admission is enabled. Hand-authored reusable-workflow callers must also permit that scope, or the token request fails. The closing half of the loop is conclusion_admission. After the ordinary conclusion job publishes the unified-session artifact, an isolated job sends bounded compact metadata and artifact IDs to /api/conclusion on the authority's origin, using a fresh OIDC token. Be precise about what that is: end-of-session delivery, not live streaming and not billing settlement. Delivery is best-effort, so a callback failure produces a warning without changing completed agent or safe-output results.

# Runtime configuration lives in repository or organization
# Actions variables. Repository variables win.

GH_AW_DEFAULT_ADMISSION_URL=https://policy.example.com/api/admission
#   ^ must be HTTPS and end in /api/admission
#   ^ empty value skips admission entirely and keeps local checks

GH_AW_DEFAULT_ADMISSION_AUDIENCE=https://policy.example.com/api/admission
#   ^ OIDC audience; defaults to the endpoint URL

GH_AW_DEFAULT_ADMISSION_ON_OUTAGE=ignore
#   ^ ignore = transient outage falls back to existing local controls
#   ^ block  = do not activate (explicit denials block either way)

# Changing these does not require recompiling, but the admission jobs
# must already be compiled in: an endpoint alone generates nothing.
# gh aw compile && git add .github/workflows && git commit

5. A cooperative gate, not a hard cap

The final section of the post argues against its own marketing, and that is the part to read to your security team verbatim. Admission remains cooperative: workflow editors can remove participation, repository-variable administrators can override organization defaults, and concurrent runs can overshoot a shared budget. Hard quotas, reservations and mid-run cancellation require independently enforced backend controls. Outage behaviour is an explicit availability choice. With the default ignore, a transient failure falls back to existing local controls; with block, activation stops. Explicit denials and invalid approvals block under either policy. The same week shipped six releases, v0.93.0 through v0.93.5, all pointed at hardening rather than features: safe-output integration jobs can now use network credentials when they need them, engine driver crashes arrive with secret-safe stderr diagnostics so triage is faster, Pi CLI 1.4.0 became the default, sharper custom linters landed, and compile time gained guards on indexing boundaries and cron-step overflow. Read the two together and the division of labour is clear. The gate decides whether to start and with how much. Hardening decides what happens after it starts. Neither substitutes for the other.

Charts and a laptop on a desk

Allowances replace runtime limits per component. They are not a shared pool.

# Policy authority, in the shape the contract implies.
# Keep policy server responsibilities separate from workflow execution.

def admit(request, oidc_token):
    claims = verify_oidc(oidc_token, audience=EXPECTED_AUDIENCE)
    if claims.repository != request.repo:
        return Deny("identity/repo mismatch")

    if request.workflow_hash not in APPROVED_HASHES[request.repo]:
        return Deny("unreviewed workflow revision")

    budget = shared_budget(org=claims.org)   # org-wide, cross-repo
    if budget.remaining < MIN_RUN_CREDITS:
        return Deny("org budget exhausted")

    return Approve(
        max_ai_credits=min(budget.remaining, POLICY_CAP),
        max_ai_credits_threat_detection=THREAT_CAP,
        max_ai_credits_evals=EVAL_CAP,
    )

# Sub-second decisions, but concurrency is not solved here:
# parallel runs can still overshoot a shared budget.

📌 Frequently Asked Questions

How does admission interact with the local daily-credit check?

A valid approval supersedes the local daily-credit check for that activation. Any shared-budget decision therefore belongs in the authority. If you want the local check to act as a fallback, you need to define explicitly when it applies, or you end up with two policies disagreeing.

Do the three max_ai_credits values share one pool?

No. The docs state that the three are not a shared pool: max_ai_credits covers the primary agent, while threat detection and evals have independent allowances. Each supplied allowance replaces that component's runtime limit even if it is higher than the workflow's configured limit, and omitted optional allowances leave existing limits untouched.

Why were no admission jobs generated when I set only the endpoint?

Because admission jobs are omitted by default. Setting GH_AW_DEFAULT_ADMISSION_URL generates nothing on its own; you must compile the jobs in with gh aw compile and commit the generated lock files. After that, changing the three Actions variables does not require recompilation.

Does gh-aw ship the admission authority?

No. gh-aw implements the workflow-side client and compiler wiring. The authority must be provisioned separately to own policy, OIDC identity verification and trusted usage accounting. The docs are explicit that installing gh-aw does not deploy a CAO instance or a hosted admission server.

Can it guarantee we never exceed the budget?

It reduces the chance, it does not guarantee it. The documentation calls admission a cooperative gate: editors can drop out, repository-variable administrators can override organization defaults, and concurrent runs can overshoot. Hard guarantees need independently enforced backend quotas, reservations and mid-run cancellation.