feat: scaffold MemoryService for Agent Episodic Memory (URT migration)#1467
feat: scaffold MemoryService for Agent Episodic Memory (URT migration)#1467
Conversation
Add MemoryService client backed by ECS (/ecs_/memory/...) endpoints for Agent Episodic Memory. This enables dynamic few-shot retrieval where agents query past episodes at execution start and inject them as examples into the system prompt. New files: - memory.py: Pydantic models (MemoryField, MemoryItem, MemoryQueryRequest, etc.) - _memory_service.py: MemoryService with create, ingest, query, retrieve, delete, list - __init__.py: Module exports Also registers sdk.memory on the UiPath class. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8e9dc58 to
8b8ddef
Compare
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
44bd0a8 to
239b787
Compare
packages/uipath-platform/src/uipath/platform/memory/_memory_service.py
Outdated
Show resolved
Hide resolved
packages/uipath-platform/src/uipath/platform/memory/_memory_service.py
Outdated
Show resolved
Hide resolved
packages/uipath-platform/src/uipath/platform/memory/_memory_service.py
Outdated
Show resolved
Hide resolved
…tract The original scaffold was based on a different/older API spec. This rewrites models and endpoints to match the actual ECS v2 episodicmemories contract: - Paths: /ecs_/v2/episodicmemories (not /ecs_/memory) - Resources identified by GUID key (not name) - Field model uses keyPath[] + value (not fieldName/fieldValue) - Search replaces query, with structured SearchSettings and per-field settings - Ingest now returns EpisodicMemoryIngestResponse with memory ID - Added missing operations: delete_index, patch_memory (status active/inactive) - EpisodicMemoryIndex includes all server fields (memoriesCount, folderKey, etc.) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per PR review feedback: ingestion should go to LLMOps, which extracts
fields from traces/feedback before forwarding to ECS.
Architecture:
- Index CRUD (create/list/get/delete) → ECS /ecs_/v2/episodicmemories
- Ingest → LLMOps /llmops_/api/Agent/memory/{id}/ingest (feedbackId-based)
- Search → LLMOps /llmops_/api/Agent/memory/{id}/search (returns systemPromptInjection)
- Patch/delete items → LLMOps /llmops_/api/Memory/{id}/items/{itemId}
LLMOps search returns systemPromptInjection — the formatted string
ready to inject into the agent's system prompt for few-shot retrieval.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full lifecycle test: create index → create feedback → ingest via LLMOps → search → verify response shape → delete index. Tests are marked @pytest.mark.e2e and excluded from default runs. Run with: uv run pytest -m e2e -v (requires UIPATH_URL, UIPATH_ACCESS_TOKEN, UIPATH_FOLDER_KEY env vars). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- LLMOps endpoints use llmopstenant_ prefix (not llmops_) through the platform gateway — llmops_ returns 302, llmopstenant_ routes correctly - SearchField.settings is now required (default FieldSettings()) per LLMOps API contract which requires the settings field on each field - Fixed E2E test feedback endpoint to use llmopstenant_ prefix E2E results: 5 passed, 1 skipped (ingest skipped because synthetic trace/span IDs don't exist in LLMOps trace store — needs real agent trace for full lifecycle test) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Adds e2e-uipath-platform job to test-packages.yml - Runs on uipath-platform changes, Python 3.11, ubuntu-latest - Uses ALPHA_TEST_CLIENT_ID/CLIENT_SECRET for auth (same as integration tests) - Reads memory folder from UIPATH_MEMORY_FOLDER secret - E2E results are non-blocking in the test gate (informational) - Test supports both token-based (local) and client credentials (CI) auth Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
| def _resolve_folder(self, folder_key: Optional[str]) -> Optional[str]: | ||
| return folder_key or self._folder_key |
There was a problem hiding this comment.
I think we need a little bit more here to make sure it can resolve the folder key from the env var inside serverless. look here: https://github.com/UiPath/uipath-python/blob/main/packages/uipath-platform/src/uipath/platform/context_grounding/_context_grounding_service.py#L2115-L2124
| self, | ||
| name: str, | ||
| description: Optional[str], | ||
| is_encrypted: Optional[bool], |
There was a problem hiding this comment.
I don't think that encrypted memory spaces are enabled in prod yet. (not sure if they are in alpha). So do we want to include this already?
| return EpisodicMemoryIndex.model_validate(response) | ||
|
|
||
| @traced(name="memory_list", run_type="uipath") | ||
| def list( |
There was a problem hiding this comment.
NIT: not sure I like calling this list since the api doesn't have list in it. maybe get all? just a thought though
| is_encrypted: Optional[bool] = None, | ||
| folder_key: Optional[str] = None, | ||
| ) -> EpisodicMemoryIndex: | ||
| """Asynchronously create a new episodic memory index.""" |
There was a problem hiding this comment.
NIT: the doc string on a lot of the async methods are not complete. I would expect them to mostly match the sync methods
| self, | ||
| memory_space_id: str, | ||
| feedback_id: str, | ||
| memory_space_name: Optional[str] = None, |
There was a problem hiding this comment.
I am not too familiar with the apis but having an optional name here seems strange to me.
Per PR review: _resolve_folder now matches ContextGroundingService pattern — resolves folder_key from folder_path via FolderService when UIPATH_FOLDER_KEY is not set (serverless/robot environments only have UIPATH_FOLDER_PATH). MemoryService now takes a FolderService dependency, wired through the UiPath class. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Summary
MemoryServiceclient inuipath-platformto match the actual ECS v2 swagger contract for episodic memoriesEpisodicMemoryIndex,EpisodicMemoryField,EpisodicMemorySearchRequest/Result,MemoryMatch, etc./ecs_/v2/episodicmemoriespaths with GUID keys (not name-based)create,get,list(OData),delete_index,ingest,search,patch_memory,delete_memory(sync + async)sdk.memoryon theUiPathclassContextGroundingService(folder-scoped,@traced,header_folder)API Contract Reference
Context
Agent Memory allows agents to persist context across jobs using dynamic few-shot retrieval. At agent start, the system queries similar past "episodes" and injects them as examples into the system prompt. This is backed by the ECS service using SQL VECTOR type.
This is the SDK layer needed for URT migration of agent memory — currently an adoption blocker for URT-only customers. Companion changes in
uipath-agents-pythonwill wire memory retrieval into the agent loop.Spec: Memory 1 Pager | Full Spec
Key changes from initial scaffold
/ecs_/memory/ecs_/v2/episodicmemoriesname(string)key(GUID)fieldName/fieldValuekeyPath: string[]+valuequerywithtopKsearchwithSearchSettings(mode, threshold, resultCount)NoneEpisodicMemoryIngestResponsewith memory IDTest plan
🤖 Generated with Claude Code