Skip to content

[agentserver-core] Add FoundryStateStore durable KV storage layer#47763

Open
shanmukha1200 wants to merge 1 commit into
Azure:mainfrom
shanmukha1200:foundry-state-store-core
Open

[agentserver-core] Add FoundryStateStore durable KV storage layer#47763
shanmukha1200 wants to merge 1 commit into
Azure:mainfrom
shanmukha1200:foundry-state-store-core

Conversation

@shanmukha1200

@shanmukha1200 shanmukha1200 commented Jun 30, 2026

Copy link
Copy Markdown
Member

Add azure.ai.agentserver.core.storage: protocol-neutral FoundryStorageClient (transport, endpoint, pipeline policies, error hierarchy) plus FoundryStateStore, a durable key-value store bound to one explicit store. FoundryStateStore.get_or_create(name, ...) is the primary entry point; store-level operations (get/update/delete) act on the bound store, and explicit item operations (create_item/set_item/get_item/delete_item/list_keys) act on items within it. On the wire, store and item names are base64url path-encoded: POST /storage/state_stores, GET|PATCH|DELETE /storage/state_stores/{name}, POST /storage/state_stores/{name}/items, GET|PUT|DELETE /storage/state_stores/{name}/items/{key}, and GET /storage/state_stores/{name}/items:keys for ordered paged key listing, with optional if_match optimistic concurrency. Adds azure-core dependency and tests.

Responses has its own storage providers idea is to make responses re-use this storage clients in a follow-up PR

Description

Please add an informative description that covers that changes made by the pull request and link all relevant issues.

If an SDK is being regenerated based on a new API spec, a link to the pull request containing these API spec changes should be included above.

All SDK Contribution checklist:

  • The pull request does not introduce [breaking changes]
  • CHANGELOG is updated for new features, bug fixes or other significant changes.
  • I have read the contribution guidelines.

General Guidelines and Best Practices

  • Title of the pull request is clear and informative.
  • There are a small number of commits, each of which have an informative message. This means that previously merged commits do not appear in the history of the PR. For more information on cleaning up the commits in your PR, see this page.

Testing Guidelines

  • Pull request includes test coverage for the included changes.

