Skip to main content

Activate Eve in Teams (Bot Framework)

This is the durable path for putting a Genesis bot inside Microsoft Teams. The bot is installed as a real Teams app — it has its own avatar, its own @-mention, and its own credentials. Messages it sends are attributed to the bot, not to the human admin who set up the integration. Users can @-mention it in channels and DM it directly. This is the same model used by Microsoft Copilot, Otter, Notion, and every other AI bot in Teams.
This page describes the Bot Framework path. There are two other Teams integration paths in Genesis with different tradeoffs:
  • Microsoft Graph / delegated OAuth (teams_tools) — sends messages as the connected human user, not as a bot. See Microsoft Teams.
  • Power Automate webhooks (teams_webhook_tools) — outbound-only alerts and reports, attributed to the Workflow. See Teams Webhook Setup.
Use the Bot Framework path when you need two-way conversation, native @-mentions, channel install events, or Adaptive Card action callbacks.

Prerequisites

  • Azure subscription with permission to create Bot resources. The free Azure tier is sufficient — Bot Service has an F0 (Free) pricing tier.
  • Microsoft 365 admin for the tenant where the bot will be used. Required for sideloading the Teams app manifest. If you’re testing in your own dev tenant, the Microsoft 365 Developer Program gives a free renewable tenant with 25 user licenses.
  • Genesis instance reachable from the public internet over HTTPS. Bot Framework Service POSTs activities to a public URL; localhost is not reachable. This is automatic only for genuinely public hosts such as Railway or Cloud Run. SPCS ingress is protected by Snowflake SSO and requires the SPCS inbound relay. For local development, expose port 8083 (or whichever port your stack uses) via ngrok or similar — ngrok http 8083 gives an https://*.ngrok-free.dev URL pointing at your local Genesis.

Architecture

For each Genesis bot you want available in Teams, you create one Azure Bot resource. The Azure Bot has a Microsoft App ID and a client secret. Genesis stores those on the bot’s bot_servicing row. When a Teams user messages the bot, Microsoft Bot Framework POSTs the activity (with a Microsoft-signed JWT) to POST /api/teams/messages/<bot_id> on your Genesis instance (or to the public SPCS inbound relay, which forwards the same JWT in X-Teams-Bot-Authorization while authenticating to Genesis with its confined Snowflake service-user credential). Genesis validates the JWT, dispatches to the bot’s agent loop, and posts the response back through Bot Framework via continue_conversation.
On Snowpark Container Services, approve and assign Teams Bot Framework external access from Config → Microsoft Teams before bots can validate inbound JWTs or send replies. See Activate Genesis Data Agents On Teams.
On Snowpark Container Services, the relay reaches Genesis through an application-role grant held by its Snowflake role. That grant belongs to the Genesis application object rather than to the account, so when the application object is dropped and recreated — as a reinstall does — the grant goes with it while the relay’s user and role survive untouched. Teams then fails for every message, in a way that looks like a Genesis fault rather than a missing grant.Every identity signal stays healthy while this is broken — the service user is enabled, the key is valid, Snowflake login succeeds, and the relay’s /health still reports token_state valid — so the relay authenticates and then receives 403s. Check the grant before anything else: SHOW GRANTS TO ROLE <relay_role> returning nothing means it must be re-applied. See Activate Genesis Data Agents On Teams.

Step 1: Create the Azure Bot resource

In the Azure Portal:
  1. Create a resource → search for Azure Bot → Create.
  2. Fill in Bot handle (any unique-in-Azure string, e.g. genesis-eve-prod).
  3. Pick a Resource group (create one if needed).
  4. Data residency: Global (regional only matters for compliance-bound deployments).
  5. Pricing tier: F0 (Free) — the free tier is fine for dev and most production use.
  6. Type of App:
    • Single Tenant — the bot only works in your tenant. Simplest. Use this for internal Genesis instances and dev tenants.
    • Multi Tenant — the bot works across customer tenants. Choose this only if you’re distributing Genesis as a multi-tenant SaaS. Requires that the Microsoft App ID be configured for AzureADMultipleOrgs audience.
      The Azure Portal’s Bot creation wizard hides Multi Tenant as a quick option as of mid-2026. To create a multi-tenant Azure Bot, you must first create the App Registration manually in Entra ID with audience “Accounts in any organizational directory,” then choose Use existing app registration in the Bot creation flow.
  7. Creation type: Create new Microsoft App ID — don’t reuse an existing App Registration intended for the delegated-OAuth Graph path; the credential rotation cadence and audit story is cleaner with a dedicated app reg.
  8. Service management reference: leave blank.
  9. Click Review + create → Create. Provisioning takes 30-90 seconds.

Step 2: Capture credentials

After provisioning, open the new Bot resource → Settings → Configuration. Capture:
  • Microsoft App ID — the GUID at the top of the Configuration page. This becomes teams_app_id.
  • App Tenant ID — also on the Configuration page. This becomes teams_app_tenant_id.
  • Bot Type — confirm it matches what you chose in Step 1.6 (Single Tenant or Multi Tenant). This becomes teams_app_type.
