feat(aicore): reactive credential reload and proactive secret watcher - #256
feat(aicore): reactive credential reload and proactive secret watcher#256tiagoek wants to merge 10 commits into
Conversation
…ature Address reviewer feedback on PR #256: 1. Remove reload_aicore_credentials() wrapper — inline set_aicore_config() directly in the except AuthenticationError blocks. The wrapper added a named function for a single call; inlining is simpler and clearer. 2. Remove transparent TLS feature (AICORE_TRANSPARENT_TLS env var, _is_transparent_tls(), conditional client_secret handling in set_aicore_config()). This feature is blocked on an upstream LiteLLM PR and is not needed for the credential rotation fix. Nicole flagged that it belongs in a future secrets-resolver refactor. Behavior unchanged: AuthenticationError still triggers set_aicore_config() + retry, completely transparent to callers.
Production validation — proactive credential reload (
|
| Pod | Image | Threads in PID 1 |
|---|---|---|
Pre-fix (6ad5b0854983) |
0.3.3-20260825... (no watcher call) |
14 |
Post-fix (1858f9060d95) |
0.3.3-20260825200836_fd446f2+1 |
15 |
The extra thread is aicore-secret-watcher (daemon, polls /etc/secrets/appfnd/aicore/aicore-instance/ mtime every 30 s). It will call set_aicore_config() on the next kubelet symlink-swap rotation without requiring a pod restart.
autonomous-documentation-org unit tests: 286 passed, 1 skipped — coverage 90.51%
E2E validation — reactive credential reload (live pod test)Environment: Kyma managed runtime, namespace Test script
Execution outputWhat this proves
|
…ature Address reviewer feedback on PR #256: 1. Remove reload_aicore_credentials() wrapper — inline set_aicore_config() directly in the except AuthenticationError blocks. The wrapper added a named function for a single call; inlining is simpler and clearer. 2. Remove transparent TLS feature (AICORE_TRANSPARENT_TLS env var, _is_transparent_tls(), conditional client_secret handling in set_aicore_config()). This feature is blocked on an upstream LiteLLM PR and is not needed for the credential rotation fix. Nicole flagged that it belongs in a future secrets-resolver refactor. Behavior unchanged: AuthenticationError still triggers set_aicore_config() + retry, completely transparent to callers.
439f8d8 to
ccadb06
Compare
Introduces two security improvements for AI Core credential handling: 1. Transparent TLS mode (AICORE_TRANSPARENT_TLS=true): when active, set_aicore_config() skips writing AICORE_CLIENT_SECRET to os.environ and removes any stale value. The infrastructure sidecar proxy adds the mTLS certificate transparently on the SDK's behalf — no secret material needed in the agent process. Addresses HASI2026203 / SEC-309 (credentials exposed as env vars with excessive scope). 2. Reactive credential reload on AuthenticationError: completion() and acompletion() now intercept litellm.AuthenticationError, re-read credentials from the mounted secret volume, and retry once. Covers client_secret rotation and mTLS certificate rotation (cert-manager updates the volume file; the next failed token refresh triggers the reload) without requiring a pod restart. Relates-to: AFSDK-4306
…ature Address reviewer feedback on PR #256: 1. Remove reload_aicore_credentials() wrapper — inline set_aicore_config() directly in the except AuthenticationError blocks. The wrapper added a named function for a single call; inlining is simpler and clearer. 2. Remove transparent TLS feature (AICORE_TRANSPARENT_TLS env var, _is_transparent_tls(), conditional client_secret handling in set_aicore_config()). This feature is blocked on an upstream LiteLLM PR and is not needed for the credential rotation fix. Nicole flagged that it belongs in a future secrets-resolver refactor. Behavior unchanged: AuthenticationError still triggers set_aicore_config() + retry, completely transparent to callers.
- Add watch_aicore_config() daemon thread that polls secret directory mtime every 30s; on change calls set_aicore_config() proactively before LiteLLM's cached OAuth token expires (avoids 401 entirely) - Add _get_secret_dir_mtime() helper — returns 0.0 on OSError so missing dirs are handled safely - Fix ruff format: add blank line after local imports inside except blocks in completion.py (sync and async paths) - Add test_aicore_watcher.py (10 cases) and test_credential_rotation_flow.py (7 cases) covering watcher unit behavior and the LiteLLM env-update contract
- Remove __wrapped__ introspection in test_credential_rotation_flow that caused ty call-non-callable error; watcher call is already verified via reloaded.wait() - Fix trailing blank lines in test_aicore.py (end-of-file-fixer) - Bump version 0.38.0 → 0.41.0 (new public API: watch_aicore_config)
ccadb06 to
10bfdc8
Compare
There was a problem hiding this comment.
Would be possible to add a unit test with lang graph?
Checking the template I don't see our completion or acompletion being used. Would be important to map if there is any in the agent itself that LoBs will have to do, or if it is just a simper version bump.
There was a problem hiding this comment.
LangGraph agents use ChatLiteLLM which calls litellm.completion directly and bypasses our completion() wrapper, so the reactive 401 handler wouldn't fire for them
To cover that gap I added patch_litellm_for_credential_rotation(), it wraps litellm.completion and litellm.acompletion at the module level so ALL callers (including ChatLiteLLM) get the same transparent reload on 401. Combined with watch_aicore_config(), LangGraph agents are fully covered
… agents
Agents using ChatLiteLLM (LangGraph) call litellm.completion directly,
bypassing the SDK completion() wrapper and its reactive 401 handler.
patch_litellm_for_credential_rotation() wraps litellm.completion/acompletion
at the module level so ALL callers get transparent credential reload on
AuthenticationError — including ChatLiteLLM — without any code changes to
the agent's LLM call patterns.
Recommended startup pattern for LangGraph agents:
set_aicore_config()
patch_litellm_for_credential_rotation() # reactive reload for ChatLiteLLM
watch_aicore_config() # proactive reload on rotation
Adds test_langgraph_compat.py (15 tests) covering: env sharing across callers,
reactive path scope documentation, watcher sufficiency, patch behaviour,
idempotency, and full startup pattern end-to-end.
Addresses review comment from thiagob on PR #256 re: LangGraph template compat.
…tellm_for_credential_rotation Address Betina's review comments on PR #256: both public API functions now emit telemetry via @record_metrics, consistent with set_aicore_config and all other SDK public functions. Adds AICORE_WATCH_CONFIG and AICORE_PATCH_LITELLM to the Operation enum.
Documents watch_aicore_config, patch_litellm_for_credential_rotation, and the recommended startup pattern for LangGraph/ChatLiteLLM agents.
…TCH_CONFIG and AICORE_PATCH_LITELLM

Description
Addresses credential rotation failures in long-running agent pods.
When BTP rotates a service binding, kubelet updates the mounted secret volume within ~2 minutes. However, agent pods are never restarted — LiteLLM's cached OAuth token (up to 12h lifetime) eventually expires and the refresh attempt fails because the process still holds the old
client_secret. This PR makes credential rotation transparent to agents without requiring pod restarts.1. Reactive credential reload on
AuthenticationErrorcompletion()andacompletion()interceptlitellm.AuthenticationError, reload credentials from the mounted secret volume viaset_aicore_config(), and retry the call once. If the retry also fails, the error propagates normally.2. Proactive secret watcher (
watch_aicore_config)A daemon thread polls the secret directory mtime every 60 seconds. On change (kubelet performs an atomic symlink swap on rotation), calls
set_aicore_config()proactively — before the cached OAuth token expires. Avoids the 401 entirely rather than recovering from it.Typical usage:
3. LangGraph / ChatLiteLLM compatibility (
patch_litellm_for_credential_rotation)LangGraph agents use
ChatLiteLLMwhich callslitellm.completiondirectly, bypassing ourcompletion()wrapper.patch_litellm_for_credential_rotation()wrapslitellm.completionandlitellm.acompletionat the module level so all callers get transparent reload on 401 — includingChatLiteLLM.Related Issues
Type of Change
How to Test
python -m pytest tests/aicore/unit/ -v # 111 passedKey test files:
tests/aicore/unit/test_aicore_watcher.py— proactive watcher (8 tests)tests/aicore/unit/test_credential_rotation_flow.py— env update contract (6 tests)tests/aicore/unit/test_completion.py— reactive reload (sync + async)tests/aicore/unit/test_langgraph_compat.py— LangGraph / ChatLiteLLM compatibility (15 tests)Checklist
Breaking Changes
None. All changes are additive:
watch_aicore_config()— new public function, opt-inpatch_litellm_for_credential_rotation()— new public function, opt-in, idempotentAuthenticationErrorincompletion()— same exception type propagates if retry also fails; no contract change