> ## Documentation Index
> Fetch the complete documentation index at: https://docs.genesiscomputing.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Activate Genesis Data Agents On Teams

> Deploy the Genesis Teams relay so Microsoft Bot Framework can reach a Snowflake SPCS-hosted Genesis instance.

# Connect Microsoft Teams to Genesis on SPCS

Microsoft Bot Framework must POST activities to a publicly reachable HTTPS
endpoint. Snowflake SPCS ingress is protected by Snowflake authentication, so
Bot Framework cannot call an SPCS-hosted Genesis service directly. The Genesis
Teams relay provides the public endpoint and forwards each activity through
authenticated SPCS ingress.

## Choose the deployment path

| Genesis deployment | Inbound path |
| - | - |
| Public AWS, Railway, Cloud Run, or another genuinely public HTTPS host | Use the [direct Bot Framework runbook](/docs/Workspaces/Microsoft-Teams/Activate-Bot-Framework-Eve-in-Teams). |
| Local development exposed through an HTTPS tunnel such as ngrok | Use the [direct Bot Framework runbook](/docs/Workspaces/Microsoft-Teams/Activate-Bot-Framework-Eve-in-Teams). |
| Snowflake SPCS | Deploy the inbound relay described on this page. |

<Note>
  Complete the Azure Bot, Teams channel, Genesis bot credentials, and Teams app
  manifest steps in the
  [direct Bot Framework runbook](/docs/Workspaces/Microsoft-Teams/Activate-Bot-Framework-Eve-in-Teams).
  On SPCS, replace only its direct messaging-endpoint step with the relay
  endpoint configured below.
</Note>

## Before you begin

You need:

* An Azure subscription and permission to deploy a public HTTPS container
* A Microsoft 365 tenant and an Azure Bot configured for Microsoft Teams
* Access to administer the Genesis deployment's Config pages
* Snowflake permission to create a dedicated service user and role
* The public SPCS service URL for the Genesis deployment

### Register the Azure resource provider

Azure Container Apps requires the `Microsoft.App` resource provider. On a
subscription that has never hosted a Container App it is unregistered, and
container-app creation fails until registration completes:

```bash theme={null}
az provider register -n Microsoft.App --wait
az provider show -n Microsoft.App --query registrationState -o tsv
# → Registered
```

Registration is a one-time, subscription-wide operation and can take a few
minutes. Azure App Service instead requires `Microsoft.Web`.

## Deploy the relay

Choose an immutable relay release and pull it from the public Docker Hub
repository. Relay versions are independent of Genesis versions.

```bash theme={null}
docker pull genesiscomputingai/genesis-teams-spcs-relay:vX.Y.Z
```

Use an immutable `vX.Y.Z` tag for production. `sha-<commit>` identifies a
source revision, while `latest` moves whenever a relay release is published.

Deploy the image to a public Azure container host such as Azure Container Apps
or Azure App Service, expose container port `8080` over HTTPS, and configure:

| Environment variable | Required | Purpose |
| - | -: | - |
| `RELAY_SPCS_BASE_URL` | yes | Public Genesis SPCS service URL, without an API path. |
| `RELAY_SNOWFLAKE_ACCOUNT` | yes | Snowflake account identifier used for OAuth and JWT subjects. |
| `RELAY_SNOWFLAKE_USER` | yes | Dedicated Snowflake service user for the relay. |
| `RELAY_SNOWFLAKE_PRIVATE_KEY_PATH` | yes | Path to the mounted service-user PEM private key. |
| `RELAY_SNOWFLAKE_PRIVATE_KEY_PASSPHRASE` | no | Passphrase for an encrypted PEM private key. |
| `RELAY_LOG_LEVEL` | no | Python log level; defaults to `INFO`. |

Store the RSA private key in the Azure host's secret facility, mount it
read-only, and set `RELAY_SNOWFLAKE_PRIVATE_KEY_PATH` to the mount path.

For Azure Container Apps, set **minimum replicas to at least 1**. Its default
scale-to-zero behavior is unsafe here: the relay pre-warms its Snowflake token
at startup, and a cold token exchange can take tens of seconds while Bot
Framework allows about 15 seconds for delivery. A cold-starting relay can
therefore drop the first message.

