Skip to content

feat: scaffold MemoryService for Agent Episodic Memory (URT migration)#1467

Open
mjnovice wants to merge 17 commits intomainfrom
feat/memory-service-scaffold
Open

feat: scaffold MemoryService for Agent Episodic Memory (URT migration)#1467
mjnovice wants to merge 17 commits intomainfrom
feat/memory-service-scaffold

Conversation

@mjnovice
Copy link
Copy Markdown
Contributor

@mjnovice mjnovice commented Mar 20, 2026

Summary

  • Rewrites MemoryService client in uipath-platform to match the actual ECS v2 swagger contract for episodic memories
  • Pydantic models aligned with VDBS codebase DTOs: EpisodicMemoryIndex, EpisodicMemoryField, EpisodicMemorySearchRequest/Result, MemoryMatch, etc.
  • Endpoints use /ecs_/v2/episodicmemories paths with GUID keys (not name-based)
  • Service supports: create, get, list (OData), delete_index, ingest, search, patch_memory, delete_memory (sync + async)
  • Registered as sdk.memory on the UiPath class
  • Follows existing patterns from ContextGroundingService (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-python will wire memory retrieval into the agent loop.

Spec: Memory 1 Pager | Full Spec

Key changes from initial scaffold

Aspect Before (scaffold) After (matches swagger)
Endpoint prefix /ecs_/memory /ecs_/v2/episodicmemories
Resource identifier name (string) key (GUID)
Field model fieldName/fieldValue keyPath: string[] + value
Search query with topK search with SearchSettings (mode, threshold, resultCount)
Ingest response None EpisodicMemoryIngestResponse with memory ID
Index model 3 fields 8 fields (memoriesCount, folderKey, isEncrypted, etc.)
Missing ops delete index, patch status Now implemented

Test plan

  • Verify models serialize/deserialize correctly with camelCase aliases
  • Verify service endpoints match ECS v2 swagger contract
  • Integration test with ECS episodic memory endpoints on alpha
  • Validate folder-scoped header propagation
  • Test OData query params on list endpoint

🤖 Generated with Claude Code

@github-actions github-actions bot added the test:uipath-langchain Triggers tests in the uipath-langchain-python repository label Mar 20, 2026
@mjnovice mjnovice marked this pull request as draft March 20, 2026 18:03
mjnovice and others added 3 commits March 23, 2026 14:55
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>
@mjnovice mjnovice force-pushed the feat/memory-service-scaffold branch from 8e9dc58 to 8b8ddef Compare March 23, 2026 21:55
mjnovice and others added 2 commits March 23, 2026 14:57
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@mjnovice mjnovice force-pushed the feat/memory-service-scaffold branch from 44bd0a8 to 239b787 Compare March 23, 2026 22:06
@mjnovice mjnovice marked this pull request as ready for review March 23, 2026 22:11
mjnovice and others added 9 commits March 27, 2026 12:04
…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>
Comment on lines +425 to +426
def _resolve_folder(self, folder_key: Optional[str]) -> Optional[str]:
return folder_key or self._folder_key
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.

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],
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.

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(
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.

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."""
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.

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,
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.

I am not too familiar with the apis but having an optional name here seems strange to me.

mjnovice and others added 2 commits March 27, 2026 13:56
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:uipath-langchain Triggers tests in the uipath-langchain-python repository

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants