fix: resolve remaining Dependabot security alerts (#833)

* fix: resolve remaining Dependabot security alerts

- Regenerate package-lock.json so npm overrides take effect
  (serialize-javascript, handlebars, path-to-regexp, brace-expansion)
- Upgrade Pygments 2.19.2 -> 2.20.0 in crewai and integration-tests
  lockfiles (fixes ReDoS via GUID matching)

* fix: resolve duplicate alembic revision ID d6e7f8a9b0c1

Two migrations shared the same revision ID: the merge migration
(drop_documents_metadata_column) and the trigram index migration
(case_insensitive_entities_trgm_index). Assign a new unique ID
to the trigram migration and update the downstream dependency.

* chore: fix lint formatting for generated and existing files
This commit is contained in:
Derek Bouius 2026-04-01 11:22:38 -04:00 committed by GitHub
parent 1a1fb35cb0
commit 300d089b6a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 67 additions and 76 deletions

View file

@ -4,8 +4,8 @@ The previous GIN trigram index on canonical_name was case-sensitive, causing
"Alice" and "alice" to have different trigram sets. This recreates it on "Alice" and "alice" to have different trigram sets. This recreates it on
LOWER(canonical_name) so the % operator matches case-insensitively. LOWER(canonical_name) so the % operator matches case-insensitively.
Revision ID: d6e7f8a9b0c1 Revision ID: 2eee35aa3cfc
Revises: c5d6e7f8a9b0 Revises: d6e7f8a9b0c1
Create Date: 2026-03-31 Create Date: 2026-03-31
""" """
@ -13,8 +13,8 @@ from collections.abc import Sequence
from alembic import context, op from alembic import context, op
revision: str = "d6e7f8a9b0c1" revision: str = "2eee35aa3cfc"
down_revision: str | Sequence[str] | None = "c5d6e7f8a9b0" down_revision: str | Sequence[str] | None = "d6e7f8a9b0c1"
branch_labels: str | Sequence[str] | None = None branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None
@ -27,7 +27,7 @@ def _get_schema_prefix() -> str:
def upgrade() -> None: def upgrade() -> None:
schema = _get_schema_prefix() schema = _get_schema_prefix()
# Drop the old case-sensitive trigram index # Drop the old case-sensitive trigram index
op.execute(f"DROP INDEX IF EXISTS entities_canonical_name_trgm_idx") op.execute("DROP INDEX IF EXISTS entities_canonical_name_trgm_idx")
# Create case-insensitive trigram index on LOWER(canonical_name) # Create case-insensitive trigram index on LOWER(canonical_name)
op.execute( op.execute(
f"CREATE INDEX IF NOT EXISTS entities_canonical_name_lower_trgm_idx " f"CREATE INDEX IF NOT EXISTS entities_canonical_name_lower_trgm_idx "
@ -36,7 +36,7 @@ def upgrade() -> None:
def downgrade() -> None: def downgrade() -> None:
op.execute(f"DROP INDEX IF EXISTS entities_canonical_name_lower_trgm_idx") op.execute("DROP INDEX IF EXISTS entities_canonical_name_lower_trgm_idx")
schema = _get_schema_prefix() schema = _get_schema_prefix()
# Restore original case-sensitive index # Restore original case-sensitive index
op.execute( op.execute(

View file

@ -21,7 +21,7 @@ from alembic import context, op
from sqlalchemy import text from sqlalchemy import text
revision: str = "a4b5c6d7e8f9" revision: str = "a4b5c6d7e8f9"
down_revision: str | Sequence[str] | None = "d6e7f8a9b0c1" down_revision: str | Sequence[str] | None = "2eee35aa3cfc"
branch_labels: str | Sequence[str] | None = None branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None depends_on: str | Sequence[str] | None = None

View file

@ -4340,13 +4340,16 @@ class MemoryEngine(MemoryEngineInterface):
# Get entity information — only for visible units # Get entity information — only for visible units
if unit_ids: if unit_ids:
unit_entities = await conn.fetch(f""" unit_entities = await conn.fetch(
f"""
SELECT ue.unit_id, e.canonical_name SELECT ue.unit_id, e.canonical_name
FROM {fq_table("unit_entities")} ue FROM {fq_table("unit_entities")} ue
JOIN {fq_table("entities")} e ON ue.entity_id = e.id JOIN {fq_table("entities")} e ON ue.entity_id = e.id
WHERE ue.unit_id = ANY($1::uuid[]) WHERE ue.unit_id = ANY($1::uuid[])
ORDER BY ue.unit_id ORDER BY ue.unit_id
""", unit_ids) """,
unit_ids,
)
else: else:
unit_entities = [] unit_entities = []

View file

@ -71,9 +71,9 @@ function heatColor(t: number): string {
// Brighter, more luminous stops — like stars in a night sky // Brighter, more luminous stops — like stars in a night sky
// teal glow → bright cyan → white-blue // teal glow → bright cyan → white-blue
const stops = [ const stops = [
[80, 200, 205], // bright teal [80, 200, 205], // bright teal
[100, 210, 255], // bright cyan [100, 210, 255], // bright cyan
[140, 180, 255], // light blue-white [140, 180, 255], // light blue-white
]; ];
const seg = v * (stops.length - 1); const seg = v * (stops.length - 1);
const i = Math.min(Math.floor(seg), stops.length - 2); const i = Math.min(Math.floor(seg), stops.length - 2);
@ -678,7 +678,10 @@ export function Constellation({
const meta = node.metadata as Record<string, any> | undefined; const meta = node.metadata as Record<string, any> | undefined;
const fullText = meta?.text || node.label || node.id; const fullText = meta?.text || node.label || node.id;
const entities: string[] = meta?.entities const entities: string[] = meta?.entities
? String(meta.entities).split(",").map((e: string) => e.trim()).filter(Boolean) ? String(meta.entities)
.split(",")
.map((e: string) => e.trim())
.filter(Boolean)
: []; : [];
const nodeColor = preparedNodes[idx].heatColor; const nodeColor = preparedNodes[idx].heatColor;
const linkCount = preparedNodes[idx].linkCount; const linkCount = preparedNodes[idx].linkCount;
@ -717,9 +720,10 @@ export function Constellation({
if (date) { if (date) {
html += `<div style="${rowStyle}"><span style="${labelStyle}">Date</span><span style="${valStyle}">${date}</span></div>`; html += `<div style="${rowStyle}"><span style="${labelStyle}">Date</span><span style="${valStyle}">${date}</span></div>`;
} else if (occurredStart) { } else if (occurredStart) {
const timeRange = occurredEnd && occurredEnd !== occurredStart const timeRange =
? `${occurredStart.slice(0, 10)}${occurredEnd.slice(0, 10)}` occurredEnd && occurredEnd !== occurredStart
: occurredStart.slice(0, 10); ? `${occurredStart.slice(0, 10)}${occurredEnd.slice(0, 10)}`
: occurredStart.slice(0, 10);
html += `<div style="${rowStyle}"><span style="${labelStyle}">Occurred</span><span style="${valStyle}">${timeRange}</span></div>`; html += `<div style="${rowStyle}"><span style="${labelStyle}">Occurred</span><span style="${valStyle}">${timeRange}</span></div>`;
} }
@ -882,10 +886,7 @@ export function Constellation({
: { position: "relative", width: "100%", height: `${height}px` } : { position: "relative", width: "100%", height: `${height}px` }
} }
> >
<canvas <canvas ref={canvasRef} style={{ width: "100%", height: "100%", display: "block" }} />
ref={canvasRef}
style={{ width: "100%", height: "100%", display: "block" }}
/>
{/* Fullscreen toggle */} {/* Fullscreen toggle */}
<button <button
@ -917,14 +918,32 @@ export function Constellation({
title={isFullscreen ? "Exit fullscreen (Esc)" : "Enter fullscreen"} title={isFullscreen ? "Exit fullscreen (Esc)" : "Enter fullscreen"}
> >
{isFullscreen ? ( {isFullscreen ? (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="4 14 10 14 10 20" /> <polyline points="4 14 10 14 10 20" />
<polyline points="20 10 14 10 14 4" /> <polyline points="20 10 14 10 14 4" />
<line x1="14" y1="10" x2="21" y2="3" /> <line x1="14" y1="10" x2="21" y2="3" />
<line x1="3" y1="21" x2="10" y2="14" /> <line x1="3" y1="21" x2="10" y2="14" />
</svg> </svg>
) : ( ) : (
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<polyline points="15 3 21 3 21 9" /> <polyline points="15 3 21 3 21 9" />
<polyline points="9 21 3 21 3 15" /> <polyline points="9 21 3 21 3 15" />
<line x1="21" y1="3" x2="14" y2="10" /> <line x1="21" y1="3" x2="14" y2="10" />

View file

@ -488,11 +488,11 @@ wheels = [
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.19.2" version = "2.20.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
] ]
[[package]] [[package]]

View file

@ -2813,11 +2813,11 @@ wheels = [
[[package]] [[package]]
name = "pygments" name = "pygments"
version = "2.19.2" version = "2.20.0"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" },
] ]
[[package]] [[package]]

64
package-lock.json generated
View file

@ -8555,15 +8555,6 @@
"npm": ">=7.0.0" "npm": ">=7.0.0"
} }
}, },
"node_modules/@redocly/openapi-core/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@redocly/openapi-core/node_modules/minimatch": { "node_modules/@redocly/openapi-core/node_modules/minimatch": {
"version": "5.1.9", "version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
@ -10560,15 +10551,6 @@
"typescript": ">=4.8.4 <6.0.0" "typescript": ">=4.8.4 <6.0.0"
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.9", "version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
@ -12072,13 +12054,12 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0"
"concat-map": "0.0.1"
} }
}, },
"node_modules/braces": { "node_modules/braces": {
@ -12992,12 +12973,6 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/confbox": { "node_modules/confbox": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.2.tgz",
@ -16028,12 +16003,6 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/express/node_modules/path-to-regexp": {
"version": "0.1.12",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
"integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
"license": "MIT"
},
"node_modules/express/node_modules/range-parser": { "node_modules/express/node_modules/range-parser": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -16967,9 +16936,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/handlebars": { "node_modules/handlebars": {
"version": "4.7.8", "version": "4.7.9",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
"integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@ -22764,9 +22733,9 @@
"license": "MIT" "license": "MIT"
}, },
"node_modules/node-forge": { "node_modules/node-forge": {
"version": "1.3.3", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz", "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.4.0.tgz",
"integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==", "integrity": "sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==",
"license": "(BSD-3-Clause OR GPL-2.0)", "license": "(BSD-3-Clause OR GPL-2.0)",
"engines": { "engines": {
"node": ">= 6.13.0" "node": ">= 6.13.0"
@ -27154,9 +27123,9 @@
} }
}, },
"node_modules/serialize-javascript": { "node_modules/serialize-javascript": {
"version": "7.0.4", "version": "7.0.5",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.4.tgz", "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz",
"integrity": "sha512-DuGdB+Po43Q5Jxwpzt1lhyFSYKryqoNjQSA9M92tyw0lyHIOur+XCalOUe0KTJpyqzT8+fQ5A0Jf7vCx/NKmIg==", "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==",
"license": "BSD-3-Clause", "license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=20.0.0" "node": ">=20.0.0"
@ -28424,15 +28393,14 @@
} }
}, },
"node_modules/terser-webpack-plugin": { "node_modules/terser-webpack-plugin": {
"version": "5.3.16", "version": "5.4.0",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.16.tgz", "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.4.0.tgz",
"integrity": "sha512-h9oBFCWrq78NyWWVcSwZarJkZ01c2AyGrzs1crmHZO3QUg9D61Wu4NPjBy69n7JqylFF5y+CsUZYmYEIZ3mR+Q==", "integrity": "sha512-Bn5vxm48flOIfkdl5CaD2+1CiUVbonWQ3KQPyP7/EuIl9Gbzq/gQFOzaMFUEgVjB1396tcK0SG8XcNJ/2kDH8g==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@jridgewell/trace-mapping": "^0.3.25", "@jridgewell/trace-mapping": "^0.3.25",
"jest-worker": "^27.4.5", "jest-worker": "^27.4.5",
"schema-utils": "^4.3.0", "schema-utils": "^4.3.0",
"serialize-javascript": "^6.0.2",
"terser": "^5.31.1" "terser": "^5.31.1"
}, },
"engines": { "engines": {

View file

@ -612,6 +612,7 @@ Controls the retain (memory ingestion) pipeline.
| `HINDSIGHT_API_RETAIN_CUSTOM_INSTRUCTIONS` | Full prompt override for fact extraction (only used when mode is `custom`). Replaces built-in extraction rules entirely. | - | | `HINDSIGHT_API_RETAIN_CUSTOM_INSTRUCTIONS` | Full prompt override for fact extraction (only used when mode is `custom`). Replaces built-in extraction rules entirely. | - |
| `HINDSIGHT_API_RETAIN_EXTRACT_CAUSAL_LINKS` | Extract causal relationships between facts | `true` | | `HINDSIGHT_API_RETAIN_EXTRACT_CAUSAL_LINKS` | Extract causal relationships between facts | `true` |
| `HINDSIGHT_API_RETAIN_BATCH_ENABLED` | Use LLM Batch API for fact extraction (50% cost savings, only with async operations) | `false` | | `HINDSIGHT_API_RETAIN_BATCH_ENABLED` | Use LLM Batch API for fact extraction (50% cost savings, only with async operations) | `false` |
| `HINDSIGHT_API_RETAIN_MAX_CONCURRENT` | Max concurrent retain DB phases (HNSW reads + writes). Limits I/O contention during high-concurrency ingestion. | `4` |
| `HINDSIGHT_API_RETAIN_BATCH_TOKENS` | Max characters per sub-batch for async retain auto-splitting | `10000` | | `HINDSIGHT_API_RETAIN_BATCH_TOKENS` | Max characters per sub-batch for async retain auto-splitting | `10000` |
| `HINDSIGHT_API_RETAIN_ENTITY_LOOKUP` | Entity lookup method during retain: `full` (exact match) or `trigram` (fuzzy trigram matching) | `trigram` | | `HINDSIGHT_API_RETAIN_ENTITY_LOOKUP` | Entity lookup method during retain: `full` (exact match) or `trigram` (fuzzy trigram matching) | `trigram` |
| `HINDSIGHT_API_RETAIN_DEFAULT_STRATEGY` | Default retain strategy name. When set, all retain calls without an explicit `strategy` parameter use this strategy. | - | | `HINDSIGHT_API_RETAIN_DEFAULT_STRATEGY` | Default retain strategy name. When set, all retain calls without an explicit `strategy` parameter use this strategy. | - |