diff --git a/hindsight-docs/docs-integrations/openclaw.md b/hindsight-docs/docs-integrations/openclaw.md index 5613a693..1e596656 100644 --- a/hindsight-docs/docs-integrations/openclaw.md +++ b/hindsight-docs/docs-integrations/openclaw.md @@ -14,36 +14,37 @@ This plugin integrates [hindsight-embed](https://vectorize.io/hindsight/cli), a ## Quick Start -**Step 1: Set up LLM for memory extraction** - -Choose one provider and set its API key: - -```bash -# Option A: OpenAI -export OPENAI_API_KEY="sk-your-key" - -# Option B: Anthropic -export ANTHROPIC_API_KEY="your-key" - -# Option C: Gemini -export GEMINI_API_KEY="your-key" - -# Option D: Groq -export GROQ_API_KEY="your-key" - -# Option E: Claude Code (no API key needed) -export HINDSIGHT_API_LLM_PROVIDER=claude-code - -# Option F: OpenAI Codex (no API key needed) -export HINDSIGHT_API_LLM_PROVIDER=openai-codex -``` - -**Step 2: Install the plugin** +**Step 1: Install the plugin** ```bash openclaw plugins install @vectorize-io/hindsight-openclaw ``` +**Step 2: Configure the LLM provider used for memory extraction** + +The plugin reads configuration from OpenClaw's plugin config — set it +non-interactively with `openclaw config set`: + +```bash +# Option A — OpenAI (set llmApiKey as a SecretRef so the value comes from +# the OPENAI_API_KEY environment variable at runtime instead of being +# stored in plaintext on disk) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source env --ref-provider default --ref-id OPENAI_API_KEY + +# Option B — Anthropic +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider anthropic +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source env --ref-provider default --ref-id ANTHROPIC_API_KEY + +# Option C — Claude Code (no API key needed) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider claude-code + +# Option D — OpenAI Codex (no API key needed) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai-codex +``` + **Step 3: Start OpenClaw** ```bash @@ -57,6 +58,8 @@ The plugin will automatically: **Important:** The LLM you configure above is **only for memory extraction** (background processing). Your main OpenClaw agent can use any model you configure separately. +**Migrating from 0.5.x?** See the [Migration from 0.5.x](#migration-from-05x) section below for the env-var → SecretRef mapping. + ## How It Works **Auto-Capture:** Every conversation is automatically stored after each turn. Facts, entities, and relationships are extracted in the background. @@ -94,9 +97,13 @@ Optional settings in `~/.openclaw/openclaw.json`: - `apiPort` - Port for the openclaw profile daemon (default: `9077`) - `daemonIdleTimeout` - Seconds before daemon shuts down from inactivity (default: `0` = never) - `embedVersion` - hindsight-embed version (default: `"latest"`) +- `llmProvider` - LLM provider for memory extraction (`openai`, `anthropic`, `gemini`, `groq`, `ollama`, `openai-codex`, `claude-code`). Required unless `hindsightApiUrl` is set. +- `llmModel` - LLM model used with `llmProvider` (provider default if omitted) +- `llmApiKey` - API key for the LLM provider. **Sensitive** — set via `openclaw config set ... --ref-source env --ref-id OPENAI_API_KEY` to reference an env var. +- `llmBaseUrl` - Optional base URL override for OpenAI-compatible providers (e.g. `https://openrouter.ai/api/v1`) - `bankMission` - Agent identity/purpose stored on the memory bank. Helps the memory engine understand context for better fact extraction during retain. Set once per bank on first use — not a recall prompt. - `dynamicBankId` - Enable per-context memory banks (default: `true`) -- `bankId` - Static bank ID used when `dynamicBankId` is `false`. Can also be set with `HINDSIGHT_BANK_ID`. +- `bankId` - Static bank ID used when `dynamicBankId` is `false`. - `bankIdPrefix` - Optional prefix for bank IDs (e.g. `"prod"` → `"prod-slack-C123"` or `"prod-shared-bank"`) - `dynamicBankGranularity` - Fields used to derive bank ID: `agent`, `channel`, `user`, `provider` (default: `["agent", "channel", "user"]`) - `excludeProviders` - Message providers to skip for recall/retain (e.g. `["slack"]`, `["telegram"]`, `["discord"]`) @@ -145,7 +152,7 @@ Available isolation fields: - `user` - The user interacting with the agent - `provider` - The message provider (e.g. Slack, Discord) -Use `bankIdPrefix` to namespace bank IDs across environments (e.g. `"prod"`, `"staging"`). Set `dynamicBankId` to `false` to use a single shared bank for all conversations. In static mode, the plugin uses `bankId`, then `HINDSIGHT_BANK_ID`, then the default `openclaw` bank name. +Use `bankIdPrefix` to namespace bank IDs across environments (e.g. `"prod"`, `"staging"`). Set `dynamicBankId` to `false` to use a single shared bank for all conversations. In static mode, the plugin uses `bankId` if set, otherwise the default `openclaw` bank name. ### Retention Controls @@ -172,38 +179,61 @@ By default, the plugin retains `user` and `assistant` messages after each turn. ### LLM Configuration -The plugin auto-detects your LLM provider from these environment variables: +Configure the memory-extraction LLM via OpenClaw's plugin config. API keys +should be stored as `SecretRef` values so they're resolved from env vars, +mounted files, or `exec`-style secret managers (Vault, etc.) at runtime +instead of sitting in plaintext on disk. -| Provider | Env Var | Notes | -|----------|---------|-------| -| OpenAI | `OPENAI_API_KEY` | | -| Anthropic | `ANTHROPIC_API_KEY` | | -| Gemini | `GEMINI_API_KEY` | | -| Groq | `GROQ_API_KEY` | | -| Claude Code | `HINDSIGHT_API_LLM_PROVIDER=claude-code` | No API key needed | -| OpenAI Codex | `HINDSIGHT_API_LLM_PROVIDER=openai-codex` | No API key needed | +| Provider | `llmProvider` | API key | +|---|---|---| +| OpenAI | `openai` | required | +| Anthropic | `anthropic` | required | +| Gemini | `gemini` | required | +| Groq | `groq` | required | +| Ollama | `ollama` | not required (local) | +| Claude Code | `claude-code` | not required (uses Claude Code CLI) | +| OpenAI Codex | `openai-codex` | not required (uses Codex CLI auth) | -The model is selected automatically by the Hindsight API. To override, set `HINDSIGHT_API_LLM_MODEL`. - -**Override with explicit config:** +**Set provider + API key:** ```bash -export HINDSIGHT_API_LLM_PROVIDER=openai -export HINDSIGHT_API_LLM_API_KEY=sk-your-key - -# Optional: custom base URL (OpenRouter, Azure, vLLM, etc.) -export HINDSIGHT_API_LLM_BASE_URL=https://openrouter.ai/api/v1 +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source env --ref-provider default --ref-id OPENAI_API_KEY ``` -**Example: Free OpenRouter model** +**Override the model (optional — Hindsight picks a sensible default per provider):** ```bash -export HINDSIGHT_API_LLM_PROVIDER=openai -export HINDSIGHT_API_LLM_MODEL=xiaomi/mimo-v2-flash # FREE! -export HINDSIGHT_API_LLM_API_KEY=sk-or-v1-your-openrouter-key -export HINDSIGHT_API_LLM_BASE_URL=https://openrouter.ai/api/v1 +openclaw config set plugins.entries.hindsight-openclaw.config.llmModel gpt-4o-mini ``` +**OpenAI-compatible providers (OpenRouter, Azure OpenAI, vLLM, ...):** + +```bash +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai +openclaw config set plugins.entries.hindsight-openclaw.config.llmBaseUrl https://openrouter.ai/api/v1 +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source env --ref-provider default --ref-id OPENROUTER_API_KEY +openclaw config set plugins.entries.hindsight-openclaw.config.llmModel xiaomi/mimo-v2-flash +``` + +**Use a file or exec source instead of env (for K8s secrets, Vault, etc.):** + +```bash +# File source (e.g. mounted Docker/K8s secret) +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source file --ref-provider mounted-json --ref-id /providers/openai/apiKey + +# Exec source (e.g. HashiCorp Vault) +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source exec --ref-provider vault --ref-id openai/api-key +``` + +The corresponding secret provider needs to be configured under `secrets.providers` +in your OpenClaw config — see `openclaw config set --help` for the +`--provider-source`/`--provider-path`/`--provider-command` builder flags. + ### External API (Advanced) Connect to a remote Hindsight API server instead of running a local daemon. This is useful for: @@ -234,20 +264,12 @@ Configure in `~/.openclaw/openclaw.json`: **Options:** - `hindsightApiUrl` - Full URL to external Hindsight API (e.g., `https://mcp.hindsight.example.com`) -- `hindsightApiToken` - API token for authentication (optional, only if API requires auth) +- `hindsightApiToken` - API token for authentication (optional). **Sensitive** — set as a SecretRef: -#### Environment Variables (Alternative) - -You can also configure via environment variables: - -```bash -export HINDSIGHT_EMBED_API_URL=https://your-hindsight-server.com -export HINDSIGHT_EMBED_API_TOKEN=your-api-token # Optional - -openclaw gateway -``` - -**Note:** Plugin config takes precedence over environment variables. + ```bash + openclaw config set plugins.entries.hindsight-openclaw.config.hindsightApiToken \ + --ref-source env --ref-provider default --ref-id HINDSIGHT_API_TOKEN + ``` #### Behavior @@ -336,27 +358,36 @@ uvx hindsight-embed@latest profile list ### No API key error -Make sure you've set one of the provider API keys (or use a provider that doesn't require one): +Make sure you've configured the LLM provider through `openclaw config set` +(or use a provider that doesn't require a key): ```bash -# Option 1: OpenAI -export OPENAI_API_KEY="sk-your-key" +# Option 1 — OpenAI (requires OPENAI_API_KEY in your env) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source env --ref-provider default --ref-id OPENAI_API_KEY -# Option 2: Anthropic -export ANTHROPIC_API_KEY="your-key" +# Option 2 — Anthropic +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider anthropic +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source env --ref-provider default --ref-id ANTHROPIC_API_KEY -# Option 3: Claude Code (no API key needed) -export HINDSIGHT_API_LLM_PROVIDER=claude-code +# Option 3 — Claude Code (no API key needed) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider claude-code -# Option 4: OpenAI Codex (no API key needed) -export HINDSIGHT_API_LLM_PROVIDER=openai-codex +# Option 4 — OpenAI Codex (no API key needed) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai-codex -# Verify it's set -echo $OPENAI_API_KEY -# or -echo $HINDSIGHT_API_LLM_PROVIDER +# Verify the config is valid +openclaw config validate + +# Inspect the current value +openclaw config get plugins.entries.hindsight-openclaw.config.llmProvider ``` +If you used `--ref-source env`, double-check that the referenced env var +(e.g. `OPENAI_API_KEY`) is exported in the shell that runs `openclaw gateway`. + ### Verify it's working Check gateway logs for memory operations: @@ -373,3 +404,29 @@ tail -f /tmp/openclaw/openclaw-*.log | grep Hindsight # [Hindsight] Retained X messages for session ... # [Hindsight] Auto-recall: Injecting X memories ``` + +## Migration from 0.5.x + +0.6.0 removes all process-environment reads from the plugin. Configuration that +previously came from shell env vars must now go through OpenClaw's plugin config +(with `SecretRef` for credentials). The plugin no longer auto-detects providers +from `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / etc. — you must set +`llmProvider` explicitly. + +| Old (0.5.x) | New (0.6.0) | +|---|---| +| `OPENAI_API_KEY=…` (auto-detected) | `openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai`
`openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey --ref-source env --ref-id OPENAI_API_KEY` | +| `HINDSIGHT_API_LLM_PROVIDER=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider …` | +| `HINDSIGHT_API_LLM_MODEL=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.llmModel …` | +| `HINDSIGHT_API_LLM_API_KEY=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey --ref-source env --ref-id …` | +| `HINDSIGHT_API_LLM_BASE_URL=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.llmBaseUrl …` | +| `HINDSIGHT_EMBED_API_URL=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.hindsightApiUrl …` | +| `HINDSIGHT_EMBED_API_TOKEN=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.hindsightApiToken --ref-source env --ref-id …` | +| `HINDSIGHT_BANK_ID=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.bankId …` | +| `llmApiKeyEnv: "MY_KEY"` (plugin config) | `llmApiKey` configured as a SecretRef with `--ref-id MY_KEY` | + +If your shell already exports `OPENAI_API_KEY`, the SecretRef config above +resolves to the same value at startup — you don't need to change your shell +setup, just point the plugin at the variable explicitly. Run +`openclaw config validate` after migrating to confirm the new shape parses +cleanly. diff --git a/hindsight-docs/src/pages/changelog/integrations/openclaw.md b/hindsight-docs/src/pages/changelog/integrations/openclaw.md index 85ae0fbf..4915c2bc 100644 --- a/hindsight-docs/src/pages/changelog/integrations/openclaw.md +++ b/hindsight-docs/src/pages/changelog/integrations/openclaw.md @@ -8,6 +8,21 @@ import PageHero from '@site/src/components/PageHero'; [← OpenClaw integration](/sdks/integrations/openclaw) +## 0.6.0 (Unreleased) + +**Breaking Changes** + +- The plugin no longer reads any configuration from process environment variables. All settings — including the LLM provider, model, API key, base URL, external Hindsight API URL/token, and bank ID — must now be set through OpenClaw's plugin config (e.g. `openclaw config set plugins.entries.hindsight-openclaw.config. `). API keys and other secrets should be configured as `SecretRef` values via `--ref-source env|file|exec` so they're resolved from your secret store at runtime instead of being stored in plaintext on disk. +- Removed the `llmApiKeyEnv` plugin config field. Use the new `llmApiKey` field configured as a SecretRef instead (e.g. `openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey --ref-source env --ref-id OPENAI_API_KEY`). +- Removed automatic LLM provider detection from `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` / `GROQ_API_KEY`. Set `llmProvider` and `llmApiKey` explicitly via `openclaw config set`. +- Removed support for the `HINDSIGHT_API_LLM_PROVIDER`, `HINDSIGHT_API_LLM_MODEL`, `HINDSIGHT_API_LLM_API_KEY`, `HINDSIGHT_API_LLM_BASE_URL`, `HINDSIGHT_EMBED_API_URL`, `HINDSIGHT_EMBED_API_TOKEN`, and `HINDSIGHT_BANK_ID` environment variables. The same values now live in plugin config — see the [migration guide](/sdks/integrations/openclaw#migration-from-05x). + +**Features** + +- Added the `llmApiKey` plugin config field, marked as a sensitive field so OpenClaw resolves it as a `SecretRef` from env, file, or exec sources. +- Added the `llmBaseUrl` plugin config field for OpenAI-compatible endpoint overrides (OpenRouter, Azure OpenAI, vLLM, etc.). +- Marked `hindsightApiToken` as a sensitive field — it can now be configured as a `SecretRef` the same way as `llmApiKey`. + ## [0.5.1](https://github.com/vectorize-io/hindsight/tree/integrations/openclaw/v0.5.1) **Bug Fixes** diff --git a/hindsight-integrations/openclaw/README.md b/hindsight-integrations/openclaw/README.md index 564b45b4..d7c3f9cb 100644 --- a/hindsight-integrations/openclaw/README.md +++ b/hindsight-integrations/openclaw/README.md @@ -5,25 +5,56 @@ Biomimetic long-term memory for [OpenClaw](https://openclaw.ai) using [Hindsight ## Quick Start ```bash -# 1. Configure your LLM provider for memory extraction -# Option A: OpenAI -export OPENAI_API_KEY="sk-your-key" - -# Option B: Claude Code (no API key needed) -export HINDSIGHT_API_LLM_PROVIDER=claude-code - -# Option C: OpenAI Codex (no API key needed) -export HINDSIGHT_API_LLM_PROVIDER=openai-codex - -# 2. Install and enable the plugin +# 1. Install the plugin openclaw plugins install @vectorize-io/hindsight-openclaw +# 2. Configure the LLM provider used for memory extraction. + +# Option A — OpenAI (or any OpenAI-compatible provider) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai +openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \ + --ref-source env --ref-provider default --ref-id OPENAI_API_KEY + +# Option B — Claude Code (no API key needed) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider claude-code + +# Option C — OpenAI Codex (no API key needed) +openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai-codex + # 3. Start OpenClaw openclaw gateway ``` That's it! The plugin will automatically start capturing and recalling memories. +`llmApiKey` is marked sensitive — `openclaw config set ... --ref-source env` writes a +SecretRef that resolves the value from your `OPENAI_API_KEY` environment variable at +runtime, so the key is never stored in plaintext on disk. `--ref-source file` and +`--ref-source exec` are also supported for mounted-secret and Vault-style setups. + +## Migrating from 0.5.x + +0.6.0 removes all process-environment reads from the plugin. Configuration that +previously came from shell env vars must now go through OpenClaw's plugin config +(with SecretRef for credentials). Concrete mappings: + +| Old (0.5.x) | New (0.6.0) | +|---|---| +| `OPENAI_API_KEY=…` (auto-detected) | `openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai`
`openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey --ref-source env --ref-id OPENAI_API_KEY` | +| `HINDSIGHT_API_LLM_PROVIDER=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider …` | +| `HINDSIGHT_API_LLM_MODEL=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.llmModel …` | +| `HINDSIGHT_API_LLM_API_KEY=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey --ref-source env --ref-id …` | +| `HINDSIGHT_API_LLM_BASE_URL=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.llmBaseUrl …` | +| `HINDSIGHT_EMBED_API_URL=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.hindsightApiUrl …` | +| `HINDSIGHT_EMBED_API_TOKEN=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.hindsightApiToken --ref-source env --ref-id …` | +| `HINDSIGHT_BANK_ID=…` | `openclaw config set plugins.entries.hindsight-openclaw.config.bankId …` | +| `llmApiKeyEnv: "MY_KEY"` (plugin config) | `llmApiKey` configured as a SecretRef with `--ref-id MY_KEY` | + +If your shell already exports `OPENAI_API_KEY`, the SecretRef config above resolves +to the same value at startup — no need to change your shell setup, just point the +plugin at the variable explicitly. Run `openclaw config validate` after migrating +to confirm the new shape parses cleanly. + ## Features - **Auto-capture** and **auto-recall** of memories each turn, injected into system prompt space so recalled memories stay out of the visible chat transcript @@ -43,11 +74,12 @@ Optional settings in `~/.openclaw/openclaw.json` under `plugins.entries.hindsigh | `embedVersion` | `"latest"` | hindsight-embed version | | `embedPackagePath` | — | Local path to `hindsight-embed` package for development | | `bankMission` | — | Agent identity/purpose stored on the memory bank. Helps the engine understand context for better fact extraction. Set once per bank — not a recall prompt. | -| `llmProvider` | auto-detect | LLM provider override for memory extraction (`openai`, `anthropic`, `gemini`, `groq`, `ollama`, `openai-codex`, `claude-code`) | -| `llmModel` | provider default | LLM model override used with `llmProvider` | -| `llmApiKeyEnv` | provider standard env var | Custom env var name for the provider API key | +| `llmProvider` | — | LLM provider for memory extraction (`openai`, `anthropic`, `gemini`, `groq`, `ollama`, `openai-codex`, `claude-code`). Required unless `hindsightApiUrl` is set. | +| `llmModel` | provider default | LLM model used with `llmProvider` | +| `llmApiKey` | — | API key for the LLM provider. **Sensitive** — set via `openclaw config set ... --ref-source env --ref-id OPENAI_API_KEY` to reference an env var (or `--ref-source file`/`exec` for mounted-secret/Vault sources). | +| `llmBaseUrl` | — | Optional base URL override for OpenAI-compatible providers (e.g. `https://openrouter.ai/api/v1`) | | `dynamicBankId` | `true` | Enable per-context memory banks | -| `bankId` | — | Static bank ID used when `dynamicBankId` is `false`. Can also be set with `HINDSIGHT_BANK_ID`. | +| `bankId` | — | Static bank ID used when `dynamicBankId` is `false`. | | `bankIdPrefix` | — | Prefix for bank IDs (e.g. `"prod"`) | | `retainTags` | `[]` | Tags applied to every retained document, useful for cross-agent/source labeling (e.g. `source_system:openclaw`, `agent:agentname`) | | `retainSource` | `"openclaw"` | `source` value written into retained document metadata | @@ -67,7 +99,7 @@ Optional settings in `~/.openclaw/openclaw.json` under `plugins.entries.hindsigh | `recallMaxQueryChars` | `800` | Maximum character length for the composed recall query before calling recall. | | `recallPromptPreamble` | built-in string | Prompt text placed above recalled memories in the injected `` system-context block. | | `hindsightApiUrl` | — | External Hindsight API URL (skips local daemon) | -| `hindsightApiToken` | — | Auth token for external API | +| `hindsightApiToken` | — | Auth token for external API. **Sensitive** — set via `openclaw config set ... --ref-source env --ref-id HINDSIGHT_API_TOKEN`. | | `ignoreSessionPatterns` | `[]` | Session key glob patterns to skip entirely — no recall, no retain (e.g. `["agent:*:cron:**"]`) | | `statelessSessionPatterns` | `[]` | Session key glob patterns for read-only sessions — retain is always skipped; recall is skipped when `skipStatelessSessions` is `true` (e.g. `["agent:*:subagent:**", "agent:*:heartbeat:**"]`) | | `skipStatelessSessions` | `true` | When `true`, sessions matching `statelessSessionPatterns` also skip recall. Set to `false` to allow recall but still skip retain. | diff --git a/hindsight-integrations/openclaw/openclaw.plugin.json b/hindsight-integrations/openclaw/openclaw.plugin.json index 33878bb1..9361acb2 100644 --- a/hindsight-integrations/openclaw/openclaw.plugin.json +++ b/hindsight-integrations/openclaw/openclaw.plugin.json @@ -2,6 +2,14 @@ "id": "hindsight-openclaw", "name": "Hindsight Memory", "kind": "memory", + "configContracts": { + "secretInputs": { + "paths": [ + { "path": "llmApiKey", "expected": "string" }, + { "path": "hindsightApiToken", "expected": "string" } + ] + } + }, "configSchema": { "type": "object", "properties": { @@ -27,7 +35,7 @@ }, "llmProvider": { "type": "string", - "description": "LLM provider for Hindsight memory (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama', 'openai-codex', 'claude-code'). Takes priority over auto-detection but not over HINDSIGHT_API_LLM_PROVIDER env var.", + "description": "LLM provider for Hindsight memory (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama', 'openai-codex', 'claude-code').", "enum": [ "openai", "anthropic", @@ -42,9 +50,13 @@ "type": "string", "description": "LLM model to use (e.g. 'gpt-4o-mini', 'claude-3-5-haiku-20241022'). Used with llmProvider." }, - "llmApiKeyEnv": { + "llmApiKey": { + "type": ["string", "object"], + "description": "API key for the LLM provider used by the Hindsight memory daemon. Set via 'openclaw config set ... --ref-source env --ref-id OPENAI_API_KEY' to reference an env var without storing plaintext." + }, + "llmBaseUrl": { "type": "string", - "description": "Name of the env var holding the API key (e.g. 'MY_CUSTOM_KEY'). If not set, uses the standard env var for the chosen provider." + "description": "Optional base URL override for OpenAI-compatible providers (e.g. 'https://openrouter.ai/api/v1')." }, "embedPackagePath": { "type": "string", @@ -60,7 +72,7 @@ "description": "External Hindsight API URL (e.g. 'https://mcp.hindsight.devcraft.team'). When set, skips local daemon and connects directly to this API." }, "hindsightApiToken": { - "type": "string", + "type": ["string", "object"], "description": "API token for external Hindsight API authentication. Required if the external API has authentication enabled." }, "dynamicBankId": { @@ -70,7 +82,7 @@ }, "bankId": { "type": "string", - "description": "Static bank ID used when dynamicBankId is false. Can also be provided via HINDSIGHT_BANK_ID." + "description": "Static bank ID used when dynamicBankId is false." }, "bankIdPrefix": { "type": "string", @@ -290,9 +302,14 @@ "label": "LLM Model", "placeholder": "e.g. gpt-4o-mini, claude-3-5-haiku-20241022" }, - "llmApiKeyEnv": { - "label": "API Key Env Var", - "placeholder": "e.g. MY_CUSTOM_API_KEY (optional)" + "llmApiKey": { + "label": "LLM API Key", + "placeholder": "API key for the chosen LLM provider", + "sensitive": true + }, + "llmBaseUrl": { + "label": "LLM Base URL", + "placeholder": "e.g. https://openrouter.ai/api/v1 (optional)" }, "embedPackagePath": { "label": "Local Package Path (Dev)", @@ -308,7 +325,8 @@ }, "hindsightApiToken": { "label": "External API Token", - "placeholder": "API token if external API requires authentication" + "placeholder": "API token if external API requires authentication", + "sensitive": true }, "dynamicBankId": { "label": "Dynamic Bank IDs", diff --git a/hindsight-integrations/openclaw/package.json b/hindsight-integrations/openclaw/package.json index 6aa2c241..e8b05e60 100644 --- a/hindsight-integrations/openclaw/package.json +++ b/hindsight-integrations/openclaw/package.json @@ -44,8 +44,8 @@ "prepublishOnly": "npm run clean && npm run build" }, "dependencies": { - "@vectorize-io/hindsight-client": "file:../../hindsight-clients/typescript", - "@vectorize-io/hindsight-all": "file:../../hindsight-all-npm" + "@vectorize-io/hindsight-client": "^0.5.0", + "@vectorize-io/hindsight-all": "^0.1.0" }, "devDependencies": { "@types/node": "^20.0.0", diff --git a/hindsight-integrations/openclaw/src/backfill.ts b/hindsight-integrations/openclaw/src/backfill.ts index 6bdf2cce..0ccfe16e 100644 --- a/hindsight-integrations/openclaw/src/backfill.ts +++ b/hindsight-integrations/openclaw/src/backfill.ts @@ -173,12 +173,9 @@ function parseArgs(argv: string[]): ParsedArgs { function inferApiSettings(pluginConfig: PluginConfig, explicitApiUrl?: string, explicitApiToken?: string): { apiUrl: string; apiToken?: string } { const apiUrl = explicitApiUrl - || process.env.HINDSIGHT_EMBED_API_URL || pluginConfig.hindsightApiUrl || `http://127.0.0.1:${pluginConfig.apiPort || 9077}`; - const apiToken = explicitApiToken - || process.env.HINDSIGHT_EMBED_API_TOKEN - || pluginConfig.hindsightApiToken; + const apiToken = explicitApiToken || pluginConfig.hindsightApiToken; return { apiUrl, apiToken: apiToken || undefined }; } diff --git a/hindsight-integrations/openclaw/src/index.ts b/hindsight-integrations/openclaw/src/index.ts index 5f370f63..dc39f4b4 100644 --- a/hindsight-integrations/openclaw/src/index.ts +++ b/hindsight-integrations/openclaw/src/index.ts @@ -245,12 +245,6 @@ async function lazyReinit(configOverride?: PluginConfig): Promise { try { await checkExternalApiHealth(externalApi.apiUrl, externalApi.apiToken); - // Health check passed — set up env vars and create client - process.env.HINDSIGHT_EMBED_API_URL = externalApi.apiUrl; - if (externalApi.apiToken) { - process.env.HINDSIGHT_EMBED_API_TOKEN = externalApi.apiToken; - } - const llmConfig = detectLLMConfig(config); clientOptions = buildClientOptions(llmConfig, config, externalApi); banksWithMissionSet.clear(); @@ -667,16 +661,8 @@ export function formatMemories(results: MemoryResult[]): string { } -// Provider detection from standard env vars -const PROVIDER_DETECTION = [ - { name: 'openai', keyEnv: 'OPENAI_API_KEY' }, - { name: 'anthropic', keyEnv: 'ANTHROPIC_API_KEY' }, - { name: 'gemini', keyEnv: 'GEMINI_API_KEY' }, - { name: 'groq', keyEnv: 'GROQ_API_KEY' }, - { name: 'ollama', keyEnv: '' }, - { name: 'openai-codex', keyEnv: '' }, - { name: 'claude-code', keyEnv: '' }, -]; +// Providers that authenticate via OAuth or run locally — no API key needed. +const NO_KEY_REQUIRED_PROVIDERS = new Set(['ollama', 'openai-codex', 'claude-code']); export function detectLLMConfig(pluginConfig?: PluginConfig): { provider?: string; @@ -685,88 +671,7 @@ export function detectLLMConfig(pluginConfig?: PluginConfig): { baseUrl?: string; source: string; } { - // Override values from HINDSIGHT_API_LLM_* env vars (highest priority) - const overrideProvider = process.env.HINDSIGHT_API_LLM_PROVIDER; - const overrideModel = process.env.HINDSIGHT_API_LLM_MODEL; - const overrideKey = process.env.HINDSIGHT_API_LLM_API_KEY; - const overrideBaseUrl = process.env.HINDSIGHT_API_LLM_BASE_URL; - - // Priority 1: If provider is explicitly set via env var, use that - if (overrideProvider) { - // Providers that don't require an API key (use OAuth or local models) - const noKeyRequired = ['ollama', 'openai-codex', 'claude-code']; - if (!overrideKey && !noKeyRequired.includes(overrideProvider)) { - throw new Error( - `HINDSIGHT_API_LLM_PROVIDER is set to "${overrideProvider}" but HINDSIGHT_API_LLM_API_KEY is not set.\n` + - `Please set: export HINDSIGHT_API_LLM_API_KEY=your-api-key` - ); - } - - return { - provider: overrideProvider, - apiKey: overrideKey || '', - model: overrideModel, - baseUrl: overrideBaseUrl, - source: 'HINDSIGHT_API_LLM_PROVIDER override', - }; - } - - // Priority 2: Plugin config llmProvider/llmModel - if (pluginConfig?.llmProvider) { - const providerInfo = PROVIDER_DETECTION.find(p => p.name === pluginConfig.llmProvider); - - // Resolve API key: llmApiKeyEnv > provider's standard keyEnv - let apiKey = ''; - if (pluginConfig.llmApiKeyEnv) { - apiKey = process.env[pluginConfig.llmApiKeyEnv] || ''; - } else if (providerInfo?.keyEnv) { - apiKey = process.env[providerInfo.keyEnv] || ''; - } - - // Providers that don't require an API key (use OAuth or local models) - const noKeyRequired = ['ollama', 'openai-codex', 'claude-code']; - if (!apiKey && !noKeyRequired.includes(pluginConfig.llmProvider)) { - const keySource = pluginConfig.llmApiKeyEnv || providerInfo?.keyEnv || 'unknown'; - throw new Error( - `Plugin config llmProvider is set to "${pluginConfig.llmProvider}" but no API key found.\n` + - `Expected env var: ${keySource}\n` + - `Set the env var or use llmApiKeyEnv in plugin config to specify a custom env var name.` - ); - } - - return { - provider: pluginConfig.llmProvider, - apiKey, - model: pluginConfig.llmModel || overrideModel, - baseUrl: overrideBaseUrl, - source: 'plugin config', - }; - } - - // Priority 3: Auto-detect from standard provider env vars - for (const providerInfo of PROVIDER_DETECTION) { - const apiKey = providerInfo.keyEnv ? process.env[providerInfo.keyEnv] : ''; - - // Skip providers that don't use API keys in auto-detection (must be explicitly requested) - const noKeyRequired = ['ollama', 'openai-codex', 'claude-code']; - if (noKeyRequired.includes(providerInfo.name)) { - continue; - } - - if (apiKey) { - return { - provider: providerInfo.name, - apiKey, - model: overrideModel, - baseUrl: overrideBaseUrl, - source: `auto-detected from ${providerInfo.keyEnv}`, - }; - } - } - - // No configuration found - show helpful error - - // Allow empty LLM config if using external Hindsight API (server handles LLM) + // External API mode: the daemon handles LLM credentials, plugin doesn't need them. const externalApiCheck = detectExternalApi(pluginConfig); if (externalApiCheck.apiUrl) { return { @@ -778,37 +683,51 @@ export function detectLLMConfig(pluginConfig?: PluginConfig): { }; } - throw new Error( - `No LLM configuration found for Hindsight memory plugin.\n\n` + - `Option 1: Set a standard provider API key (auto-detect):\n` + - ` export OPENAI_API_KEY=sk-your-key\n` + - ` export ANTHROPIC_API_KEY=your-key\n` + - ` export GEMINI_API_KEY=your-key\n` + - ` export GROQ_API_KEY=your-key\n\n` + - `Option 2: Use Codex or Claude Code (no API key needed):\n` + - ` export HINDSIGHT_API_LLM_PROVIDER=openai-codex # Requires 'codex auth login'\n` + - ` export HINDSIGHT_API_LLM_PROVIDER=claude-code # Requires Claude Code CLI\n\n` + - `Option 3: Set llmProvider in openclaw.json plugin config:\n` + - ` "llmProvider": "openai"\n\n` + - `Option 4: Override with Hindsight-specific env vars:\n` + - ` export HINDSIGHT_API_LLM_PROVIDER=openai\n` + - ` export HINDSIGHT_API_LLM_API_KEY=sk-your-key\n` + - ` export HINDSIGHT_API_LLM_BASE_URL=https://openrouter.ai/api/v1 # Optional\n\n` + - `The model will be selected automatically by Hindsight. To override: export HINDSIGHT_API_LLM_MODEL=your-model` - ); + const provider = pluginConfig?.llmProvider; + if (!provider) { + throw new Error( + `No LLM provider configured for the Hindsight memory plugin.\n\n` + + `Set the provider via 'openclaw config set':\n` + + ` openclaw config set plugins.entries.hindsight-openclaw.config.llmProvider openai\n\n` + + `For providers that need an API key, configure it as a SecretRef so the value\n` + + `is read from an env var (or file/exec source) at runtime instead of stored in plain text:\n` + + ` openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \\\n` + + ` --ref-source env --ref-provider default --ref-id OPENAI_API_KEY\n\n` + + `Providers that don't need an API key: ${[...NO_KEY_REQUIRED_PROVIDERS].join(', ')}.\n` + + `Or point the plugin at an external Hindsight API by setting hindsightApiUrl instead.` + ); + } + + const apiKey = pluginConfig?.llmApiKey ?? ''; + if (!apiKey && !NO_KEY_REQUIRED_PROVIDERS.has(provider)) { + throw new Error( + `llmProvider is set to "${provider}" but llmApiKey is empty.\n\n` + + `Configure it via 'openclaw config set' as a SecretRef:\n` + + ` openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey \\\n` + + ` --ref-source env --ref-provider default --ref-id OPENAI_API_KEY` + ); + } + + return { + provider, + apiKey, + model: pluginConfig?.llmModel, + baseUrl: pluginConfig?.llmBaseUrl, + source: 'plugin config', + }; } /** - * Detect external Hindsight API configuration. - * Priority: env vars > plugin config + * Detect external Hindsight API configuration from plugin config. */ export function detectExternalApi(pluginConfig?: PluginConfig): { apiUrl: string | null; apiToken: string | null; } { - const apiUrl = process.env.HINDSIGHT_EMBED_API_URL || pluginConfig?.hindsightApiUrl || null; - const apiToken = process.env.HINDSIGHT_EMBED_API_TOKEN || pluginConfig?.hindsightApiToken || null; - return { apiUrl, apiToken }; + return { + apiUrl: pluginConfig?.hindsightApiUrl ?? null, + apiToken: pluginConfig?.hindsightApiToken ?? null, + }; } /** @@ -867,9 +786,6 @@ async function checkExternalApiHealth(apiUrl: string, apiToken?: string | null): function getPluginConfig(api: MoltbotPluginAPI): PluginConfig { const config = api.config.plugins?.entries?.['hindsight-openclaw']?.config || {}; const defaultMission = 'You are an AI assistant helping users across multiple communication channels (Telegram, Slack, Discord, etc.). Remember user preferences, instructions, and important context from conversations to provide personalized assistance.'; - const envBankId = typeof process.env.HINDSIGHT_BANK_ID === 'string' && process.env.HINDSIGHT_BANK_ID.trim().length > 0 - ? process.env.HINDSIGHT_BANK_ID.trim() - : undefined; return { bankMission: config.bankMission || defaultMission, @@ -879,13 +795,14 @@ function getPluginConfig(api: MoltbotPluginAPI): PluginConfig { embedPackagePath: config.embedPackagePath, llmProvider: config.llmProvider, llmModel: config.llmModel, - llmApiKeyEnv: config.llmApiKeyEnv, + llmApiKey: config.llmApiKey, + llmBaseUrl: config.llmBaseUrl, hindsightApiUrl: config.hindsightApiUrl, hindsightApiToken: config.hindsightApiToken, apiPort: config.apiPort || 9077, // Dynamic bank ID options (default: enabled) dynamicBankId: config.dynamicBankId !== false, - bankId: envBankId || (typeof config.bankId === 'string' && config.bankId.trim().length > 0 ? config.bankId.trim() : undefined), + bankId: typeof config.bankId === 'string' && config.bankId.trim().length > 0 ? config.bankId.trim() : undefined, bankIdPrefix: config.bankIdPrefix, retainTags: Array.isArray(config.retainTags) ? config.retainTags.filter((tag): tag is string => typeof tag === 'string') : undefined, retainSource: typeof config.retainSource === 'string' && config.retainSource.trim().length > 0 ? config.retainSource.trim() : undefined, @@ -1013,10 +930,7 @@ export default function (api: MoltbotPluginAPI) { log.warn(`could not initialize retain queue: ${error}`); } - // Set env vars so CLI commands (uvx hindsight-embed) use external API - process.env.HINDSIGHT_EMBED_API_URL = externalApi.apiUrl; if (externalApi.apiToken) { - process.env.HINDSIGHT_EMBED_API_TOKEN = externalApi.apiToken; debug('[Hindsight] API token configured'); } } else { @@ -1176,10 +1090,6 @@ export default function (api: MoltbotPluginAPI) { if (externalApi.apiUrl) { // External API mode usingExternalApi = true; - process.env.HINDSIGHT_EMBED_API_URL = externalApi.apiUrl; - if (externalApi.apiToken) { - process.env.HINDSIGHT_EMBED_API_TOKEN = externalApi.apiToken; - } await checkExternalApiHealth(externalApi.apiUrl, externalApi.apiToken); diff --git a/hindsight-integrations/openclaw/src/types.ts b/hindsight-integrations/openclaw/src/types.ts index 56f8058a..4806023d 100644 --- a/hindsight-integrations/openclaw/src/types.ts +++ b/hindsight-integrations/openclaw/src/types.ts @@ -55,14 +55,15 @@ export interface PluginConfig { daemonIdleTimeout?: number; // Seconds before daemon shuts down (0 = never) embedVersion?: string; // hindsight-embed version (default: "latest") embedPackagePath?: string; // Local path to hindsight package (e.g. '/path/to/hindsight') - llmProvider?: string; // LLM provider override (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama') - llmModel?: string; // LLM model override (e.g. 'gpt-4o-mini', 'claude-3-5-haiku-20241022') - llmApiKeyEnv?: string; // Env var name holding the API key (e.g. 'MY_CUSTOM_KEY') + llmProvider?: string; // LLM provider (e.g. 'openai', 'anthropic', 'gemini', 'groq', 'ollama', 'openai-codex', 'claude-code') + llmModel?: string; // LLM model (e.g. 'gpt-4o-mini', 'claude-3-5-haiku-20241022') + llmApiKey?: string; // LLM provider API key. Configure via SecretRef: openclaw config set ... --ref-source env --ref-id OPENAI_API_KEY + llmBaseUrl?: string; // Optional base URL override for OpenAI-compatible providers (e.g. OpenRouter) apiPort?: number; // Port for openclaw profile daemon (default: 9077) hindsightApiUrl?: string; // External Hindsight API URL (skips local daemon when set) - hindsightApiToken?: string; // API token for external Hindsight API authentication + hindsightApiToken?: string; // API token for external Hindsight API. Configure via SecretRef. dynamicBankId?: boolean; // Enable per-channel memory banks (default: true) - bankId?: string; // Static bank ID used when dynamicBankId is false. Can also be set via HINDSIGHT_BANK_ID. + bankId?: string; // Static bank ID used when dynamicBankId is false. bankIdPrefix?: string; // Prefix for bank IDs (e.g. 'prod' -> 'prod-slack-C123') retainTags?: string[]; // Tags applied to all retained documents (e.g. ['source_system:openclaw', 'agent:agentname']) retainSource?: string; // Source written into retained document metadata (default: 'openclaw') diff --git a/hindsight-integrations/openclaw/tests/hooks.integration.test.ts b/hindsight-integrations/openclaw/tests/hooks.integration.test.ts index 61eb5a5d..8cf20871 100644 --- a/hindsight-integrations/openclaw/tests/hooks.integration.test.ts +++ b/hindsight-integrations/openclaw/tests/hooks.integration.test.ts @@ -148,18 +148,15 @@ beforeAll(async () => { // Reset module registry so we get a fresh module with clean state. vi.resetModules(); - // Provide LLM config — used by plugin init even in HTTP mode. - process.env.HINDSIGHT_API_LLM_PROVIDER = 'openai'; - process.env.HINDSIGHT_API_LLM_API_KEY = 'test-key-hooks'; - // Point the plugin at the running test API. - process.env.HINDSIGHT_EMBED_API_URL = HINDSIGHT_API_URL; - const mod = await import('../src/index.js'); const { HindsightClient } = await import('@vectorize-io/hindsight-client'); const pluginFn = mod.default; const getClient = mod.getClient; + // Plugin runs in external API mode (talks to the running test API), so no LLM + // credentials are needed in the plugin config — the daemon handles them. const handle = createMockApi({ + hindsightApiUrl: HINDSIGHT_API_URL, dynamicBankId: true, excludeProviders: ['slack'], retainEveryNTurns: 1, // retain every turn so individual tests aren't affected by chunking @@ -188,9 +185,6 @@ beforeAll(async () => { afterAll(async () => { vi.restoreAllMocks(); - delete process.env.HINDSIGHT_API_LLM_PROVIDER; - delete process.env.HINDSIGHT_API_LLM_API_KEY; - delete process.env.HINDSIGHT_EMBED_API_URL; if (stopServicesFn) await stopServicesFn().catch(() => {}); }, 15_000); diff --git a/skills/hindsight-docs/references/changelog/integrations/openclaw.md b/skills/hindsight-docs/references/changelog/integrations/openclaw.md index ae96519e..233dc0a8 100644 --- a/skills/hindsight-docs/references/changelog/integrations/openclaw.md +++ b/skills/hindsight-docs/references/changelog/integrations/openclaw.md @@ -8,6 +8,21 @@ import PageHero from '@site/src/components/PageHero'; ← OpenClaw integration +## 0.6.0 (Unreleased) + +**Breaking Changes** + +- The plugin no longer reads any configuration from process environment variables. All settings — including the LLM provider, model, API key, base URL, external Hindsight API URL/token, and bank ID — must now be set through OpenClaw's plugin config (e.g. `openclaw config set plugins.entries.hindsight-openclaw.config. `). API keys and other secrets should be configured as `SecretRef` values via `--ref-source env|file|exec` so they're resolved from your secret store at runtime instead of being stored in plaintext on disk. +- Removed the `llmApiKeyEnv` plugin config field. Use the new `llmApiKey` field configured as a SecretRef instead (e.g. `openclaw config set plugins.entries.hindsight-openclaw.config.llmApiKey --ref-source env --ref-id OPENAI_API_KEY`). +- Removed automatic LLM provider detection from `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` / `GEMINI_API_KEY` / `GROQ_API_KEY`. Set `llmProvider` and `llmApiKey` explicitly via `openclaw config set`. +- Removed support for the `HINDSIGHT_API_LLM_PROVIDER`, `HINDSIGHT_API_LLM_MODEL`, `HINDSIGHT_API_LLM_API_KEY`, `HINDSIGHT_API_LLM_BASE_URL`, `HINDSIGHT_EMBED_API_URL`, `HINDSIGHT_EMBED_API_TOKEN`, and `HINDSIGHT_BANK_ID` environment variables. The same values now live in plugin config — see the migration guide. + +**Features** + +- Added the `llmApiKey` plugin config field, marked as a sensitive field so OpenClaw resolves it as a `SecretRef` from env, file, or exec sources. +- Added the `llmBaseUrl` plugin config field for OpenAI-compatible endpoint overrides (OpenRouter, Azure OpenAI, vLLM, etc.). +- Marked `hindsightApiToken` as a sensitive field — it can now be configured as a `SecretRef` the same way as `llmApiKey`. + ## [0.5.1](https://github.com/vectorize-io/hindsight/tree/integrations/openclaw/v0.5.1) **Bug Fixes**