To get the client secret (which becomes teams_app_password):
  1. Click Manage Password next to the App ID. This takes you to the underlying Entra App Registration’s Certificates & secrets blade.
  2. Azure auto-creates a secret when the Bot resource is provisioned, but the unmasked value is only shown at creation time — once the page refreshes, only La_*****… is visible. Delete the auto-created secret (trash icon), then click + New client secret:
    • Description: genesis-<bot-handle>
    • Expires: 180 days (rotate per your security policy)
    • Click Add
  3. Immediately copy the Value column (the click-to-copy button next to it). The value disappears on the next page load. Store it directly into your secret manager / bot_servicing row in the next step — do not paste it into chat, tickets, or terminal scrollback.

Step 3: Enable the Teams channel

In the same Bot resource → Settings → Channels:
  1. Find Microsoft Teams in the Available Channels list → click it.
  2. Accept the Microsoft Channel Publication Terms.
  3. Pick Microsoft Teams Commercial (or Microsoft Teams Government if your customer is on the Government cloud).
  4. Click Apply. A “Microsoft Teams channel enabled” notification confirms.

Step 4: Set the messaging endpoint

In the same Bot resource → Settings → Configuration:
  • Messaging endpoint:
    • <your-genesis-public-host> is your Genesis instance’s HTTPS hostname (e.g. genesis.your-company.com, or the ngrok URL during dev).
    • <bot_id> is the Genesis bot’s id (case-sensitive — usually Eve).
  • Leave Enable Streaming Endpoint unchecked.
  • Schema Transformation Version V1.3 is fine.
  • Click Apply — a “Saved Configuration” notification confirms.

Step 5: Configure the bot in Genesis

In the Genesis dashboard, open Agents → your bot (e.g. Eve) → the Teams tab and enter the Bot Framework identity from Step 2:
  • App ID — the Microsoft App (client) ID.
  • Client Secret — the client secret value (on later edits, leave this blank to keep the stored secret).
  • Tenant ID — your Azure AD tenant (required for a SingleTenant app).
  • App Type — SingleTenant or MultiTenant (match your app registration).
  • Toggle Activate on Teams on, then click Save.
Saving stores the credentials. Genesis refreshes the bot’s Teams adapter asynchronously in the background — you do not need a full Genesis restart for credential changes to take effect. Allow a few seconds after save before testing inbound messages. If inbound traffic still fails JWT validation with Invalid AppId passed on token, confirm the App ID and tenant match the Azure registration and check /api/teams/bot/{bot_id}/runtime.
Deactivated agents do not receive new Teams messages. If an agent is deactivated in Agents Center and Skills, Genesis evicts its Teams adapter until it is reactivated.
After the restart, the server log shows the adapter registered:
and the public health endpoint reports the registered count:
The Activate on Teams toggle is the per-bot on/off switch. To temporarily disable the Teams surface for a bot without losing its configuration, untoggle it and restart Genesis. To disable Teams across all bots globally, set the config field teams.enabled to false (or the equivalent env var GENESIS_TEAMS_ENABLED=false, which still works). That global switch is a kill-switch: inbound POST /api/teams/messages/{bot_id} traffic and teams_bot_action SEND_PROACTIVE are refused on the next request — you do not need to wait for a session rebuild.Note that GENESIS_TEAMS_ENABLED supplies the default for teams.enabled, so it applies only while that field has never been set explicitly. If someone has saved teams.enabled through the config UI or API, the stored value wins over the env var — use the override GENESIS__TEAMS__ENABLED=false if you need the switch to take effect regardless of stored config.

Checking whether a bot’s Teams adapter is really running

Saving credentials is not the same as having a live adapter — registration also requires Activate on Teams, a non-empty App ID, and a successful background runtime refresh after save (usually within a few seconds). To see the actual state for one bot:
accepting_inbound: false means inbound Teams messages for that bot are being rejected with 404 — check globally_enabled (server-wide teams.enabled) and adapter_registered (per-bot adapter in the live registry). When adapter_registered is false, reason tells you which registration prerequisite is missing.

Step 6: Download the Teams app package

In the same Teams tab for the bot, click Download Teams app package. Genesis renders a Teams app .zip — ready to sideload — built from the bot’s configured Bot Framework App ID. The package contains:
  • manifest.json — the Teams app declaration (bot id, scopes, command list)
  • color.png + outline.png — bot icons (default solid-color placeholders)
Set (and save) the bot’s Teams credentials in Step 5 before you download. The package embeds the bot’s App ID, so if you download it while the bot is still pointed at a different App ID, the sideloaded app’s tokens won’t match the bot and every message will 401. Re-download after any App ID change.
The Teams app id (the GUID Teams uses to track installs) is a deterministic uuid5 derived from the bot id, so re-downloading never creates a “new” app from the customer’s perspective.

