Skip to content

Support per-client LLM configuration in a single powermem server instance #1018

@lightzt99

Description

@lightzt99

Problem

After PR #1017 is merged, a powermem server instance uses a single global .env file (~/.powermem/.env) for LLM configuration. All connected programming tools (Claude Code, Cursor, Codex, OpenCode, etc.) share the same provider, model, and API key.

Users often work with multiple tools simultaneously and want each tool to use a different LLM backend — for example, Claude Code via Anthropic, Cursor via OpenAI, and Codex via DeepSeek — while still sharing a single powermem server for memory storage.

Proposed Solution

Allow a powermem server instance to serve multiple clients, each with its own LLM configuration.

Registration Flow

Client                         Server
  │                              │
  │  POST /api/v1/instances      │
  │  {                           │
  │    "client": "claude-code",  │
  │    "provider": "anthropic",  │
  │    "model": "claude-sonnet-4-6",│
  │    "api_key": "sk-...",      │
  │    "base_url": ""            │
  │  }                           │
  │──────────────────────────────>│
  │                              │  ─ generates instance_id
  │  {"instance_id": "pmem-abc123"}│
  │<──────────────────────────────│
  │                              │
  │  (client saves instance_id   │
  │   to its plugin data dir)    │
  │                              │
  │  POST /api/v1/memories/search│
  │  X-PowerMem-Instance: pmem-abc123 │
  │  {"query": "..."}            │
  │──────────────────────────────>│  ─ looks up LLM config by instance_id
  │                              │  ─ uses anthropic/claude-sonnet-4-6
  │  search results              │
  │<──────────────────────────────│
  1. Client calls POST /api/v1/instances with its LLM config (provider + model + api_key + base_url).
  2. Server persists the config and returns an instance_id.
  3. Client stores the instance_id in its local data directory (e.g., ~/.powermem/instance.id for the Claude Code plugin).
  4. All subsequent requests include X-PowerMem-Instance header. Server uses the corresponding LLM config.
  5. Requests without the header fall back to the global .env config (backward compatible).

Instance ID storage per tool

Tool Instance ID location
Claude Code plugin ~/.powermem/instance.id
Cursor extension Extension settings / .cursor/mcp.json
Codex ~/.codex/powermem-instance.id
OpenCode ~/.config/opencode/powermem-instance.id
Generic MCP client Client-specific config file

Persistence (open for design)

Option A — SQLite:

CREATE TABLE instances (
    instance_id TEXT PRIMARY KEY,
    client TEXT NOT NULL DEFAULT '',
    provider TEXT NOT NULL,
    model TEXT NOT NULL,
    api_key TEXT NOT NULL,
    base_url TEXT DEFAULT '',
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

Pros: structured, queryable, no file-locking issues.

Option B — YAML (~/.powermem/instances.yaml):

instances:
  pmem-abc123:
    client: claude-code
    provider: anthropic
    model: claude-sonnet-4-6
    api_key: sk-ant-...
  pmem-def456:
    client: cursor
    provider: openai
    model: gpt-4o
    api_key: sk-...

Pros: human-editable, easy to inspect/backup.

Option C — Both: check YAML first, fall back to SQLite. YAML for manual management, SQLite for API-registered instances.

Management API (open for design)

Method Endpoint Description
POST /api/v1/instances Register a new instance
GET /api/v1/instances List all instances (api_key masked)
GET /api/v1/instances/{id} Get instance details
PUT /api/v1/instances/{id} Update instance config
DELETE /api/v1/instances/{id} Remove an instance

Registration endpoint (open for design)

Option A — Dedicated endpoint:

POST /api/v1/instances
{
  "client": "claude-code",
  "provider": "anthropic",
  "model": "claude-sonnet-4-6",
  "api_key": "sk-ant-...",
  "base_url": ""
}

Option B — Reuse init.sh flow: The existing plugin init script already creates .env. After PR #1017, it could also call POST /api/v1/instances to register and receive an instance_id, which it stores to ~/.powermem/instance.id.

Option C — First-request implicit registration: Client sends LLM config on the first request via headers, server auto-creates an instance and returns the instance_id in the response.

Security considerations

  • api_key should be stored encrypted or with restricted file permissions (0600).
  • instance_id is not a secret — it only maps to the config; the actual auth key is the api_key stored server-side.
  • List/show endpoints must mask api_key (e.g., sk-a…b12x).

Scope

  • In scope: LLM configuration per client instance (provider, model, api_key, base_url).
  • Out of scope (for now): Per-instance embedder config, per-instance vector store config, multi-tenancy/isolation of memory data.

Impact

  • src/server/main.py — parse X-PowerMem-Instance header, inject into request context
  • src/powermem/config_loader.py — load LLM config per instance
  • apps/claude-code-plugin/scripts/init.sh — register instance on init, persist instance_id
  • Storage layer — new table or config file for instance configs
  • .env behavior — unchanged, serves as the default/fallback

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions