Skip to content

feat(agent-memory): add access_strategy and tenant params for multitenancy#222

Draft
cassiofariasmachado wants to merge 6 commits into
mainfrom
feat/enable-multitenancy-support-for-agent-memory
Draft

feat(agent-memory): add access_strategy and tenant params for multitenancy#222
cassiofariasmachado wants to merge 6 commits into
mainfrom
feat/enable-multitenancy-support-for-agent-memory

Conversation

@cassiofariasmachado

@cassiofariasmachado cassiofariasmachado commented Jul 9, 2026

Copy link
Copy Markdown
Member

Disclaimer: Do not include SAP-internal or customer-specific information in this PR (e.g. internal system URLs, customer names, tenant IDs, or confidential configurations). This is a public repository.

Description

Adds multitenancy support to the agent_memory module. Implements AFSDK-4011.

AccessStrategy controls which tenant token is used for every operation:

  • SUBSCRIBER (default) — fetches a subscriber-scoped OAuth2 token using the provided tenant subdomain. The subscriber token URL is derived by replacing the provider's identityzone in token_url. Raises AgentMemoryValidationError if no tenant is available.
  • PROVIDER — uses the provider token; no tenant required. ⚠️ Emits a logger.warning() on every call — no tenant isolation applies.

Two ways to configure:

  1. Client-level (set once at create_client(), inherited by all calls):
client = create_client(
    access_strategy=AccessStrategy.SUBSCRIBER,
    tenant="acme-corp",
)
client.list_memories(agent_id="x", invoker_id="u")  # no tenant= needed
  1. Per-call (explicit, overrides client default):
client.list_memories(
    agent_id="x", invoker_id="u",
    access_strategy=AccessStrategy.SUBSCRIBER,
    tenant="acme-corp",
)

Per-call params always override the client-level default.

Code review changes

  • SUBSCRIBER_ONLY / PROVIDER_ONLY renamed to SUBSCRIBER / PROVIDER for brevity
  • logger.warning() emitted whenever AccessStrategy.PROVIDER is resolved, making the no-isolation risk explicit at runtime

Breaking change: create_client() with no args raises AgentMemoryConfigError (default SUBSCRIBER requires tenant). Migration:

client = create_client(tenant="my-subdomain")
# or
client = create_client(access_strategy=AccessStrategy.PROVIDER)

Related Issue

Closes #

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Code refactoring
  • Dependency update

How to Test

  1. Unit tests: pytest tests/agent_memory/unit/ — 256 tests pass
  2. Integration tests (provider only): pytest tests/agent_memory/integration/ — 14 pass, 12 skip
  3. Integration tests with subscriber tenant: CLOUD_SDK_CFG_HANA_AGENT_MEMORY_DEFAULT_SUBSCRIBER_TENANT=<subdomain> pytest tests/agent_memory/integration/ — 26 pass

Checklist

  • I have read the Contributing Guidelines
  • I have verified that my changes solve the issue
  • I have added/updated automated tests to cover my changes
  • All tests pass locally
  • I have verified that my code follows the Code Guidelines
  • I have updated documentation (if applicable)
  • I have added type hints for all public APIs
  • My code does not contain sensitive information (credentials, tokens, etc.)
  • I have followed Conventional Commits for commit messages

Breaking Changes

What breaks: create_client() with no args raises AgentMemoryConfigError.

Migration:

# Option 1 — subscriber tenant
client = create_client(tenant="my-subdomain")

# Option 2 — provider only (no isolation)
client = create_client(access_strategy=AccessStrategy.PROVIDER)

Additional Notes

  • Token caching is per tenant subdomain — provider and subscriber tokens cached independently in HttpTransport._oauth_cache.
  • The identityzone field is parsed from the UAA JSON binding and stored in AgentMemoryConfig. It is used to derive the subscriber token URL via string replacement, following the pattern used in the destination module.
  • BDD integration tests use a single Given I use the configured subscriber tenant step to flip the strategy — all scenario step implementations are shared (no duplication).

| Value | Description |
| --------------------------- | ------------------------------------------------------------------ |
| `SUBSCRIBER_ONLY` (default) | Reads and writes against the subscriber tenant. Requires `tenant`. |
| `PROVIDER_ONLY` | Reads and writes against the provider tenant. No `tenant` needed. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think it might be useful to note that there is no tenant isolation in this case, just to make the consequences of enabling provider very clear.

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. I've updated the documentation to make it clear.

- create_client() and AgentMemoryClient.__init__ accept access_strategy
  (default SUBSCRIBER_ONLY) and tenant as constructor params
- _resolve_tenant is now an instance method; per-call params override
  instance defaults
- All method signatures change to Optional[AccessStrategy] = None
  (sentinel: use instance default)
- user-guide: add client-level configuration section and PROVIDER_ONLY
  isolation warning
- tests: update TestAccessStrategy and TestCreateClient for instance method
@cassiofariasmachado cassiofariasmachado force-pushed the feat/enable-multitenancy-support-for-agent-memory branch from 3282daf to db9ba9b Compare July 14, 2026 20:40
… warning

- SUBSCRIBER_ONLY → SUBSCRIBER, PROVIDER_ONLY → PROVIDER across all
  agent_memory source, tests, feature files, and user guide
- Add logger.warning() when AccessStrategy.PROVIDER is used, making
  the no-isolation behaviour explicit at runtime
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