Skip to content

[scope] /ada Slack slash command on top of /run_sse #275

Description

@Levaj2000

API surface check (main, 2026-05-14) — no material drift from #212.
Confirmed endpoints: POST /run_sse · POST /apps/{app}/users/{user}/sessions · GET /ada/healthz · GET /ada/version
SSE event shape: content.parts[]text | functionCall {id,name,args} | functionResponse {id,name,response}; top-level usageMetadata, partial flag.
Auth: bearer token checked against AI_IDENTITY_API_KEY/ADA_ADMIN_KEY when ADA_REQUIRE_AUTH=1. Everything below is safe to build against.


1. Hosting

Ada already runs on GKE in the ai-identity namespace. Add a dedicated ada-slack Deployment + ClusterIP Service; reuse the existing GKE Ingress with a new /slack/* path rule. A second Ingress for one path is unnecessary toil; path-based routing on the existing one keeps cert management in one place.

GKE manifests (new files k8s/ada-slack-deployment.yaml, k8s/ada-slack-service.yaml):

# Deployment (namespace: ai-identity)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ada-slack
  namespace: ai-identity
spec:
  replicas: 2
  selector:
    matchLabels: {app: ada-slack}
  template:
    metadata:
      labels: {app: ada-slack}
    spec:
      serviceAccountName: ada-gke-sa        # Workload Identity → Vertex AI
      containers:
      - name: ada-slack
        image: gcr.io/ai-identity/ada-slack:$(IMAGE_TAG)
        env:
        - {name: ADA_REQUIRE_AUTH, value: "1"}
        - name: ADA_ADMIN_KEY
          valueFrom: {secretKeyRef: {name: ada-secrets, key: ada-admin-key}}
        - name: SLACK_SIGNING_SECRET
          valueFrom: {secretKeyRef: {name: ada-secrets, key: slack-signing-secret}}
        - name: SLACK_BOT_TOKEN
          valueFrom: {secretKeyRef: {name: ada-secrets, key: slack-bot-token}}
        - name: ADA_INTERNAL_URL
          value: "http://ada-svc.ai-identity.svc.cluster.local:8000"
        ports:
        - containerPort: 9000
---
apiVersion: v1
kind: Service
metadata:
  name: ada-slack-svc
  namespace: ai-identity
spec:
  selector: {app: ada-slack}
  ports:
  - {port: 80, targetPort: 9000}

Secrets (GCP Secret Manager → External Secrets Operator):

  • slack-signing-secret — from Slack app config
  • slack-bot-tokenxoxb-… for chat.postMessage/chat.update
  • ada-admin-key — bearer token for internal calls to Ada /run_sse

Image: New docker/ada-slack/Dockerfile (Python 3.12, ~200 LOC FastAPI app). Built in existing Cloud Build pipeline, tagged with git SHA.

Ingress patch (add to existing k8s/ingress.yaml):

- path: /slack/*
  backend:
    service: {name: ada-slack-svc, port: {number: 80}}

2. Authentication

Slack request verification: Middleware verifies X-Slack-Signature via HMAC-SHA256 over v0:{X-Slack-Request-Timestamp}:{raw_body} with SLACK_SIGNING_SECRET. Reject if |now − timestamp| > 300 s (replay window). This runs before any handler touches the payload.

User attribution: Map Slack user_id → AI Identity user at call time (query /api/v1/users?slack_id= or a lookup table in the ada-slack config). Use the resolved AI Identity user_id as the /run_sse user_id field so audit logs reflect real identities. If no mapping exists, fall back to slack:{team_id}:{user_id} as a synthetic handle — better than collapsing all activity to a shared bot account.


3. Session Strategy

Recommendation: one Slack thread = one Ada session.

Session ID: slack:{team_id}:{channel_id}:{thread_ts} (use the root message ts as thread_ts; follow-up messages in the thread carry that same ts). This preserves context across follow-up questions — the primary value of an agentic assistant. Ada can re-read files across a multi-turn debug session without re-establishing state.

The alternative (ephemeral per-command) is simpler but destroys context on every message, wasting tokens and breaking multi-turn flow. Not worth it.

Expire sessions after 24 h of inactivity via ADK's session TTL to prevent stale context buildup.


4. Response Format

Slack requires an HTTP 200 within 3 seconds. Ada's SSE stream can run 10–60 s.

Flow:

  1. < 100 ms: Return HTTP 200 (empty body). Immediately POST _Ada is thinking…_ via response_url (5-min window, no auth).
  2. Background task (asyncio.create_task): Open ADK SSE stream. Buffer text; accumulate tool events.
  3. First content chunk: chat.update to replace the thinking message.
  4. Subsequent updates: chat.update on the same message ts, at most once per second (Slack throttle: ~1 req/s per channel per bot). Batch partial chunks within the 1 s window.
  5. Stream close: Final chat.update with complete response; post tool-call summaries as threaded replies (see §5).

For MVP, asyncio.create_task in the FastAPI process is sufficient. Migrate to a task queue (e.g. Cloud Tasks) if pod restarts mid-stream become a problem.


5. Tool-Call Surfacing

Post tool calls as threaded replies on the main Ada message — one reply per unique tool name, collapsing multiple calls to the same tool. Use Block Kit.

Mock Block Kit JSON for a read_file call:

{
  "blocks": [
    {
      "type": "context",
      "elements": [{"type": "mrkdwn", "text": ":wrench: *read_file* · `path: agent/ada/agent.py`"}]
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Args*\n```{\n  \"path\": \"agent/ada/agent.py\"\n}```"
      }
    },
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*Response* (truncated)\n```from google.adk import Agent\n…```"
      }
    }
  ]
}

Truncate response to 500 chars with … [truncated] suffix (Slack block text cap is 3 000 chars). For responses worth preserving in full, upload via files.upload and link in the block.


6. Minimal Infra Changes

Area Change
CORS None — ada-slack calls Ada internally over the cluster network
serve.py / auth None — ada-slack calls ada-svc with Authorization: Bearer $ADA_ADMIN_KEY; existing middleware handles it
Slack app manifest New (see below)
GKE ada-slack Deployment + Service + Ingress path rule
CI/CD Add ada-slack image build step to cloudbuild.yaml

Slack app manifest (slack-manifest.yaml):

display_information:
  name: Ada
  description: AI Identity senior engineer
  background_color: "#b45309"
features:
  slash_commands:
    - command: /ada
      url: https://ada.ai-identity.example.com/slack/slash
      description: Ask Ada about the codebase
      usage_hint: "[your question]"
      should_escape: false
  bot_user:
    display_name: Ada
    always_online: true
oauth_config:
  scopes:
    bot:
      - chat:write
      - chat:write.public
      - commands
      - files:write
settings:
  socket_mode_enabled: false
  token_rotation_enabled: false

New files:

  • docker/ada-slack/Dockerfile
  • docker/ada-slack/main.py (~200 LOC FastAPI: /slack/slash handler + HMAC middleware + SSE bridge)
  • k8s/ada-slack-deployment.yaml
  • k8s/ada-slack-service.yaml
  • Patch k8s/ingress.yaml (add /slack/* rule)
  • slack-manifest.yaml

References: #212 (serve.py + chat UI shipped on main)

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions