How MemoryProxy Manages Multi-Tenant Routing Using spaceId in TencentDB-Agent-Memory

MemoryProxy isolates tenants by extracting a spaceId from the request URL, then uses this identifier to route traffic to dedicated backend pools, scope storage credentials, and namespace all persistence operations.

TencentDB-Agent-Memory is a memory-enabled agent framework designed for multi-tenant cloud deployments. The MemoryProxy component enforces tenant isolation by parsing a spaceId—representing a unique memory instance—from every incoming request path. This identifier propagates through the entire request lifecycle, binding to per-tenant resources, storage keys, and temporary security tokens.

Extracting spaceId from Incoming Requests

MemoryProxy implements deterministic routing by embedding the tenant identifier directly in the URL structure.

URL Path Conventions

All agent-facing endpoints follow the pattern /{agent}/{spaceId}/{operation}. For example, a chat completion request targeting the opencode agent for tenant abc123 would arrive as POST /opencode/abc123/v1/chat/completions. This placement ensures the spaceId is available at the edge before any backend logic executes, as documented in the repository's routing specification.

The extractSpaceIdFromPath Implementation

In MemoryProxy/src/workbuddyHandler.ts, the extractSpaceIdFromPath function parses the incoming request path using a strict regex pattern to isolate the tenant segment.

// MemoryProxy/src/workbuddyHandler.ts#L829
function extractSpaceIdFromPath(path: string): string | undefined {
  const match = path.match(/^\/[^/]+\/([^/]+)\/.*$/);
  return match ? match[1] : undefined;
}

If the path lacks a spaceId segment, the function returns undefined, triggering the fallback logic described in later sections.

Per-Tenant Backend Routing and Isolation

Once extracted, the spaceId drives connection pooling and downstream service binding.

Dedicated Backend Pools

The proxy maintains separate LRU caches for each tenant to prevent cross-contamination of connections. According to MemoryProxy/src/types.ts, the system defines a default limit of 100 backend connections per spaceId, configurable via the spaceBackendLimit parameter.

// MemoryProxy/src/types.ts#L102
export interface ProxyConfig {
  spaceId?: string;
  spaceBackendLimit?: number; // defaults to 100
}

This limits resource exhaustion by ensuring one tenant cannot monopolize the proxy's connection pool.

Context Propagation to MemoryCore

After extraction, the spaceId binds to the request context. At line 1118 in workbuddyHandler.ts, the proxy attaches the identifier to the metadataClient and passes it as serviceIdOverride when invoking MemoryCore operations. This ensures downstream services operate within the correct tenant boundary.

// MemoryProxy/src/workbuddyHandler.ts#L1118
const spaceId = extractSpaceIdFromPath(req.path) ?? "";
const metadataClient = getMetadataClient(config.coreSkill, spaceId, apiKey);
// Propagated to MemoryCore as serviceIdOverride

Storage Layer Tenant Isolation

The spaceId enforces data isolation at the persistence layer through namespacing and scoped credentials.

Namespaced Storage Keys

In MemoryProxy/src/storage/key-utils.ts, all object keys are prefixed with the tenant identifier. This prevents key collisions across tenants sharing the same underlying storage bucket.

// MemoryProxy/src/storage/key-utils.ts#L5
const objectKey = `${bucket}/${spaceId}/${userId}/${agentSource}/${sessionId}/metadata.json`;

This convention applies universally to COS (Cloud Object Storage) and Redis backends, ensuring logical separation even when physical hardware is shared.

Scoped STS Credentials

Temporary security tokens are restricted to tenant-specific paths. As defined in MemoryProxy/src/storage/cos-types.ts, STS credentials granted to the proxy are scoped to proxy_cache/{ttl|nottl}/{spaceId}/*, preventing tenants from accessing each other's cached data.

// MemoryProxy/src/storage/cos-types.ts#L57
export interface STSPolicy {
  resource: `proxy_cache/${'ttl' | 'nottl'}/${string}/*`; // string = spaceId
}

Skill Bridge and Downstream Integration

The skill bridge layer handles spaceId differently depending on the backend type. In MemoryProxy/src/skill/skill-bridge.ts (lines 46-87), Redis-backed instances ignore the spaceId segment because they utilize a single shared instance, while COS-backed storage strictly enforces the tenant path. The spaceId is passed as the first argument to bridge functions, allowing adaptive routing logic.

Administrative Cache Management

Operators can evict a specific tenant's resources without affecting others. The evictCosSpace function in MemoryProxy/src/storage/factory.ts (lines 222-226) clears a tenant's cached backend connections and associated STS tokens.

// MemoryProxy/src/storage/factory.ts#L222-L226
export async function evictCosSpace(spaceId: string): Promise<void> {
  await backendCache.delete(spaceId);
  await stsTokenCache.delete(spaceId);
}

If a request arrives without a spaceId, the proxy falls back to an empty string or the _default instance, ensuring backward compatibility while maintaining isolation for explicit tenants, as implemented in MemoryProxy/src/skill/kv-version-pin-repo.ts (lines 24-29).

Summary

  • URL Parsing: extractSpaceIdFromPath extracts tenant identifiers from /{agent}/{spaceId}/ patterns.
  • Connection Isolation: Per-tenant LRU pools limit each spaceId to 100 concurrent backends.
  • Context Binding: The spaceId propagates via metadataClient and serviceIdOverride to MemoryCore.
  • Storage Namespacing: COS keys and Redis prefixes include the spaceId to prevent cross-tenant data leaks.
  • Credential Scoping: STS tokens are restricted to proxy_cache/{type}/{spaceId}/* paths.
  • Administrative Control: evictCosSpace enables targeted cache clearing per tenant.

Frequently Asked Questions

How does MemoryProxy extract the spaceId from a request URL?

MemoryProxy uses the extractSpaceIdFromPath function in MemoryProxy/src/workbuddyHandler.ts. This utility applies the regex /^\/[^/]+\/([^/]+)\/.*$/ to capture the segment immediately following the agent name. If the URL structure is invalid or the segment is missing, the function returns undefined, triggering fallback logic.

What happens if a request does not include a spaceId?

When extractSpaceIdFromPath returns undefined, the proxy defaults to an empty string or the _default memory instance, as handled in MemoryProxy/src/skill/kv-version-pin-repo.ts. This ensures legacy clients without explicit tenant routing still function, though they operate in a shared default space rather than an isolated tenant environment.

How does spaceId isolation work at the storage layer?

At the storage layer, spaceId appears in every object key path (e.g., bucket/spaceId/userId/...) according to MemoryProxy/src/storage/key-utils.ts. Additionally, STS temporary credentials are scoped to specific spaceId prefixes, preventing tenants from listing or accessing objects belonging to other memory instances, even if they share the same physical bucket.

What is the default limit for per-tenant backend connections?

By default, each spaceId is limited to 100 concurrent backend connections, defined in MemoryProxy/src/types.ts. This per-tenant LRU cache prevents resource monopolization and ensures fair resource distribution across all tenants sharing the proxy instance.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →