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.
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
main.py — Slack signature verification, event filtering, allowlist,
placeholder/edit message flow, per-thread conversation memory (in-process).agent.py — the agent loop. The model receives the workspace routing table
in its system prompt plus tool schemas, decides which tools to call against
which tenants (parallel calls supported), then composes the Slack reply.workspace.py — tenant routes, per-route service-account credentials with
domain-wide delegation, and the read-only tool registry.Three enforcement layers:
.readonly
(test_assistant.py::test_every_scope_is_readonly enforces this).workspace.py. There is no code
path that mutates tenant data.dwd_readonly_scopes.txt) for this assistant’s lookups.Routes come from JSON via one of (first match wins):
GW_ROUTES_JSON — inline JSON.GW_ROUTES_FILE — path to a JSON file.GW_ROUTES_SECRET — Secret Manager resource (full
projects/.../secrets/... path, or short name +
GW_ROUTES_SECRET_PROJECT).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.
| 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. |
dwd_readonly_scopes.txt to the existing domain-wide delegation entry for
its gw-sa-* service account. Append — do not replace.cd integrations\google_workspace_admin_assistant
.\.venv\Scripts\python.exe -m unittest test_assistant -v
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.
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>