Skip to content

Add AgentCore memory search adapter, remove dead extensions#3

Open
adhajar-amzn wants to merge 18 commits into
mainfrom
agentcore-memory-search-adapter
Open

Add AgentCore memory search adapter, remove dead extensions#3
adhajar-amzn wants to merge 18 commits into
mainfrom
agentcore-memory-search-adapter

Conversation

@adhajar-amzn
Copy link
Copy Markdown

Summary

  • Add AgentCore-backed memory search adapter — replaces OC's built-in SQLite + embedding pipeline with RetrieveMemoryRecordsCommand, eliminating the need for a local embedding provider (OpenAI, Gemini, etc.) in the container
  • New memory-agentcore plugin (kind: "memory") registers memory_search/memory_get tools backed by AgentCoreMemoryManager, with fallback to OC built-in when config unavailable
  • Remove dead codeextensions/nova/ (migrated to gateway) and src/hyperion/ (migrated to extensions/hyperion/src/lib/)
  • Fix pre-existing type/lint errors — plugin-sdk import paths, mock type casts, null safety, cross-extension inventory baseline

Changes

New files

  • extensions/agentcore/src/memory-manager.tsAgentCoreMemoryManager implements MemorySearchManager
  • extensions/memory-agentcore/ — plugin entry, package.json, plugin manifest

Modified files

  • extensions/agentcore/src/runtime.ts — fix SDK types (BatchCreateMemoryRecordsCommand, searchCriteria.searchQuery, memoryRecordSummaries)
  • extensions/agentcore/src/service.ts — export getAgentCoreConfig() singleton
  • extensions/agentcore/src/config.ts — parse memoryId from SSM config
  • extensions/agentcore/src/types.ts — add memoryId to config type
  • extensions/hyperion/ — fix import paths (openclaw/plugin-sdk/core and /acpx)
  • config/openclaw.json5 — set plugins.slots.memory: "memory-agentcore"

Removed (dead code)

  • extensions/nova/ — 16 files, channel plugin migrated to gateway
  • src/hyperion/ — 12 files, runtime layer migrated to extensions/hyperion/src/lib/

Test plan

  • pnpm check passes (type check + all lint rules)
  • Verify memory_search tool returns results from AgentCore Memory in beta
  • Verify fallback to OC built-in when memoryId not configured

🤖 Generated with Claude Code

adhajar-amzn and others added 17 commits March 19, 2026 18:51
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WebSocket-based channel plugin for nova.amazon.com with credential
resolution, inbound message parsing, outbound delivery, onboarding
wizard, reconnect with exponential backoff, and heartbeat keepalive.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add required deviceId param to WebSocket URL (server returns 400 without it)
- Await sendNovaMessage() in deliver callback to propagate errors
- Block all messages when allowlist is empty instead of accepting all
- Store reconnect timer and clear on abort to prevent resource leaks
- Fix off-by-one in reconnect attempt logging vs delay calculation
- Use WebSocket.OPEN class constant consistently in send.ts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
core.config.resolveStorePath does not exist — use
core.channel.session.resolveStorePath(cfg.session?.store, { agentId })
matching the pattern used by Matrix and MSTeams extensions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python script that sends a message to the bot via the API Gateway
Management API and polls the EC2 session transcripts for the response.
Supports both direct WS and inject modes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Read NOVA_API_KEY from environment variable instead of embedding the
secret in source control.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The deliver callback was reading payload.parts (always empty) instead of
payload.text where the framework actually places the response. The
response frame also lacked a 'to' field, so the server-side Lambda had
no way to resolve the target user's connection. The test script could
only look up connections by connectionId (which changes on reconnect).

- monitor.ts: read payload.text in the deliver callback
- send.ts: include target userId ('to') in the response frame
- test_nova_ws.py: add --user-id flag for stable connection lookups

Also deployed to AWS (not in this repo):
- connect Lambda: stores userId from query string in DynamoDB
- message Lambda: forwards bot responses to the target user's WebSocket
  via the API Gateway Management API
- IAM: added execute-api:Invoke to the message Lambda role

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and unit tests

Multi-tenant DynamoDB integration layer (src/hyperion/):
- TenantConfigLoader: loads tenant_config + channel links + credentials, assembles OpenClawConfig
- HyperionDynamoDBClient: DDB operations for all 4 tables with multi-agent composite keys
- HyperionPairingStore: pairing code CRUD with 5-min TTL and retry logic
- SessionManager: session key parsing, tenant/agent ID extraction, memory namespacing
- UserCredentialStore: KMS envelope encryption for per-user API keys

