From 5da99b8b5b7367387dd6aa69d77646dfe82aa13c Mon Sep 17 00:00:00 2001 From: handnewb <61999949+handnewb@users.noreply.github.com> Date: Tue, 11 Aug 2026 18:02:32 -0300 Subject: [PATCH 1/2] fix(embedded): preserve shell environment variables when constructor defaults are empty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The HindsightEmbedded constructor included every parameter in the config dict even when the caller left them at their default of "". Those empty-string values travelled through the daemon-startup env pipeline and overwrote the caller's actual shell environment variables (os.environ) set via export or .env files, so HINDSIGHT_API_LLM_API_KEY (and every other key) was silently ignored unless passed explicitly to the constructor. Root cause: the HINDSIGHT_* propagation loop in _start_daemon used 'value is not None' as its guard, so an empty string was always propagated and overwrote any prior os.environ value. Changes (defence in depth): - embedded.py: _set_if_truthy helper — only add keys with truthy values to the config dict. An empty default no longer poisons the merged env. - daemon_embed_manager.py: guard propagation on 'value' (truthy) instead of 'value is not None', so even if an empty string reaches this loop it is harmlessly skipped. Closes #3253. --- hindsight-all/hindsight/embedded.py | 63 ++++++++++--------- .../hindsight_embed/daemon_embed_manager.py | 2 +- .../sdks/integrations/agent-plugin.md | 14 +++++ 3 files changed, 49 insertions(+), 30 deletions(-) create mode 100644 skills/hindsight-docs/references/sdks/integrations/agent-plugin.md diff --git a/hindsight-all/hindsight/embedded.py b/hindsight-all/hindsight/embedded.py index 64df55d042..5c4c144af4 100644 --- a/hindsight-all/hindsight/embedded.py +++ b/hindsight-all/hindsight/embedded.py @@ -45,6 +45,21 @@ logger = logging.getLogger(__name__) +def _set_if_truthy(target: dict[str, str]): + """Return a setter that only writes non-empty values to *target*. + + Callers that rely on shell environment variables + (``os.environ``) should not have those silently overridden by a + constructor default of ``\"\"``. See issue #3253. + """ + + def _set(key: str, value: str) -> None: + if value: + target[key] = value + + return _set + + class HindsightEmbedded: """ Hindsight client with automatic daemon lifecycle management. @@ -110,20 +125,20 @@ def __init__( """ self.profile = profile - # Build config dict for daemon (matches CLI format) - self.config = { - "HINDSIGHT_API_LLM_PROVIDER": llm_provider, - "HINDSIGHT_API_LLM_API_KEY": llm_api_key, - "HINDSIGHT_API_LLM_MODEL": llm_model, - "HINDSIGHT_API_LOG_LEVEL": log_level, - "HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT": str(idle_timeout), - } - - if llm_base_url: - self.config["HINDSIGHT_API_LLM_BASE_URL"] = llm_base_url - - if database_url: - self.config["HINDSIGHT_EMBED_API_DATABASE_URL"] = database_url + # Build config dict for daemon (matches CLI format). + # Only include keys with actual values so that callers relying on shell + # environment variables (os.environ) aren't silently overridden by empty + # defaults. See issue #3253. + self.config: dict[str, str] = {} + _set_if = _set_if_truthy(self.config) + + _set_if("HINDSIGHT_API_LLM_PROVIDER", llm_provider) + _set_if("HINDSIGHT_API_LLM_API_KEY", llm_api_key) + _set_if("HINDSIGHT_API_LLM_MODEL", llm_model) + _set_if("HINDSIGHT_API_LOG_LEVEL", log_level) + _set_if("HINDSIGHT_EMBED_DAEMON_IDLE_TIMEOUT", str(idle_timeout) if idle_timeout else "") + _set_if("HINDSIGHT_API_LLM_BASE_URL", llm_base_url or "") + _set_if("HINDSIGHT_EMBED_API_DATABASE_URL", database_url or "") self._ui = ui self._ui_port = ui_port @@ -175,17 +190,13 @@ def _ensure_started(self): self._started = False if self._closed: - raise RuntimeError( - "Cannot use HindsightEmbedded after it has been closed" - ) + raise RuntimeError("Cannot use HindsightEmbedded after it has been closed") # Use embed manager interface for daemon management logger.info(f"Ensuring daemon is running for profile '{self.profile}'...") success = self._manager.ensure_running(self.config, self.profile) if not success: - raise RuntimeError( - f"Failed to start daemon for profile '{self.profile}'" - ) + raise RuntimeError(f"Failed to start daemon for profile '{self.profile}'") # Get daemon URL and create client daemon_url = self._manager.get_url(self.profile) @@ -196,9 +207,7 @@ def _ensure_started(self): # Start UI if requested if self._ui: logger.info(f"Starting UI for profile '{self.profile}'...") - ui_started = self._manager.start_ui( - self.profile, self._ui_port, self._ui_hostname - ) + ui_started = self._manager.start_ui(self.profile, self._ui_port, self._ui_hostname) if not ui_started: logger.warning(f"Failed to start UI for profile '{self.profile}'") @@ -219,8 +228,7 @@ def _cleanup(self, stop_daemon_on_close: bool = False): # Mark closed to prevent new operations but skip shared-state # teardown — the daemon's idle timeout handles the rest. logger.warning( - "Cleanup lock acquisition timed out for profile '%s'; " - "marking closed, daemon will idle-stop on its own", + "Cleanup lock acquisition timed out for profile '%s'; marking closed, daemon will idle-stop on its own", self.profile, ) self._closed = True @@ -433,10 +441,7 @@ def url(self) -> str: def is_running(self) -> bool: """Check if the client is initialized and the daemon is responsive.""" return ( - self._started - and not self._closed - and self._client is not None - and self._manager.is_running(self.profile) + self._started and not self._closed and self._client is not None and self._manager.is_running(self.profile) ) @property diff --git a/hindsight-embed/hindsight_embed/daemon_embed_manager.py b/hindsight-embed/hindsight_embed/daemon_embed_manager.py index 4b1508b4b7..fafacd1300 100644 --- a/hindsight-embed/hindsight_embed/daemon_embed_manager.py +++ b/hindsight-embed/hindsight_embed/daemon_embed_manager.py @@ -540,7 +540,7 @@ def _start_daemon_locked( # HINDSIGHT_API_EMBEDDINGS_PROVIDER) are silently dropped because the # whitelist above only covers LLM/log/idle_timeout keys. for key, value in config.items(): - if key.startswith("HINDSIGHT_") and value is not None: + if key.startswith("HINDSIGHT_") and value: env[key] = str(value) # Use profile-specific database (check config for override) diff --git a/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md b/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md new file mode 100644 index 0000000000..87382baf0c --- /dev/null +++ b/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md @@ -0,0 +1,14 @@ + +# Agent Plugins + +Portable long-term memory for any [Agent Plugins](https://agent-plugins.org) client, powered by Hindsight. + +Agent Plugins is the vendor-neutral open standard for packaging Agent Skills + MCP servers into a single distributable plugin. Hindsight ships one plugin that every compatible client can load. + +## Quick Start + +1. Get your API key from the Hindsight Cloud dashboard. +2. Set `HINDSIGHT_API_KEY` and optionally `HINDSIGHT_BANK_ID`. +3. Install the plugin in your client through its plugin/MCP UI. + +Once installed, agents can call `recall`, `retain`, and `reflect` automatically through the bundled skill. From 9ab439dd45731b95b89f877891c14817a41e3257 Mon Sep 17 00:00:00 2001 From: handnewb <61999949+handnewb@users.noreply.github.com> Date: Wed, 12 Aug 2026 09:31:53 -0300 Subject: [PATCH 2/2] chore: sync agent-plugin.md with latest generate-docs-skill.sh output --- .../sdks/integrations/agent-plugin.md | 83 +++++++++++++++++-- 1 file changed, 77 insertions(+), 6 deletions(-) diff --git a/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md b/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md index 87382baf0c..c47aa3256a 100644 --- a/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md +++ b/skills/hindsight-docs/references/sdks/integrations/agent-plugin.md @@ -1,14 +1,85 @@ # Agent Plugins -Portable long-term memory for any [Agent Plugins](https://agent-plugins.org) client, powered by Hindsight. +Portable long-term memory for any [Agent Plugins](https://agent-plugins.org) client, powered by [Hindsight](https://vectorize.io/hindsight). -Agent Plugins is the vendor-neutral open standard for packaging Agent Skills + MCP servers into a single distributable plugin. Hindsight ships one plugin that every compatible client can load. +[Agent Plugins](https://agent-plugins.org) is the vendor-neutral open standard (developed with Amazon, Cursor, Microsoft, OpenAI, and Vercel) for packaging **Agent Skills + MCP servers** into a single distributable plugin. Instead of a separate integration per tool, Hindsight ships **one** plugin that every compatible client can load — at launch: **ChatGPT / Codex, Cursor, GitHub Copilot, Kiro, and VS Code**. ## Quick Start -1. Get your API key from the Hindsight Cloud dashboard. -2. Set `HINDSIGHT_API_KEY` and optionally `HINDSIGHT_BANK_ID`. -3. Install the plugin in your client through its plugin/MCP UI. +> **💡 Recommended: Hindsight Cloud** +> +[Sign up free](https://ui.hindsight.vectorize.io/signup) for a Hindsight Cloud API key — no self-hosting, no local daemon to manage. +1. Get your `hsk_...` API key from [ui.hindsight.vectorize.io/connect](https://ui.hindsight.vectorize.io/connect). +2. Set the environment variables the plugin reads: -Once installed, agents can call `recall`, `retain`, and `reflect` automatically through the bundled skill. + ```bash + export HINDSIGHT_API_KEY="hsk_your_token" + export HINDSIGHT_BANK_ID="my-project" # optional; defaults to "default" + ``` + +3. Install the plugin in your client (through its plugin/MCP UI, or by pointing it at the plugin directory — installation is client-specific per the standard). + +Once installed, ask the agent something that depends on past context, or tell it a durable preference — it calls `recall` and `retain` automatically, guided by the bundled skill. + +## What's in the plugin + +The plugin is a thin, transport-only wrapper — all memory logic stays server-side in Hindsight. It follows the Agent Plugins `1.0.0` layout: + +``` +agent-plugin/ +├── plugin.json # manifest ($schema + name + metadata) +├── mcp.json # Hindsight MCP server (Streamable HTTP) +└── skills/ + └── hindsight-memory/ + └── SKILL.md # teaches the agent when to recall / retain / reflect +``` + +- **`mcp.json`** connects the client to Hindsight's built-in [MCP server](../../developer/mcp-server.md) over Streamable HTTP. +- **`skills/hindsight-memory/SKILL.md`** is loaded into the agent's context so it knows *when* to reach for memory, not just that the tools exist. + +## Memory tools + +Via the MCP server, the agent gets Hindsight's full memory surface. The three it reaches for most: + +| Tool | When | What it does | +|------|------|--------------| +| `recall` | Before answering, when past context could help | Semantic + keyword + graph + temporal retrieval over the bank | +| `retain` | After learning a durable, reusable fact | Stores the fact for future sessions | +| `reflect` | When a lookup is too shallow and you need synthesized reasoning | Disposition-aware reasoning over everything remembered | + +Additional tools (knowledge pages, mental models, documents, tags) are exposed too — see the [MCP Server reference](../../developer/mcp-server.md). + +## Configuration + +The plugin reads two environment variables, interpolated into `mcp.json`: + +| Setting | Env Var | Default | Description | +|---------|---------|---------|-------------| +| API key | `HINDSIGHT_API_KEY` | — | Your `hsk_...` key. Sent as `Authorization: Bearer`. Required for Hindsight Cloud. | +| Memory bank | `HINDSIGHT_BANK_ID` | `default` | Bank to read from and write to (sent as `X-Bank-Id`). Use one bank per user, project, or team for isolation. | + +> **📝 Env-var syntax varies by client** +> +Most clients substitute `${VAR}`; some (VS Code, Cursor) use `${env:VAR}`. If your client doesn't interpolate, paste the literal key and bank id into `mcp.json`. +**Self-hosting:** replace the host in `mcp.json` (`https://api.hindsight.vectorize.io`) with your deployment's URL. A local server with the MCP endpoint open needs no API key. + +## Explicit tools vs. automatic capture + +Agent Plugins `1.0.0` standardizes **Skills + MCP**, not session lifecycle hooks. This plugin therefore delivers **explicit, tool-driven** memory that works identically across every supported client. + +For the fully automatic experience — recall injected before every prompt and transcripts retained on session end — use the native, hook-based integration built for your specific tool, such as [Claude Code](claude-code.md) or [Codex](codex.md). Both share the same Hindsight banks, so memory captured by the hook-based integration is recalled through the Agent Plugin, and vice versa. + +## Troubleshooting + +**No memories recalled**: `recall` returns results only after something has been retained. Retain a fact first, or seed the bank via the [API](../../developer/api/quickstart.md). + +**401 Unauthorized**: Check `HINDSIGHT_API_KEY` is set and your client is interpolating it into the `Authorization` header (see the env-var syntax note above). + +**Wrong or empty memory**: Confirm `HINDSIGHT_BANK_ID` points at the bank you expect. Different tools writing to different banks won't share memory. + +## Learn more + +- [Agent Plugins standard](https://agent-plugins.org) +- [Hindsight MCP Server reference](../../developer/mcp-server.md) +- [Hindsight Cloud sign-up](https://ui.hindsight.vectorize.io/signup)