Configure the **readiness** probe to use `/ready` and the **liveness** probe to
use `/health`: readiness gates traffic, while liveness restarts the container.
Replica replacement for platform maintenance, node drain, and image updates
happens regardless of scaling configuration, so the readiness probe matters
even when replicas are pinned to 1. Without it, a replacement relay can receive
activities before its Snowflake token is warm, and Bot Framework may exhaust
its retries before the relay can serve, dropping the message.

The relay's activity-deduplication cache is per-instance and in memory. With
`N` relay replicas, the same activity can be admitted up to `N` times.
Genesis-side deduplication remains authoritative.

After deployment, verify:

```bash theme={null}
curl https://<relay-host>/health
# → {"status":"ok","token_state":"valid"}
```

Do not configure Azure Bot until `token_state` is `valid`.

## Configure Snowflake and Genesis

<Steps>
  <Step title="Create a dedicated Snowflake identity">
    Create a service user and a role used only by the relay. The recommended
    name for both is `GENESIS_TEAMS_RELAY`. Configure RSA key-pair
    authentication on the user.

    ```sql theme={null}
    CREATE ROLE <relay_role>;

    CREATE USER <relay_user>
      TYPE = SERVICE
      DEFAULT_ROLE = <relay_role>
      RSA_PUBLIC_KEY = '<base64 body, no PEM header/footer>';

    GRANT ROLE <relay_role> TO USER <relay_user>;
    ```

    Then grant that role exactly two privileges — the Genesis application role,
    which is what authorises the relay to reach the Genesis service endpoint,
    and usage on the warehouse the deployment runs against:

    ```sql theme={null}
    GRANT APPLICATION ROLE GENESIS_DATA_AGENTS.APP_PUBLIC TO ROLE <relay_role>;
    GRANT USAGE ON WAREHOUSE <warehouse> TO ROLE <relay_role>;
    ```

    `GENESIS_DATA_AGENTS` is the Genesis application name; substitute the name
    your deployment used at install time. `APP_PUBLIC` is the application's only
    application role.

    Confirm both grants landed:

    ```sql theme={null}
    SHOW GRANTS TO ROLE <relay_role>;
    ```

    Do not reuse a human, `ACCOUNTADMIN`, or application-owner credential for
    the relay. Record the key owner in the deployment runbook.
  </Step>

  <Step title="Rotate the relay key">
    Rotation is a two-part operation: publish the new public key in Snowflake,
    then point the relay at the matching new private key.

    ```sql theme={null}
    ALTER USER <relay_user> SET RSA_PUBLIC_KEY='<base64 body, no PEM header/footer>';
    ```

    Strip the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` lines —
    Snowflake stores the base64 body only.

    Verify what Snowflake now holds:

    ```sql theme={null}
    DESCRIBE USER <relay_user>;
    ```

    `RSA_PUBLIC_KEY_FP` is the fingerprint of the stored key and
    `RSA_PUBLIC_KEY_LAST_SET_TIME` records when it was last changed — together
    they confirm the rotation took effect rather than silently leaving the old
    key in place.

    For zero-downtime rotation, Snowflake accepts a second key in
    `RSA_PUBLIC_KEY_2`. Publish the new key there first, restart the relay onto
    the new private key, confirm `/health` still reports `token_state` `valid`,
    then clear the old key:

    ```sql theme={null}
    ALTER USER <relay_user> SET RSA_PUBLIC_KEY_2='<new base64 body>';
    -- restart the relay with the new private key mounted, verify /health
    ALTER USER <relay_user> UNSET RSA_PUBLIC_KEY;
    ```

    Update the mounted private key in the Azure secret store as part of the same
    change, and rotate immediately after any suspected exposure.
  </Step>

  <Step title="Confine the relay identity in Genesis">
    In the Genesis dashboard, open **Config → Advanced Settings** and set
    `teams.relay_service_user` to the exact Snowflake service-user name, for
    example `GENESIS_TEAMS_RELAY`.

    Genesis then confines requests authenticated as that identity to the Teams
    message, Teams health, and enabled Teams ingress-canary routes. The
    forwarded Bot Framework authorization header is accepted only after this
    identity gate passes.
  </Step>

  <Step title="Optional: enable ingress diagnostics">
    When header loss or relay routing issues are suspected, set
    `teams.ingress_canary_enabled` to `true` in **Config → Advanced Settings**.
    The confined relay identity (and unauthenticated callers when the flag is
    on) can then reach `GET` or `POST /api/teams/ingress-canary`, which returns
    a value-redacted summary of headers (presence, length, and a short SHA-256
    prefix — never plaintext tokens). Disable the flag after troubleshooting.
  </Step>

  <Step title="Approve Bot Framework egress">
    In the Genesis dashboard, open **Config → Microsoft Teams**, click
    **Approve External Access** (this opens Snowsight to approve the
    "Teams Bot Framework" external-access reference), then **Assign to
    Genesis**. Genesis needs this external access both to fetch Microsoft's
    signing metadata (to validate inbound Bot Framework tokens) and to send
    replies. Assigning briefly restarts the Genesis service to apply the change.
  </Step>
</Steps>

### If the relay starts returning 403s

<Warning>
  **A relay that authenticates fine but gets 403s has lost its application-role
  grant.** This failure is worth recognising on sight because every identity
  signal looks healthy: the service user is enabled, the key is valid, Snowflake
  login succeeds, and the relay's `/health` still reports `token_state`
  `valid`. Only the authorisation to reach Genesis is gone, so the symptom
  presents as a Genesis permissions bug rather than a missing Snowflake grant.

  The grant is scoped to the Genesis application object rather than to the
  account, so it does not survive that object being dropped and recreated —
  which is what reinstalling Genesis does. The relay's user and role are
  account-level and survive untouched, which is why nothing else looks wrong.

  Check first:

  ```sql theme={null}
  SHOW GRANTS TO ROLE <relay_role>;
  ```

  An empty result confirms it. Re-run the grants from the identity step above:

  ```sql theme={null}
  GRANT APPLICATION ROLE GENESIS_DATA_AGENTS.APP_PUBLIC TO ROLE <relay_role>;
  GRANT USAGE ON WAREHOUSE <warehouse> TO ROLE <relay_role>;
  ```

  Adding that `SHOW GRANTS` check to the post-install runbook costs one query
  and removes an outage class that is otherwise slow to diagnose.
</Warning>

## Configure Azure

1. Before restricting Azure Container Apps ingress, verify in a live deployment
   whether IP restrictions accept the Azure Bot Service service tag or require
   CIDR ranges. Do not configure a service-tag restriction until that behavior
   is confirmed.
2. In the Azure Bot resource, open **Settings → Configuration**.
3. Set **Messaging endpoint** to:

   ```text theme={null}
   https://<relay-host>/api/teams/messages/<bot_id>
   ```

   `<bot_id>` is the case-sensitive Genesis bot id, usually `Eve`.
4. Leave **Enable Streaming Endpoint** unchecked and click **Apply**.

## Verify the connection

1. Confirm `GET https://<relay-host>/health` reports
   `"token_state":"valid"`.
2. Confirm the Genesis Teams health endpoint is reachable through the SPCS
   deployment and reports at least one registered bot.
3. Install the Teams app and send the bot a direct message.
4. Check relay logs for the request id, bot id, upstream status, and latency.
   The relay intentionally does not log request bodies, authorization headers,
   private keys, or tokens.
5. Confirm Genesis logs show the activity was enqueued and the reply was sent.

## Security boundary

<Warning>
  **The relay is trusted infrastructure.** The Bot Framework JWT does not make
  an untrusted relay safe to operate. If the relay host or its Snowflake key is
  compromised, an attacker can impersonate Teams traffic to this bot and reach
  the Teams route only until the key is rotated.
</Warning>

Reduce this residual risk by keeping the relay small and patched, applying a
host-specific inbound policy only after it has been verified, confining its
Snowflake identity in Genesis, protecting the private key in the Azure secret
store, and rotating the key immediately after suspected exposure.

## Related

* [Activate Eve in Teams (Bot Framework)](/docs/Workspaces/Microsoft-Teams/Activate-Bot-Framework-Eve-in-Teams) —
  Azure Bot, Teams app, and direct-public-host setup
* [Microsoft Bot Framework documentation](https://learn.microsoft.com/en-us/azure/bot-service/)