Step 7: Sideload the manifest into your tenant

Open Teams Admin Center:
  1. Manage apps → Actions (top right) → Upload new app → Upload → pick the .zip from Step 6.
  2. A “New app added” confirmation appears. The app’s status will be “Available to: Everyone (org-wide default)” — meaning anyone in your tenant can install it from the Teams Apps catalog.
  3. If your tenant policy blocks custom apps, you’ll see an error. Go to Teams apps → Setup policies → Global → Upload custom apps and enable it, then retry.
The generated app package requests the resource-specific consent (RSC) permission ChannelMessage.Read.Group. A team owner must consent to this permission when the app is installed in a team or updated. Tenant policy can prevent a team owner from granting it; in that case, ask your Microsoft 365 administrator to allow or approve the app.
This permission means Microsoft delivers every message from channels where the app is installed to Genesis. Genesis receives all of those channel messages, including messages in threads where the bot was not mentioned. It only acts on a channel thread after the bot has been @-mentioned in that thread, and stop ends that engagement.
Existing installations do not gain this permission automatically. Download a new app package from Genesis, re-upload it in Teams Admin Center, and update the team installation so a team owner can review and consent to the new permission. Until that happens, the bot continues to receive only explicitly mentioned channel messages, so untagged thread follow-ups cannot reach Genesis.
Updating the catalog app does nothing for a team running a sideloaded copy. If the same bot was ever uploaded straight into a team as a custom app, that team keeps its own copy of the package with its own permissions. Publishing a new version to the org catalog leaves it untouched, and untagged messages keep not arriving even though Teams Admin Center shows the new version and the permission.Check for this when Teams Admin Center shows the app installed for no one while the bot is clearly present in a channel, or when several entries with the same bot name appear in the @-mention autocomplete. Remove every copy from the team, then install the catalog app once so a team owner consents to it.
Because RSC is granted at install time, an in-place update does not always re-trigger the consent prompt. If untagged follow-ups still do not arrive after updating, remove the app from the team and add it again — a fresh install reliably requests consent. The failure is silent from inside Teams: a bot that never received a message and a bot that received one and correctly ignored it both look identical. Genesis logs tell them apart — an ignored message records reason=not_directed_to_bot, whereas a message Microsoft never delivered leaves no log line at all. For operators who have enabled http.rate_limit.enabled (it is off by default), RSC increases inbound /api/teams request volume because unengaged channel messages still reach Genesis before being ignored. If that traffic should not consume the general HTTP rate-limit budget, add /api/teams to the existing configurable ignore-prefix list.

Step 8: Install the bot in a Teams chat or channel

In Teams Desktop (or Teams Web):
  1. Click Apps in the left rail.
  2. Search for the bot name (e.g. Eve). It appears under “Built for your org.”
  3. Click Add — choose Personal for a 1:1 DM with the bot (simplest first smoke), or Add to a team / chat for group conversations.
  4. Once added, type a message. The bot responds within a few seconds.

Verifying end-to-end

A successful install + message produces this trace in the Genesis server log:
If the activity arrives at ngrok but doesn’t reach Genesis, that’s a routing problem (typically: messaging endpoint URL wrong, or bot_id mismatch between Azure config and bot_servicing). The ngrok inspector at http://127.0.0.1:4040 shows every request. If the message reaches Genesis but Eve doesn’t respond, check:
  • /api/teams/health returns registered_bots > 0 (the per-bot adapter must be registered).
  • The bot’s bot_servicing row has teams_active='Y' AND a non-null teams_app_id.
  • The bot’s session bootstrapped successfully — look for STARTUP_PHASE create_app_sessions elapsed_s (success) versus Failed to create agent sessions (failure; commonly an unrelated LLM config issue).

What this path supports today

  • ✅ User @-mentions the bot in a channel or DMs it — bot responds in-thread
  • ✅ Messages are attributed to the bot principal (not a human admin)
  • ✅ Channel install / uninstall events recorded in teams_installations
  • ✅ Adaptive Card content rendered correctly when posted from agent tools
  • ✅ Eve’s full agent loop (skills, tool calls, multi-turn context)
  • ✅ Accepted Teams messages create Requests rows automatically by default (ticket_automation.auto_track_chat_requests); channel reply chains reuse one Request, follow-ups attach by deterministic thread/quote correlation, and each row deep-links back to the originating Teams message when Genesis can build a teams.microsoft.com message URL
  • ⚠️ Adaptive Card Action.Execute (Approve/Reject buttons) — code path exists but needs a real card-sending agent flow to smoke
  • ⚠️ Proactive sends (teams_bot_action SEND_PROACTIVE) — requires conversation reference persistence (supported in both SQLAlchemy and legacy metadata modes; the bot must have received at least one message since the reference was stored), and teams.enabled must be true server-wide. Use teams_bot_tools / LIST_REACHABLE to see which conversations are currently reachable.

Common pitfalls