AgentCore ACP extension (extensions/agentcore/):
- AgentCoreRuntime: implements OC AcpRuntime interface (ensureSession, runTurn, cancel, close)
- Pre-turn: loads tenant context + retrieves memory in parallel
- Post-turn: fires memory extraction job (fire-and-forget)
- Error classification: throttling, resource not found, service unavailable
- Config loader: SSM parameter-based configuration with local override support

Hyperion gateway plugin (extensions/hyperion/):
- Plugin entry that registers createHyperionPluginService()
- Creates AWS SDK clients, calls createHyperionRuntime(), stores as global singleton
- Stage resolution from plugin config / HYPERION_STAGE / STACK_NAME

Nova channel integration:
- monitor.ts loads per-tenant config via getHyperionRuntime() on each inbound message

Unit tests (158 tests across 6 files):
- Vitest 4.x compatible mocks (regular functions for constructors)
- Properly typed test helpers, zero as-any casts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
… pairing, tool key mapping

- Derive tenant ID from session key via extractTenantId() instead of agent
  name, preventing cross-tenant collisions when agents share names (P1)
- Consume pairing codes atomically with DynamoDB conditional delete to
  prevent double-redemption race conditions (P1)
- Map tool API keys to provider-specific paths (brave_search → search.apiKey,
  others → search.<provider>.apiKey) instead of overwriting a single key (P1)
- Pass gateway base config (ctx.config) into Hyperion runtime so tenant
  configs inherit global settings like ACP backend selection (P1)
- Add endpointOverride to AgentCore config that applies after SSM loading,
  so runtime ARNs are still discovered when testing with a local endpoint (P2)
- Fix env var restoration in credentials test to delete undefined keys
  instead of setting them to string "undefined"
- Update tests for all changes; all 99 tests pass

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
ALB forwards IDP user identity via Amz-Mons-Idp-Subject header.
No gateway token needed — ALB SG + WAF provide network-level access control.
Regenerate bundled provider auth env vars for amazon-nova extension.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…tion

src/hyperion/ lives in the root workspace but imports @aws-sdk/client-dynamodb,
@aws-sdk/client-kms, and @aws-sdk/lib-dynamodb. These were only declared in
extensions/hyperion/package.json and not hoisted, causing tsgo failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move hyperion lib code into extensions/hyperion/src/lib/ so the extension
doesn't depend on src/hyperion/ (which isn't copied to the Docker runtime
image). Update all cross-extension imports to use relative sibling paths
instead of reaching into src/.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When OPENCLAW_EXTENSIONS is set, remove all other extensions from the
runtime-assets stage. This prevents OOM from loading 40+ upstream
extensions that aren't needed in our deployment.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a message arrives and no ACP session exists for the session key,
auto-initialize one before dispatching. This ensures messages route to
the AgentCore backend instead of falling through to the embedded Pi
agent, which would try to call the model directly.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Keep all extensions in the runtime image. The OC config's plugins.allow
controls which extensions are loaded at runtime, so physical removal is
unnecessary. This fixes startup failures when the config references
extensions that were stripped (amazon-nova, memory-core).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add AgentCore-backed memory search adapter replacing OC's built-in
SQLite + embedding pipeline, eliminating the need for a local embedding
provider (OpenAI, Gemini, etc.) in the container.

New:
- AgentCoreMemoryManager implementing MemorySearchManager interface
- memory-agentcore plugin (kind: "memory") with memory_search/memory_get tools
- getAgentCoreConfig() singleton for cross-plugin config sharing

Fixed:
- runtime.ts SDK types (RetrieveMemoryRecordsCommand, BatchCreateMemoryRecordsCommand)
- hyperion extension imports (openclaw/plugin-sdk → openclaw/plugin-sdk/core and /acpx)
- runtime.test.ts mock casts (as unknown as ReturnType<...>)
- tenant-config-loader.ts null safety and unknown type narrowing
- memory-manager.ts inlines OC memory types (no src/ tree imports)
- Updated cross-extension import inventory baseline

Removed (migrated to extensions/hyperion/ and gateway):
- extensions/nova/ — channel plugin migrated to gateway
- src/hyperion/ — runtime layer migrated to extensions/hyperion/src/lib/

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comment thread extensions/agentcore/src/config.ts Outdated
}

let memoryNamespacePrefix = DEFAULT_MEMORY_NAMESPACE_PREFIX;
let memoryId: string | undefined;
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

why is this set to undefined

Addresses PR feedback: explain why memoryId starts as undefined
(populated from SSM config JSON, stays undefined if not configured).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@adhajar-amzn
Copy link
Copy Markdown
Author

Re: config.ts:63memoryId is declared before the try/catch so it's accessible in the return statement (line 82). If SSM memory-config JSON doesn't contain a memoryId (or parse fails), it stays undefined — correct since memoryId?: string is optional in AgentCoreRuntimeConfig. Added a clarifying comment in df3bc3a.

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.

1 participant