Skip to content

[codex] Add teamsbot ingress service#665

Open
neokry wants to merge 4 commits into
paradigmxyz:mainfrom
neokry:codex/teamsbot-ingress
Open

[codex] Add teamsbot ingress service#665
neokry wants to merge 4 commits into
paradigmxyz:mainfrom
neokry:codex/teamsbot-ingress

Conversation

@neokry

@neokry neokry commented Jun 18, 2026

Copy link
Copy Markdown

Summary

Adds a Microsoft Teams ingress service at parity with the existing Slack and Discord services. The Teamsbot receives Bot Framework activities, gates them by Teams/team/channel/tenant policy, forwards session messages to api-rs, renders streamed session output back to Teams, and persists render obligations for recovery after crashes or delivery failures.

Also adds shared session stream parsing in @centaur/api-client so Teams, Slack, and Discord consume session events consistently, plus Teams principal derivation for iron-control permissioning.

What Changed

  • Added services/teamsbot with Teams SDK ingress, reply sinks, attachment hydration, thread/context serialization, Postgres-backed state, render recovery, local simulation, tests, Dockerfile, and README.
  • Added shared session transport helpers in packages/api-client and moved Slack/Discord SSE parsing onto the shared parser.
  • Added Teams principal derivation for channel, group chat, and personal chat contexts.
  • Added Helm chart, network policy, ingress, bootstrap-secret, image publishing, Justfile, CI, and docs wiring.

Validation

  • pnpm --filter teamsbot check:types
  • bun test test/teamsbot.test.ts
  • Local Teamsbot /ready returned 200 while running through the dev server.

Notes

  • Teamsbot is disabled by default in chart values and requires Teams Bot Framework credentials plus allowlist configuration before it will respond.

@neokry neokry changed the title [codex] add teamsbot ingress service [codex] Add teamsbot ingress service Jun 18, 2026
@neokry neokry force-pushed the codex/teamsbot-ingress branch from 6f537f8 to f0067cf Compare June 18, 2026 18:41
@goksu

goksu commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

@neokry Thank you for the diff! Was this tested with an actual Teams production or deployed to somewhere? Since internally we don't use Teams we would need to get a full E2E validation and a champion to carry this integration moving forward.

@neokry

neokry commented Jun 19, 2026

Copy link
Copy Markdown
Author

@neokry Thank you for the diff! Was this tested with an actual Teams production or deployed to somewhere? Since internally we don't use Teams we would need to get a full E2E validation and a champion to carry this integration moving forward.

Hey no problem! Yes I have tested with my own teams and have it working. I could send a screen recording or add you to the teams deployment for a test. What do you think is the best path to move foward? If screen recording works what would you need to see included for full e2e verification?

@goksu

goksu commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator

@neokry Thank you for the diff! Was this tested with an actual Teams production or deployed to somewhere? Since internally we don't use Teams we would need to get a full E2E validation and a champion to carry this integration moving forward.

Hey no problem! Yes I have tested with my own teams and have it working. I could send a screen recording or add you to the teams deployment for a test. What do you think is the best path to move foward? If screen recording works what would you need to see included for full e2e verification?

Sure! Some videos of the critical flows like triggering a model answer by tagging the bot would be sufficient. I'll review the rest in the meantime and may have comments.

@goksu goksu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Architecture concern on packages/api-client/src/session-transport.ts.

@@ -0,0 +1,318 @@
export type FetchLike = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a full new module introducing some sort of "shared" layer between renderers. Although in idea this may seem like a "code-cleanup" it actually likely will regress Slack and Discord bots and how they handle rendering.

On Centaur architecture we are trying to approach rendering with 3 layers:

  • Common Rust API / codex transport server: fully typechecked
  • Rendering layer converting server types to events that can be consumed by clients
  • Each client building its own session handling (Slackbot, Discordbot)

We should instead of trying to do this just build any specific handler needs into Teamsbot.

for (let attempt = 0; ; attempt += 1) {
const deferredCount = await teamsbot.recoverRenderObligations();
if (deferredCount === 0) {
return;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This recovery loop exits permanently once a scan has no deferred obligations. If a live Teams render later stores a new renderObligation, nothing schedules another pass, so recovery can be stranded until pod restart. This should be periodic/long-running or rescheduled when an obligation is indexed.

@neokry

neokry commented Jun 22, 2026

Copy link
Copy Markdown
Author

Thanks for reviewing the changes @goksu I made some new commits that should handle those issues you brought up. Here is a video of teamsbot usage also:

Screen.Recording.2026-06-22.at.11.43.52.AM.mov

@goksu goksu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the updates @neokry! We are getting close!

Have you looked into integrating the existing Chat SDK interface instead of going with an interface that is "hand-rolled" for Teams here? Was there any blockers you ran into? If not it might actually drop the number of lines in this diff significantly and allow a better chance to review and keep up to date in the future.

| Discord channel | `discord-channel-<guild-id>-<channel-id>` |
| Teams user | `msteams-user-<tenant-id>-<user-id>` |
| Teams channel | `msteams-channel-<tenant-id>-<team-id>-<conversation-id>` |
| Teams chat fallback | `msteams-chat-<tenant-id>-<conversation-id>` |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems odd to be having a fallback selector here? What is the use case?

falls back to the requesting user's grants. DMs and one-person runs normally use
the user principal directly.
Channel grants are shared by everyone in that channel or conversation. DMs,
Teams personal chats, and one-person runs normally use the user principal

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This update specific to how Teams work seems unnecessary detail.

@@ -60,7 +63,7 @@ impl PrincipalRef {
/// rename.
pub fn derive_principal(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mslipper — If we proceed this change it seems like it will be the first instance of the proxy being extended for a different renderer. There are bunch of Slack specific functionality here. Have you thought about how to introduce multi-client to this surface? Would it be another layer above proxy that specifically handles client specific author / channel parsing or does this change here make sense for the time being?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants