SysEngineering

Google Workspace Admin Assistant

A read-only, multi-tenant Slack chatbot for Google Workspace administration. Ask it anything in plain English (“what are the 2SV settings for Hillspire’s workspace?”, “is jdoe@scsp.ai suspended?”, “who are the super admins across all workspaces?”) and it routes the question to the right Google Admin Console(s) on the backend, runs read-only Admin SDK / Cloud Identity / Chrome Management lookups, and writes the answer itself.

The LLM backbone is Claude Opus 5. OpenAI and Vertex paths remain in agent.py as fallbacks, selectable with LLM_PROVIDER without a code change.

Architecture

Slack (Events API)
  -> Cloud Run: FastAPI (main.py)
       -> agent.py: Claude Opus 5 tool-calling loop (no hardcoded intents)
            -> workspace.py: ~20 read-only tools, each taking a `workspace`
               parameter, executed with per-tenant delegated credentials
                 -> Google Admin SDK / Reports / Cloud Identity / Chrome APIs

Read-only guarantee

Three enforcement layers:

  1. Every OAuth scope requested ends in .readonly (test_assistant.py::test_every_scope_is_readonly enforces this).
  2. Only list/get/report API calls exist in workspace.py. There is no code path that mutates tenant data.
  3. Domain-wide delegation in each tenant only needs the read-only scope list (dwd_readonly_scopes.txt) for this assistant’s lookups.

Multi-tenant routing

Routes come from JSON via one of (first match wins):

The production config reuses the helpdesk module’s routes secret (projects/okta-workflows-offboard/secrets/slack-helpdesk-routes-sandbox), so tenants are maintained in exactly one place. Route shape (same as the helpdesk module):

[
  {
    "key": "hillspire-prod",
    "label": "Hillspire",
    "domains": ["hillspire.com", "ericschmidt.com"],
    "adminEmail": "admin@hillspire.com",
    "serviceAccountSecret": "gw-sa-hillspire"
  }
]

GW_ROUTE_SA_SECRET_OVERRIDES (JSON object, route key -> secret resource) overrides a route’s service-account secret. Used so the sandbox route keeps using this project’s own already-authorized service account.

Routes are cached for the life of the instance; deploy a new revision (or restart) after changing them.

Environment variables

Variable Purpose
SLACK_BOT_TOKEN / SLACK_SIGNING_SECRET Slack app credentials (Secret Manager).
LLM_PROVIDER anthropic (default in production), openai, or vertex.
ANTHROPIC_API_KEY Claude key (Secret Manager: gw-assistant-anthropic-api-key).
ANTHROPIC_MODEL Default claude-opus-5.
ANTHROPIC_EFFORT Default low, for latency. Raise for harder questions.
OPENAI_API_KEY OpenAI key (Secret Manager) — fallback provider.
OPENAI_MODEL Default gpt-5.4-mini.
OPENAI_MAX_OUTPUT_TOKENS Default 1500 (clamped 300-4000).
OPENAI_REASONING_EFFORT Optional reasoning effort.
GW_ROUTES_SECRET / GW_ROUTES_JSON / GW_ROUTES_FILE Workspace routes.
GW_ROUTE_SA_SECRET_OVERRIDES Per-route SA secret overrides (JSON).
GW_DEFAULT_WORKSPACE Optional route key assumed when the user says “our” without naming a workspace. Unset = the bot asks.
GOOGLE_CLOUD_PROJECT Fallback project for short serviceAccountSecret names.
SLACK_ALLOWED_USER_IDS Optional comma-separated Slack user allowlist.
ALLOW_UNVERIFIED_SLACK_REQUESTS Local dev only.
LOG_LEVEL Default INFO.

Onboarding a tenant

  1. The tenant must be in the routes secret (it already is if the helpdesk module covers it).
  2. In that tenant’s Admin Console, append the scopes from dwd_readonly_scopes.txt to the existing domain-wide delegation entry for its gw-sa-* service account. Append — do not replace.
  3. Ask the bot something about that workspace. A 403 answer means step 2 is not done yet; everything else keeps working.

Tests

cd integrations\google_workspace_admin_assistant
.\.venv\Scripts\python.exe -m unittest test_assistant -v

Scheduled group inventory

group_inventory_job.py reuses this service’s multi-tenant read-only credential layer to inventory actual Google Group membership and reconcile it against the HR-backed extraction workbook. It is deployed as a separate Cloud Run Job; see GROUP_INVENTORY.md.

Deploy

See HANDOFF.md for the full no-traffic-revision deploy flow. Short version:

gcloud run deploy google-workspace-admin-assistant `
  --project gw-admin-assistant --region us-central1 `
  --source . --no-traffic --tag <tag> --revision-suffix <suffix>