> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Hindsight

> Hindsight is a long-term memory server for AI agents (retain, recall, reflect) over REST and MCP, backed by PostgreSQL with pgvector. One location, optional HA database and workers. Deploys into an existing GVC; needs API key, web UI key, database and LLM key secrets first.

## Overview

[Hindsight](https://github.com/vectorize-io/hindsight) is an open-source memory server for AI agents. Agents **retain** conversations and documents, and an LLM extracts facts, entities and timestamps into a per-bank memory graph. Agents then **recall** (hybrid search, reranked) or **reflect** (the LLM reasons over the bank) through a REST API or a built-in MCP server. A **bank** is one isolated memory store, created on first use; give each agent or user its own.

Unlike plain vector search, Hindsight does not store raw chunks and return the nearest ones. Retain turns text into structured facts linked to entities and timestamps, consolidation merges related facts over time, and recall combines semantic, keyword and graph signals with a reranker. This template deploys the API, a web UI (called Hindsight Control Plane upstream), optional dedicated workers, and PostgreSQL with pgvector (a single instance by default, or a highly available cluster).

> [!NOTE]
>
> This template deploys into an existing [GVC](https://docs.controlplane.com/reference/gvc.md) that you already have. It does not create or manage a GVC. Install into a GVC with a single location: the database runs in one location.

### What Gets Created

| Resource | Name | Purpose | Optional |
| - | - | - | - |
| Workload | `RELEASE_NAME-hindsight` | REST and MCP API on port `8888` (standard, 1 replica by default) | — |
| Workload | `RELEASE_NAME-hindsight-ui` | Web UI on port `9999`, behind an access key (standard) | `ui.enabled: true` (default) |
| Workload | `RELEASE_NAME-hindsight-worker` | Dedicated background task workers (stateful) | `worker.enabled: true` |
| Workload | `RELEASE_NAME-pgvector` | PostgreSQL with pgvector holding every bank (stateful, TCP `5432`) | `pgvector.enabled: true` (default) |
| Volume Set | `RELEASE_NAME-pgvector-vs` | Database volume | `pgvector.enabled: true` (default) |
| Secret | `RELEASE_NAME-pgvector-init` | First-boot SQL that creates the `vector` extension; no credentials | `pgvector.enabled: true` (default) |
| Workload | `RELEASE_NAME-postgres-ha` | Patroni PostgreSQL cluster (3 replicas) | `postgresHA.enabled: true` |
| Workload | `RELEASE_NAME-postgres-ha-proxy` | HAProxy leader endpoint for the cluster | `postgresHA.enabled: true` |
| Workload | `RELEASE_NAME-etcd` | etcd consensus store for the cluster | `postgresHA.enabled: true` |
| Volume Set | `RELEASE_NAME-postgres-ha-vs` | Volume per Patroni replica | `postgresHA.enabled: true` |
| Volume Set | `RELEASE_NAME-etcd-vs` | Volume per etcd replica | `postgresHA.enabled: true` |
| Identity | `RELEASE_NAME-hindsight-identity` | Identity of the API and workers | — |
| Identity | `RELEASE_NAME-hindsight-ui-identity` | Identity of the web UI | — |
| Policy | `RELEASE_NAME-hindsight-policy` | `reveal` on exactly the secrets you name, for the API and workers | — |
| Policy | `RELEASE_NAME-hindsight-ui-policy` | `reveal` on the API key and web UI access key | — |

The template creates no secrets with credentials. The `postgresHA` option also creates its own identities, policies and startup secrets.

## Prerequisites

Create these secrets before you install. Secret names are org-wide, so give each release its own. Secrets are org-level, so no GVC flag is involved.

**1. Create the API key secret**

The Bearer token for REST and MCP. It opens every bank:

```bash theme={null}
printf '%s' "$(openssl rand -hex 32)" | cpln secret create-opaque --name API_KEY_SECRET_NAME --encoding plain -f -
```

Set `auth.apiKeySecretName` to this name.

**2. Create the database credentials secret**

A dictionary secret with exactly `username`, `password` and `database`. Use letters and digits only, because they go into a connection URL:

```bash theme={null}
cpln secret create-dictionary --name DB_CREDENTIALS_SECRET_NAME \
  --entry username=hindsight \
  --entry password="$(openssl rand -hex 24)" \
  --entry database=hindsight
```

Set `pgvector.config.credentialsSecretName` to this name (and `postgresHA.config.credentialsSecretName` when you use the HA database). Read it back with `cpln secret reveal DB_CREDENTIALS_SECRET_NAME -o yaml`.

**3. Create the web UI access key secret**

Needed while `ui.enabled` is `true`. It is the key you type to log in to the web UI:

```bash theme={null}
printf '%s' "$(openssl rand -hex 16)" | cpln secret create-opaque --name UI_KEY_SECRET_NAME --encoding plain -f -
```

Set `ui.accessKeySecretName` to this name.

**4. Create the LLM key secret**

The API key of your LLM provider. Not needed for `ollama`, for a mock or `none` provider, or for a self-hosted server without authentication:

```bash theme={null}
printf '%s' "$OPENAI_API_KEY" | cpln secret create-opaque --name LLM_KEY_SECRET_NAME --encoding plain -f -
```

Set `llm.apiKeySecretName` to this name, or to `""` when no key is needed. The model must support JSON-schema (structured) output. Hindsight asks for up to 65k output tokens per retain call; for a model with a smaller limit set `HINDSIGHT_API_RETAIN_MAX_COMPLETION_TOKENS` (above 3000) in `hindsight.extraEnv`.

> [!WARNING]
>
> **Create every secret before installing, or the deployment wedges silently.** A name that points at a secret which does not exist installs successfully and then never starts. The container never runs, so `cpln logs` returns zero lines. The one place the reason appears is `status.versions[].message`:
>
> ```bash theme={null}
> cpln workload get-deployments RELEASE_NAME-hindsight --gvc GVC_NAME -o yaml
> ```
>
> Create the missing secret, then run `cpln workload force-redeployment RELEASE_NAME-hindsight --gvc GVC_NAME` to skip the wait.

Database backups are optional and need a bucket and a Control Plane cloud account, set up as in the [pgvector](https://docs.controlplane.com/template-catalog/templates/pgvector.md) or [PostgreSQL Highly Available](https://docs.controlplane.com/template-catalog/templates/postgres-highly-available.md) template.

## Installation

Install the chart from the marketplace registry, pointing it at the secrets you created:

```bash theme={null}
cpln helm install RELEASE_NAME oci://ghcr.io/controlplane-com/templates/hindsight \
  --version 1.0.0 --gvc GVC_NAME \
  --set auth.apiKeySecretName=API_KEY_SECRET_NAME \
  --set pgvector.config.credentialsSecretName=DB_CREDENTIALS_SECRET_NAME \
  --set ui.accessKeySecretName=UI_KEY_SECRET_NAME \
  --set llm.apiKeySecretName=LLM_KEY_SECRET_NAME
```

The defaults use OpenAI with `gpt-5-mini`; for another provider add the `llm.*` values described under [Configuration](#configuration).

Or follow the instructions for your preferred method:

> [card] [UI](https://docs.controlplane.com/template-catalog/install-manage/ui.md)
>
> Browse, install, and manage templates visually

> [card] [CLI](https://docs.controlplane.com/template-catalog/install-manage/cli.md)
>
> Manage templates from your terminal

> [card] [Terraform](https://docs.controlplane.com/template-catalog/install-manage/terraform.md)
>
> Declare templates in your Terraform configurations

> [card] [Pulumi](https://docs.controlplane.com/template-catalog/install-manage/pulumi.md)
>
> Declare templates in your Pulumi programs

## Configuration

### API and Access

```yaml theme={null}
auth:
  apiKeySecretName: my-hindsight-api-key

hindsight:
  image: ghcr.io/vectorize-io/hindsight-api:1.2.1
  # set hindsight.replicas: 2, worker.enabled: true and postgresHA.enabled: true for near-zero-downtime upgrades and failover
  replicas: 1                   # 1-10 interchangeable API replicas sharing PostgreSQL
  requestTimeoutSeconds: 300    # longest request (a sync retain or reflect waits on the LLM) before it is cut (1-3600)
  dbPoolMaxSize: 20             # PostgreSQL connections per process; (replicas + worker replicas) x this must be <= 90

publicAccess:
  api: false                    # true = REST + MCP on a public *.cpln.app URL (API key still required)
  ui: false                     # true = web UI on a public *.cpln.app URL (access key still required)
internalAccess:                 # who in Control Plane may call the API
  type: same-gvc                # none | same-gvc | same-org | workload-list (none needs ui.enabled: false)
  workloads: []                 # used with workload-list
```

The API and the web UI are private by default. The two public switches are independent: you can expose the web UI while the API stays private, because the web UI calls the API inside the GVC.

### LLM

```yaml theme={null}
llm:
  provider: openai              # e.g. openai, anthropic, gemini, groq, ollama, litellm
  model: gpt-5-mini             # must support JSON-schema output
  baseUrl: ""                   # "" = provider default; e.g. http://my-ollama.my-gvc.cpln.local:11434/v1
  apiKeySecretName: my-hindsight-llm-key
```

The LLM does fact extraction, reflect and consolidation. Settings for other providers (the provider's own key goes in `llm.apiKeySecretName`, empty for a server without authentication):

| Provider | `llm.provider` | `llm.model` (example) | `llm.baseUrl` |
| - | - | - | - |
| Anthropic | `anthropic` | `claude-sonnet-4-5` | `""` |
| Gemini | `gemini` | `gemini-2.5-flash` | `""` |
| [Ollama](https://docs.controlplane.com/template-catalog/templates/ollama.md) template | `ollama` | `qwen2.5:7b` | `http://OLLAMA_RELEASE-ollama.GVC_NAME.cpln.local:11434/v1` |
| [vLLM](https://docs.controlplane.com/template-catalog/templates/vllm.md) template | `openai` | the served model name | `http://VLLM_RELEASE-vllm.GVC_NAME.cpln.local:8000/v1` |
| [LiteLLM](https://docs.controlplane.com/template-catalog/templates/litellm.md) template | `openai` | a model name LiteLLM routes | `http://LITELLM_RELEASE-litellm.GVC_NAME.cpln.local:4000/v1` |

vLLM and LiteLLM expose an OpenAI-compatible API, so they use provider `openai` with a `/v1` base URL. A small model on CPU can need minutes for one reflect; raise `hindsight.requestTimeoutSeconds` (up to 3600) or give Ollama a GPU.

### Embeddings and Reranker

```yaml theme={null}
embeddings:
  provider: local               # local (bundled BAAI/bge-small-en-v1.5, 384 dims) | openai (OpenAI or OpenAI-compatible)
  model: ""                     # openai only; "" = text-embedding-3-small. Changing it later needs a re-embed
  baseUrl: ""                   # openai only; e.g. http://my-ollama.my-gvc.cpln.local:11434/v1
  apiKeySecretName: ""          # openai only; opaque secret. "" = use the LLM key

reranker:
  provider: local               # local (bundled hs-rerank-1) | rrf (no model; required with the slim image)
```

Choose the embedding provider at install: stored vectors belong to the model that wrote them. To change it later, follow [Changing the Embedding Model](#changing-the-embedding-model). The default full image (about 1 GB compressed) bundles both local models, so recall makes no external call. The `-slim` image is less than half the size and has no local models; use it with an OpenAI-compatible embeddings server and rank fusion:

```yaml theme={null}
hindsight:
  image: ghcr.io/vectorize-io/hindsight-api:1.2.1-slim
embeddings:
  provider: openai
  model: nomic-embed-text
  baseUrl: http://my-ollama.my-gvc.cpln.local:11434/v1
  apiKeySecretName: ""          # openai only; opaque secret. "" = use the LLM key
reranker:
  provider: rrf
```

### Extra Settings

```yaml theme={null}
hindsight:
  # Plain upstream settings (HINDSIGHT_API_*), applied to the API and workers.
  extraEnv: []
  # extraEnv:
  #   - name: HINDSIGHT_API_RETAIN_MAX_COMPLETION_TOKENS   # needed for models with < 65k output tokens
  #     value: "16000"
  # Optional prerequisite `dictionary` secret: each listed key becomes an env var of the same name. "" = none.
  extraSecrets:
    secretName: ""              # e.g. my-hindsight-extra-secrets
    keys: []                    # e.g. [HINDSIGHT_API_RERANKER_COHERE_API_KEY]
```

Any upstream `HINDSIGHT_API_*` setting goes in `extraEnv`; secret ones (a Cohere reranker key, per-operation LLM keys) go in a dictionary secret listed in `extraSecrets`. The template refuses the settings it owns, including every `HINDSIGHT_API_TENANT_*` setting.

### Workers

```yaml theme={null}
worker:
  enabled: false                # true = background tasks move off the API into their own workload
  replicas: 1                   # 1-10
```

Without dedicated workers each API replica also runs retain, consolidation and other background tasks. Each worker replica keeps a stable id, so a restarted worker picks up its own unfinished tasks.

### Web UI

```yaml theme={null}
ui:
  enabled: true                 # browse banks, entities and documents; test recall and reflect
  image: ghcr.io/vectorize-io/hindsight-control-plane:1.2.1
  # REQUIRED PREREQUISITE SECRET when enabled — an `opaque` secret: the key you type to log in to the web UI.
  accessKeySecretName: my-hindsight-ui-access-key
```

### Database

```yaml theme={null}
pgvector:
  enabled: true
  config:
    # REQUIRED PREREQUISITE SECRET — CREATE IT BEFORE YOU INSTALL, or the deployment WEDGES silently.
    # A `dictionary` secret with exactly username, password and database, LETTERS AND DIGITS ONLY.
    credentialsSecretName: my-hindsight-db-credentials
  volumeset:
    capacity: 10                # GiB (minimum 10)
# set postgresHA.enabled: true (and pgvector.enabled: false) for near-zero-downtime upgrades and failover
postgresHA:
  enabled: false
  config:
    # REQUIRED PREREQUISITE SECRET when enabled — same keys and letters-and-digits rule as pgvector.config above.
    credentialsSecretName: my-hindsight-db-credentials
  replicas: 3
  volumeset:
    capacity: 10                # GiB per replica (minimum 10)
```

Enable exactly one of `pgvector` and `postgresHA`. Every knob of the [pgvector](https://docs.controlplane.com/template-catalog/templates/pgvector.md) and [PostgreSQL Highly Available](https://docs.controlplane.com/template-catalog/templates/postgres-highly-available.md) templates is available under its key, including scheduled backups under `backup`.

## Connecting

| Endpoint | Address | Auth |
| - | - | - |
| REST API, same GVC | `http://RELEASE_NAME-hindsight.GVC_NAME.cpln.local:8888/v1/default/banks/BANK_ID/...` | `Authorization: Bearer API_KEY` |
| MCP, same GVC | `http://RELEASE_NAME-hindsight.GVC_NAME.cpln.local:8888/mcp/BANK_ID/` | `Authorization: Bearer API_KEY` |
| API, public (`publicAccess.api: true`) | `status.canonicalEndpoint` of `RELEASE_NAME-hindsight` | `Authorization: Bearer API_KEY` |
| Web UI, public (`publicAccess.ui: true`) | `status.canonicalEndpoint` of `RELEASE_NAME-hindsight-ui` | access key |
| Health | `/health` on port `8888` | none |

Read a canonical endpoint with `cpln workload get RELEASE_NAME-hindsight --gvc GVC_NAME -o yaml`, and the API key with `cpln secret reveal API_KEY_SECRET_NAME -o yaml`. From a workload in the same GVC:

```bash theme={null}
H="Authorization: Bearer $HINDSIGHT_API_KEY"
URL=http://RELEASE_NAME-hindsight.GVC_NAME.cpln.local:8888

curl -s -X POST "$URL/v1/default/banks/my-agent/memories" -H "$H" -H 'content-type: application/json' \
  -d '{"items":[{"content":"Alice prefers oolong tea and works on the billing team."}]}'
curl -s -X POST "$URL/v1/default/banks/my-agent/memories/recall" -H "$H" -H 'content-type: application/json' \
  -d '{"query":"What does Alice drink?"}'
curl -s -X POST "$URL/v1/default/banks/my-agent/reflect" -H "$H" -H 'content-type: application/json' \
  -d '{"query":"What do we know about Alice?"}'
```

A bank is created on first use. Without the key every `/v1` and `/mcp` call returns `401`.

### MCP Clients

An MCP client connects to one bank at `/mcp/BANK_ID/` (streamable HTTP); the tools include `retain`, `recall` and `reflect`. For Claude Code, with the public API enabled:

```bash theme={null}
claude mcp add --transport http hindsight https://CANONICAL_ENDPOINT/mcp/my-agent/ \
  --header "Authorization: Bearer $HINDSIGHT_API_KEY"
```

With `--scope project`, Claude Code lists the server as pending until you approve it the next time you start `claude`.

### Web UI Access

Open the web UI and log in with the access key from your web UI secret. It lets you browse banks, entities and documents, and try recall and reflect. While it is private, forward the API or the web UI to your machine (then open `http://localhost:9999`):

```bash theme={null}
cpln port-forward RELEASE_NAME-hindsight 8888:8888 --gvc GVC_NAME
cpln port-forward RELEASE_NAME-hindsight-ui 9999:9999 --gvc GVC_NAME
```

For regular browser use, set `publicAccess.ui: true` and use the public web UI behind its access key; port-forward suits `curl` and short checks. Firewall changes take a short while to propagate.

## Operations

### Changing the Embedding Model

Stored vectors belong to the model that wrote them, so changing `embeddings.*` after data exists breaks recall until every memory is re-embedded. The install wizard refuses this change; make it with the CLI. With the release still on the old model, run the re-embed with the new model's settings, then upgrade, then finish:

```bash theme={null}
cpln workload exec RELEASE_NAME-hindsight --gvc GVC_NAME --container hindsight -- env \
  HINDSIGHT_API_EMBEDDINGS_PROVIDER=openai HINDSIGHT_API_EMBEDDINGS_OPENAI_BASE_URL=NEW_URL \
  HINDSIGHT_API_EMBEDDINGS_OPENAI_MODEL=NEW_MODEL HINDSIGHT_API_EMBEDDINGS_OPENAI_API_KEY=KEY_OR_unused \
  hindsight-admin reembed --all-schemas
cpln helm upgrade RELEASE_NAME oci://ghcr.io/controlplane-com/templates/hindsight \
  --version 1.0.0 --gvc GVC_NAME -f values.yaml
cpln workload exec RELEASE_NAME-hindsight --gvc GVC_NAME --container hindsight -- hindsight-admin reembed --all-schemas --finish
```

The upgrade uses a `values.yaml` file with the new `embeddings.*` values, in the chart's nesting. Between the first and second command, retains to the old servers return `503`; recall keeps working.

### Scaling and Availability

The defaults are one API replica and one database instance. For near-zero-downtime upgrades and failover set `hindsight.replicas: 2`, `worker.enabled: true` and `postgresHA.enabled: true` (with `pgvector.enabled: false`). API replicas share PostgreSQL and are interchangeable. A single database instance restarts with a brief interruption; the HA cluster fails over instead. Keep `(hindsight.replicas + worker.replicas) x hindsight.dbPoolMaxSize` at or below 90.

The template runs in one location. Uninstalling deletes the database volume and every memory bank with it.

### Webhooks

Webhooks deliver only to public addresses by default. To reach a receiver inside Control Plane, list its exact hostname (comma-separated, no wildcards) in `HINDSIGHT_API_WEBHOOK_ALLOWED_HOSTS` through `hindsight.extraEnv`:

```yaml theme={null}
hindsight:
  extraEnv:
    - name: HINDSIGHT_API_WEBHOOK_ALLOWED_HOSTS
      value: my-receiver.my-gvc.cpln.local
```

### Mental Models

Mental models and pages are synthesized summaries of a bank: a mental model answers a standing question (for example, what is known about a person or project), and a page collects such content into a document. Create them in the web UI, or through the API or MCP. A refresh rewrites the content from the facts retained so far, so a refresh after new retains includes the new facts; a bank with no facts gives empty content.

They are written by an agentic reflect loop that needs a capable LLM. A small model on CPU may not finish before `HINDSIGHT_API_REFLECT_WALL_TIMEOUT` (300 seconds): use a hosted or GPU-served model, or raise that timeout through `hindsight.extraEnv`.

### Stuck Background Tasks

A task claimed by a process that was hard-killed (an out-of-memory kill, a lost node) stays claimed under that process's id. List them and hand them back:

```bash theme={null}
cpln workload exec RELEASE_NAME-hindsight --gvc GVC_NAME --container hindsight -- hindsight-admin worker-status
cpln workload exec RELEASE_NAME-hindsight --gvc GVC_NAME --container hindsight -- hindsight-admin decommission-worker WORKER_ID
```

### Troubleshooting

> [accordion] The deployment waits and cpln logs returns nothing
>
> A prerequisite secret (API key, database, web UI key or LLM key) does not exist. Read the reason with `cpln workload get-deployments RELEASE_NAME-hindsight --gvc GVC_NAME -o yaml` (`status.versions[].message`), create the secret, then run `cpln workload force-redeployment RELEASE_NAME-hindsight --gvc GVC_NAME`.

> [accordion] Every call returns 401
>
> The request has no `Authorization: Bearer API_KEY` header, or the key differs from the API key secret. `/health` needs no key.

> [accordion] Retain fails although the API is up
>
> The LLM key or base URL is wrong; a bad key does not stop startup. Read the API logs with `cpln logs '{gvc="GVC_NAME", workload="RELEASE_NAME-hindsight"}' --limit 50 --since 10m`.

## Important Notes

* One API key opens every bank: anyone holding it, or the web UI access key, can read and change all banks. Give each agent its own bank id to keep memories separate, not for security. Per-user keys are not available.
* Updating a secret restarts nothing: after rotating one, run `cpln workload force-redeployment` on the API, the workers and the web UI.
* The database password is read only when PostgreSQL first initializes; run `ALTER ROLE` before updating its secret.
* Database credentials must be letters and digits only.
* Choose the embedding provider at install; changing it later needs a re-embed.
* Use the `-slim` image only with `embeddings.provider: openai` and `reranker.provider: rrf`.
* Install into a GVC with one location; uninstalling deletes every memory bank.

## External References

* [Hindsight on GitHub](https://github.com/vectorize-io/hindsight)
* [Hindsight documentation](https://hindsight.vectorize.io)
* [pgvector template](https://docs.controlplane.com/template-catalog/templates/pgvector.md)
* [PostgreSQL Highly Available template](https://docs.controlplane.com/template-catalog/templates/postgres-highly-available.md)