Copilot AI review requested due to automatic review settings June 30, 2026 05:45
@github-actions github-actions Bot added Community Contribution Community members are working on the issue customer-reported Issues that are reported by GitHub users external to the Azure organization. Hosted Agents sdk/agentserver/* labels Jun 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution @shanmukha1200! We will review the pull request and get back to you soon.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a new protocol-neutral storage layer (azure.ai.agentserver.core.storage) to azure-ai-agentserver-core. It introduces FoundryStorageClient (a base owning the AsyncPipelineClient, policy chain, and error handling) and FoundryStateStore, a generic durable key-value store over POST /storage/state:read|:write|:listKeys with namespace/key/value/tags, optional if_match optimistic concurrency, and ordered, paged list_keys. This generalizes the existing responses-package FoundryStorageProvider into the shared core so protocol packages can build resource-specific clients on top.

Changes:

  • New storage subpackage: client/transport, endpoint resolution, error hierarchy, pipeline policies, JSON helpers, and the FoundryStateStore KV store with its serializer.
  • Added azure-core>=1.30.0 dependency and a CHANGELOG 2.0.0b6 (Unreleased) entry.
  • Added unit tests for request construction and response handling.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
storage/__init__.py Public exports for the new storage package.
storage/_client.py FoundryStorageClient base: pipeline, policies, _send_storage_request.
storage/_endpoint.py FoundryStorageEndpoint resolution and versioned URL building.
storage/_errors.py Storage exception hierarchy incl. new FoundryStoragePreconditionError (412).
storage/_policies.py UA + per-retry logging policies with URL masking.
storage/_state.py FoundryStateStore read/write/list_keys/get/set/delete.
storage/_state_serializer.py Wire (de)serialization + StateItem/StateKey/KeyPage types.
storage/_json.py Small JSON parsing helper.
tests/test_foundry_state_store.py Unit tests for request/response behavior.
pyproject.toml Adds azure-core>=1.30.0 dependency.
CHANGELOG.md Adds 2.0.0b6 (Unreleased) feature entry (version not synced — see comment).

Comment thread sdk/agentserver/azure-ai-agentserver-core/CHANGELOG.md Outdated
Comment thread sdk/agentserver/azure-ai-agentserver-core/docs/state-store-guide.md Outdated
Comment thread sdk/agentserver/azure-ai-agentserver-core/docs/state-store-guide.md Outdated
shanmukha1200 added a commit to shanmukha1200/azure-sdk-for-python that referenced this pull request Jul 10, 2026
…e-storage spec (foundrysdk_specs#247)

Per the latest commit on coreai-microsoft/foundrysdk_specs#247
("rename route to /storage/state_stores, add store PATCH update, align
object descriptors"), the state-store REST path is /storage/state_stores/*
(snake_case with underscore), not /storage/statestores/*.

- _state.py: store path + create() now target state_stores.
- _policies.py: masked-logging allowlist updated to the new segment name.
- Updated docs/state-store-guide.md, README, and test URL assertions.
- Fixed the core CHANGELOG/README, which still described the earlier
  namespace-based design (pre-dating the PR Azure#47763 pull) instead of the
  current store-bound statestores-protocol API; also dropped an unused
  aiohttp dependency left over from that earlier design.

No functional change beyond the route rename -- the object-type descriptor
changes in the spec commit (state_store / state_store.item) are response-only
fields the SDK does not parse or assert on.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
shanmukha1200 added a commit to shanmukha1200/azure-sdk-for-python that referenced this pull request Jul 12, 2026
…te/get/update/delete

Per review feedback on PR Azure#47763 (constructor-then-create felt awkward) and
the guide-first workflow: reshape the store-admin surface to four verbs
instead of six, and stop threading the per-request delegated-user header
through the constructor.

- FoundryStateStore.get_or_create(name, ...) is now the sole entry point (an
  async classmethod): resolves the store in one call (fetch, or create on
  first use, refetching on a create/create race), replacing the previous
  constructor + separate create()/create_or_get()/get_or_create() dance.
- get(key=None) and delete(key=None, ...) are overloaded on whether a key is
  supplied: no key acts on the bound store itself (was get_properties /
  delete_store); a key acts on one item (unchanged item-level behavior).
  update(...) replaces update_metadata(...) (no collision, simple rename).
- Removed the constructor's user_id parameter. x-ms-user-id is a per-request
  delegation header, not a store-level setting -- it is now resolved
  dynamically, per call, from azure.ai.agentserver.core's existing
  request-scoped platform context (get_request_context().user_id), the same
  mechanism protocol hosts already populate from the inbound x-agent-user-id
  header. A single (possibly long-lived, reused) FoundryStateStore instance
  can now safely serve requests for different users.
- azure-ai-agentserver-activity's FoundryStorage updated to call the new
  classmethod for writes while keeping the plain constructor for reads/
  deletes (both already tolerate a not-yet-created store gracefully), so the
  "only create on first write" behavior is unchanged.
- Rewrote the core state-store tests, the sample, and the developer guide for
  the new shape; added a "Limits" section to the guide mirroring the spec's
  field-constraints tables (no equivalent guide exists yet for the
  resilient-task primitive to model this section after).

Verified: black, mypy, and pytest all clean for both packages (148 + 99
passed). Two mypy findings in _state.py (tags Union narrowing in update(),
**query kwargs in list_keys) are pre-existing, inherited verbatim from PR
Azure#47763's pulled code -- unrelated to this rename and left as-is.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
shanmukha1200 added a commit to shanmukha1200/azure-sdk-for-python that referenced this pull request Jul 12, 2026
…tate-store guide

Same fix as the sibling PR Azure#47763: the guide's code samples returned typed
objects (StateStore, StateStoreItem, etc.) but never imported or named the
types, so every example read as if get()/set()/list_keys() returned raw
dicts. Added a Typed Models section mapping each method to its return
model, and added explicit imports/type annotations to the Getting Started,
Store Lifecycle, Fetch-one-item, and Listing Keys examples.
StateStoreItem.value stays intentionally untyped (opaque application JSON)
-- called that out explicitly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread sdk/agentserver/azure-ai-agentserver-core/docs/state-store-guide.md
Comment thread sdk/agentserver/azure-ai-agentserver-core/docs/state-store-guide.md Outdated
Comment thread sdk/agentserver/azure-ai-agentserver-core/docs/state-store-guide.md Outdated
Comment thread sdk/agentserver/azure-ai-agentserver-core/docs/state-store-guide.md Outdated
shanmukha1200 added a commit to shanmukha1200/azure-sdk-for-python that referenced this pull request Jul 13, 2026
Address review feedback (PR Azure#47763): remove the key-vs-no-key @Overloads on
FoundryStateStore.get/delete and give every item operation an explicit,
consistently named method.

- set -> set_item
- get(key) -> get_item(key); bare get() stays store-scoped
- delete(key) -> delete_item(key); bare delete() stays store-scoped
- create_item / list_keys unchanged

Store-level get()/update()/delete() now unambiguously act on the bound store.
Updates README, state-store guide, samples, tests, and CHANGELOG to match.
No TypeSpec/generated-model changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
shanmukha1200 added a commit to shanmukha1200/azure-sdk-for-python that referenced this pull request Jul 14, 2026
…fied with Azure#47763)

Bring the PR Azure#47763 review fix (explicit, consistently named item operations)
into the unified activity+storage branch and update the activity bridge to
match.

Core (FoundryStateStore):
- remove key-vs-no-key @Overloads on get/delete
- set -> set_item, get(key) -> get_item, delete(key) -> delete_item
- bare get()/update()/delete() stay store-scoped
- create_item / list_keys unchanged
- keep unified's call-id identity model (x-agent-foundry-call-id); no
  x-ms-user-id/user_id delegation

Activity bridge (_foundry_storage.py):
- _read_item/_write_item/_delete_item now call get_item/set_item/delete_item
- update fake-store mocks + assertions in test_foundry_storage.py

Docs/samples/tests updated. No TypeSpec/generated-model changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@shanmukha1200
shanmukha1200 force-pushed the foundry-state-store-core branch from 5d04c21 to 3816b39 Compare July 15, 2026 07:22
store itself; the explicit `*_item` methods act on individual items within it.

```python
info: StateStore | None = await store.get() # the store's metadata, or None if absent

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggestion: Returning None on a .get() is a bit ambiguous here, is the store missing vs is the store metadata missing.

if the store exists, I'd expect store.get() to always return the store object/metadata instead of None.

If this needs to return a None a different method name like .get_info() could be better.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ok this is the sample customer code its not that we give None if the store doesn't exist we generally give a 404 but this is more of a guidance that we are giving to customer on how to use it

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I understand that the API returns 404 and I see that we capture that 404 and return a None

My concern is more around the method semantics than the transport behavior.

get() feels like a more natural fit as a static/factory-style API (e.g. FoundryStateStore.get(name)), where the resource may or may not exist.

Here we already have an FoundryStateStore instance bound to a particular store, with instance do we expect the API to ever return a 404 ?
if yes, we might have to rethink if .get() is the right name here.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes on the first draft of the pr the name was getStateStoreMetadata() it was changed because of the feedback once you say stateStoreMetadata you have to return a different typed object other than StateStore itself

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Just saw some old comments, I think ravi's comment is also along the same lines

#47763 (comment)

store = await FoundryStateStore.get(name); is what he mentioned in comments, which is a factory style object.
our current implementation does not match this.

Comment thread sdk/agentserver/azure-ai-agentserver-core/docs/state-store-guide.md
Comment thread sdk/agentserver/azure-ai-agentserver-core/docs/state-store-guide.md
Copilot AI review requested due to automatic review settings July 20, 2026 14:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 4 comments.

:return: The updated store descriptor.
:rtype: ~azure.ai.agentserver.core.storage.StateStore
"""
body = serialize_store_update_request(description, tags)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done. _state.py now imports a single shared _UNSET sentinel from _state_serializer, so omitted args are correctly dropped. Added regression tests for update(description=...), update(tags=...), and update().

Comment on lines +249 to +256
try:
await store._fetch_properties()
except FoundryStorageNotFoundError:
try:
await store._create_properties()
except FoundryStorageConflictError:
await store._fetch_properties()
return store

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done. get_or_create now wraps fetch/create/refetch in try/except BaseException and calls store.aclose() before re-raising, so the pipeline and any owned credential are closed on every failure (including cancellation). Added tests covering fetch and create failures.

StateStoreItemKey,
)

__all__ = [

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done. Regenerated and committed api.md and api.metadata.yml; they now include the full azure.ai.agentserver.core.storage surface.

Comment on lines +165 to +166
def _store_path(self) -> str:
return f"state_stores/{_encode_segment(self._name)}"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done. Updated the PR description to document the actual state_stores/{name}/items wire contract implemented here.

Add azure.ai.agentserver.core.storage: protocol-neutral FoundryStorageClient
(transport, endpoint, pipeline policies, error hierarchy) plus FoundryStateStore,
a durable key-value store bound to one explicit store. get_or_create(name, ...)
is the primary entry point; store-level get/update/delete act on the bound store,
and explicit create_item/set_item/get_item/delete_item/list_keys act on items.

Addresses PR review feedback:
- Share a single _UNSET sentinel between _state and _state_serializer so
  one-field update() calls no longer leak the sentinel into the payload.
- Close the store (pipeline + owned credential) on every get_or_create failure,
  including cancellation, so a failed resolution cannot leak resources.
- Bound-store get() returns StateStore and raises FoundryStorageNotFoundError
  instead of returning None; docs and tests updated to match.
- Regenerate api.md / api.metadata.yml to include the storage surface.
- Add regression tests for one-field updates and get_or_create cleanup.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 2c9dca80-df28-4b1e-8c61-3a69bf7e9a8a
Copilot AI review requested due to automatic review settings July 21, 2026 07:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated 7 comments.

@@ -1,205 +1,371 @@
```py
namespace azure.ai.agentserver.core
namespace azure.ai.agentserver.core.storage
"""Build a full storage API URL for *path* with ``api-version`` appended."""
url = f"{self.storage_base_url}{path}?api-version={_encode(self.api_version)}"
for key, value in extra_params.items():
url += f"&{key}={_encode(value)}"
policies.RequestIdPolicy(),
policies.HeadersPolicy(),
ua_policy,
policies.AsyncRetryPolicy(),
Comment on lines +139 to +141
# Endpoint and credential resolve from FOUNDRY_PROJECT_ENDPOINT + DefaultAzureCredential.
# get_or_create() resolves (or creates, on first use) the store in one call.
store = await FoundryStateStore.get_or_create("checkpoints/thread-abc", user_isolation=True)
Comment on lines +29 to +33
__all__ = [
"DEFAULT_ITEM_TTL_SECONDS",
"DeletedStateStore",
"DeletedStateStoreItem",
"FOUNDRY_TOKEN_SCOPE",
Comment on lines +93 to +94
async def __aenter__(self) -> "FoundryStorageClient":
return self
Comment on lines +55 to +61
if not (endpoint.startswith("http://") or endpoint.startswith("https://")):
raise ValueError(f"endpoint must be a valid absolute URL, got: {endpoint!r}")
normalized = endpoint.rstrip("/")
if normalized.endswith("/storage"):
storage_base_url = normalized + "/"
else:
storage_base_url = normalized + "/storage/"
@github-actions

Copy link
Copy Markdown
Contributor
[Pilot] PR Pipeline Failure Analysis

A CI pipeline failed on this pull request. Here is an automated analysis of what went wrong and how to get the build green.

What failed

Two distinct failure categories were detected in the azure-ai-agentserver-core CI pipeline (build #6592959):

  1. Test failures (all platforms): Three tests in TestFoundryEnrichmentSpanProcessor (tests/test_tracing.py) failed consistently across every platform and distribution (macOS 3.11, Ubuntu 3.10/3.12/3.13/3.14, Windows 3.12) — both wheel and sdist installs:

    • test_agent_attrs_present_on_exported_span
    • test_agent_attrs_survive_framework_overwrite
    • test_blueprint_id_uses_correct_attribute_key
  2. Validation failure (apistub): The APIView stub generation (apistub check) timed out — pip install of the built wheel (azure_ai_agentserver_core-2.0.0b8) exceeded the 120-second limit. This is likely an infrastructure/transient issue but could also indicate the wheel takes unusually long to install.

Recommended next steps

  • Fix the tracing test failures: Investigate sdk/agentserver/azure-ai-agentserver-core/tests/test_tracing.py — the TestFoundryEnrichmentSpanProcessor tests are failing across all platforms, suggesting a logic issue in the FoundryEnrichmentSpanProcessor implementation or test setup introduced by this PR. Run the tests locally to reproduce and fix the root cause.
  • Re-run for the apistub timeout: The pip install timeout during apistub may be transient. After fixing the test failures, re-queue the pipeline to see if apistub passes on retry. If it continues to time out, investigate whether the package has unusually heavy install-time dependencies.
  • See the CI troubleshooting guide: https://aka.ms/ci-fix
  • Push new commits to address the failures; this comment updates automatically on the next failing run.
Raw pipeline analysis (azsdk ci analyze)
Analyzing pipeline https://github.com/Azure/azure-sdk-for-python/pull/47763...
--------------------------------------------------------------------------------
Failed Tests
--------------------------------------------------------------------------------
{
  "macos311 - whl": [
    "sdk.agentserver.azure-ai-agentserver-core.tests.test_tracing.TestFoundryEnrichmentSpanProcessor.test_agent_attrs_present_on_exported_span",
    "sdk.agentserver.azure-ai-agentserver-core.tests.test_tracing.TestFoundryEnrichmentSpanProcessor.test_agent_attrs_survive_framework_overwrite",
    "sdk.agentserver.azure-ai-agentserver-core.tests.test_tracing.TestFoundryEnrichmentSpanProcessor.test_blueprint_id_uses_correct_attribute_key"
  ],
  "(same 3 tests repeated across all platforms: ubuntu2404_310, ubuntu2404_312, Ubuntu2404_313, Ubuntu2404_314, windows2022_312 - both whl and sdist)": []
}
--------------------------------------------------------------------------------
Failed Tasks
--------------------------------------------------------------------------------
apistub check on azure-ai-agentserver-core:
  subprocess.TimeoutExpired: Command 'pip install azure_ai_agentserver_core-2.0.0b8-py3-none-any.whl' timed out after 120 seconds
  apistub check completed with exit code 1

=== SUMMARY ===
/mnt/vss/_work/1/s/sdk/agentserver/azure-ai-agentserver-core  apistub  FAIL(1)  133.99s

### Pipeline: https://dev.azure.com/azure-sdk/public/_build/results?buildId=6592959

Copilot detected the failing pipeline and generated the analysis above. To have it attempt a fix automatically, reply with @copilot please fix the failing pipeline on this PR.

Generated by Pipeline Analysis - Next Steps · 33 AIC · ⌖ 8.8 AIC · ⊞ 6.6K ·

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

Labels

Community Contribution Community members are working on the issue customer-reported Issues that are reported by GitHub users external to the Azure organization. Hosted Agents sdk/agentserver/*

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants