feat: add per-turn state foundation (state scopes, container, loader) - #553
Open
Lily Du (lilyydu) wants to merge 6 commits into
Open
feat: add per-turn state foundation (state scopes, container, loader)#553Lily Du (lilyydu) wants to merge 6 commits into
Lily Du (lilyydu) wants to merge 6 commits into
Conversation
Lily Du (lilyydu)
force-pushed
the
lilyydu/01-state-foundation
branch
from
August 11, 2026 17:35
5b4d655 to
dc9c10d
Compare
Lily Du (lilyydu)
marked this pull request as ready for review
August 11, 2026 17:35
Contributor
There was a problem hiding this comment.
Pull request overview
Adds a new, standalone per-turn state subpackage (microsoft_teams.apps.state) that provides TurnState scopes (dirty tracking + sealing), a container for conversation/user scopes, and a loader that persists scopes through the shared Storage abstraction. This is positioned as the foundational state layer for the upcoming multi-connection OAuth stack, without wiring into dispatch yet.
Changes:
- Introduces
TurnState(MutableMapping) with sealing and dirty tracking. - Adds
TurnStateContainerandTurnStateLoader(+StateOptions) for load/save/delete overStorage. - Adds initial pytest coverage for state behavior and storage persistence semantics.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/apps/src/microsoft_teams/apps/state/turn_state.py | Implements TurnState mapping with sealing/dirty tracking. |
| packages/apps/src/microsoft_teams/apps/state/container.py | Adds TurnStateContainer for bundling scopes + identity and providing seal()/delete(). |
| packages/apps/src/microsoft_teams/apps/state/loader.py | Adds TurnStateLoader with key layout, JSON blob persistence, and TTL-on-load behavior. |
| packages/apps/src/microsoft_teams/apps/state/options.py | Introduces StateOptions configuration (storage, key prefix, ttl). |
| packages/apps/src/microsoft_teams/apps/state/init.py | Exposes the new state-layer public surface for microsoft_teams.apps.state. |
| packages/apps/tests/test_state.py | Adds unit tests covering TurnState semantics and loader round-trips/TTL handling. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Lily Du (lilyydu)
force-pushed
the
lilyydu/01-state-foundation
branch
from
August 13, 2026 20:21
eca75c1 to
bc16aa4
Compare
…sation_id/user_id Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…sation_id TurnStateContainer is on the public API surface, so its constructor signature is a compatibility contract. Make all fields keyword-only via @DataClass(kw_only=True) so fields can be reordered or added later without breaking callers, and drop the empty-string default on conversation_id (it is the storage key identity — a silent "" default would persist under a garbage key instead of failing loudly). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Lily Du (lilyydu)
force-pushed
the
lilyydu/01-state-foundation
branch
from
August 13, 2026 21:23
bc16aa4 to
85ec356
Compare
This was referenced Aug 13, 2026
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Mehak Bindra (MehakBindra)
approved these changes
Aug 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is PR 1 of 5 in the multi-connection OAuth stack. It lands the per-turn state layer on its own so the rest of the stack can build on it. It is purely additive — no existing files are modified and nothing is wired into dispatch yet (that's PR 2). The design mirrors the C#
Microsoft.Teams.Apps.Statepackage so the SDKs stay behaviorally aligned.New subpackage
microsoft_teams.apps.state(not yet re-exported from the publicappspackage — deliberate; PR 2 wiresctx.state/App(state=...)):Decomposition
TurnState— aMutableMapping[str, Any]scope with dirty tracking (loader only writes mutated scopes) and sealing (post-turn access raisesTurnStateSealedError).TurnStateContainer— bundles theconversationscope and optionaluserscope plus their identity; exposesseal()/delete().TurnStateLoader— load/save/delete over theStorageABC. Scopes persist as JSON strings ({"ts": <epoch>, "data": {...}}); empty-but-dirty scopes delete their key, clean scopes are skipped.StateOptions—storage,key_prefix(default"ts"),ttl.Parity with C#
Key layout matches exactly:
{prefix}:conv:{conversationId}/{prefix}:user:{conversationId}:{userId}, default prefix"ts".Same
TurnState/TurnStateContainersplit, dirty/seal semantics, empty-scope-deletes-key behavior.Intentional divergence: C# delegates expiry to
IDistributedCache; ourStorageABC has no native TTL, so blobs carry atsand the loader enforcesttlon load. No schema-version field (C# has none either).Values must be JSON-serializable (non-serializable raises
TypeErrorat save; documented onTurnState).Design Choices
MutableMappingsoTurnStatefeels like a normal dict to users while funneling every mutation through a single point, giving us dirty-tracking and sealing for free without overriding each method (which subclassingdictwould've required).