diff --git a/docs/memory-cognee.md b/docs/memory-cognee.md index 6980513f5..1a82f6e1b 100644 --- a/docs/memory-cognee.md +++ b/docs/memory-cognee.md @@ -16,7 +16,7 @@ Cognee is an AI memory framework that: - Extracts entities (people, places, concepts) from documents - Builds a knowledge graph of relationships - Enables semantic search with LLM-powered reasoning -- Supports multiple search modes (insights, chunks, summaries) +- Supports multiple search modes (GRAPH_COMPLETION, chunks, summaries) Learn more at [docs.cognee.ai](https://docs.cognee.ai/). @@ -24,57 +24,18 @@ Learn more at [docs.cognee.ai](https://docs.cognee.ai/). ### Option 1: Local Docker (Recommended) -Run Cognee locally using Docker Compose: - -**Step 1: Create docker-compose.yml** - -```yaml -version: '3.8' - -services: - cognee: - image: topoteretes/cognee:latest - container_name: cognee - ports: - - "8000:8000" - environment: - # Optional: Set API key for authentication - - COGNEE_API_KEY=your-local-api-key - # Database configuration - - DATABASE_URL=postgresql://cognee:cognee@postgres:5432/cognee - volumes: - - cognee_data:/app/data - depends_on: - - postgres - restart: unless-stopped - - postgres: - image: postgres:15-alpine - container_name: cognee-postgres - environment: - - POSTGRES_USER=cognee - - POSTGRES_PASSWORD=cognee - - POSTGRES_DB=cognee - volumes: - - postgres_data:/var/lib/postgresql/data - restart: unless-stopped - -volumes: - cognee_data: - postgres_data: -``` - -**Step 2: Start Cognee** +Use the repo example compose file: ```bash -docker-compose up -d +docker compose -f examples/cognee-docker-compose.yaml up -d ``` -**Step 3: Verify** +This binds to `127.0.0.1:8000`, so Cognee is only reachable from your machine. + +**Verify:** ```bash -curl http://localhost:8000/status -# Should return: {"status":"healthy"} +curl http://localhost:8000/health ``` ### Option 2: Cognee Cloud @@ -87,26 +48,49 @@ Use the hosted Cognee service: ## Configuration -Add Cognee memory configuration to your `~/.clawdbot/config.yaml`: +Clawdbot reads `~/.clawdbot/clawdbot.json` (JSON5). For local + secure setup, use an env var for the Cognee token and reference it in config. -### Basic Configuration (Docker Local) +### Step 1: Put tokens in `examples/.env` -```yaml -agents: - defaults: - memorySearch: - enabled: true - provider: cognee - sources: [memory] # or [memory, sessions] - cognee: - baseUrl: http://localhost:8000 - datasetName: clawdbot - searchType: insights # or "chunks", "summaries" - maxResults: 6 - autoCognify: true +```bash +LLM_API_KEY="your-llm-api-key" +COGNEE_API_KEY="your-cognee-access-token" +CLAWDBOT_GATEWAY_TOKEN="your-random-gateway-token" ``` -### Cloud Configuration +### Step 2: Configure Cognee in `~/.clawdbot/clawdbot.json` + +```json5 +{ + agents: { + defaults: { + memorySearch: { + enabled: true, + provider: "cognee", + sources: ["memory", "sessions"], + experimental: { sessionMemory: true }, + cognee: { + baseUrl: "http://localhost:8000", + apiKey: "${COGNEE_API_KEY}", + datasetName: "clawdbot", + searchType: "GRAPH_COMPLETION", + maxResults: 6, + autoCognify: true, + timeoutSeconds: 180 + } + } + } + } +} +``` + +### Step 3: Start the gateway with env loaded + +```zsh +set -a; source "examples/.env"; set +a; pnpm clawdbot gateway --port 18789 --token "$CLAWDBOT_GATEWAY_TOKEN" --verbose +``` + +### Cloud Configuration (tbt) ```yaml agents: @@ -119,7 +103,7 @@ agents: baseUrl: https://cognee--cognee-saas-backend-serve.modal.run apiKey: your-api-key-here # Required for cloud datasetName: clawdbot - searchType: insights + searchType: GRAPH_COMPLETION maxResults: 8 autoCognify: true timeoutSeconds: 60 @@ -130,22 +114,21 @@ agents: | Option | Type | Default | Description | |--------|------|---------|-------------| | `baseUrl` | string | `http://localhost:8000` | Cognee API endpoint | -| `apiKey` | string | - | API key (required for cloud, optional for local) | +| `apiKey` | string | - | Access token (required if Cognee auth is enabled) | | `datasetName` | string | `"clawdbot"` | Dataset name for organizing memories | -| `searchType` | string | `"insights"` | Search mode: `insights`, `chunks`, or `summaries` | +| `searchType` | string | `"GRAPH_COMPLETION"` | Search mode: `GRAPH_COMPLETION`, `chunks`, or `summaries` | | `maxResults` | number | `6` | Maximum search results returned | | `autoCognify` | boolean | `true` | Auto-process documents after adding | | `cognifyBatchSize` | number | `100` | Batch size for processing | -| `timeoutSeconds` | number | `30` | Request timeout in seconds | +| `timeoutSeconds` | number | `180` | Request timeout in seconds | ## Search Types -Cognee offers three search modes: +Cognee offers these modes: -### Insights (Recommended) +### GRAPH_COMPLETION Best for: **High-level understanding and reasoning** -- Returns AI-generated insights from knowledge graph -- Combines multiple related facts +- Returns graph-completion outputs over the knowledge graph - Good for: "What projects am I working on?" or "Summarize my notes about X" ### Chunks @@ -181,20 +164,11 @@ agents: sessionMemory: true ``` -### Manual Sync - -Force a memory sync: +### Manual Sync and Status ```bash -# Not yet implemented - coming soon -clawdbot memory sync --provider cognee -``` - -### Check Status - -```bash -# Not yet implemented - coming soon -clawdbot memory status --provider cognee +# Force sync + cognify for the current agent +clawdbot memory status --index --json ``` ## How It Works @@ -231,7 +205,7 @@ clawdbot memory status --provider cognee **Solutions**: 1. Verify Docker is running: `docker ps | grep cognee` 2. Check Cognee logs: `docker logs cognee` -3. Test manually: `curl http://localhost:8000/status` +3. Test manually: `curl http://localhost:8000/health` 4. Ensure port 8000 is not blocked ### Slow Performance @@ -250,6 +224,15 @@ clawdbot memory status --provider cognee 3. Process fewer files at once 4. Clear old datasets via Cognee API +### 401 Unauthorized (Cognee auth) + +**Cause**: Cognee auth is enabled, but Clawdbot is missing/using an invalid `COGNEE_API_KEY`. + +**Fix**: +1. Log in to Cognee and get a fresh access token. +2. Update `COGNEE_API_KEY` in `examples/.env`. +3. Restart the gateway with env loaded (see Step 3 above). + ## Advanced Configuration ### Per-Agent Override @@ -265,7 +248,7 @@ agents: memorySearch: provider: cognee # Override for this agent cognee: - searchType: insights + searchType: GRAPH_COMPLETION maxResults: 10 ``` @@ -285,7 +268,7 @@ Add health checks to docker-compose.yml: services: cognee: healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8000/status"] + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 @@ -313,15 +296,13 @@ Mount volumes for persistence: ```yaml volumes: - - ./cognee_data:/app/data + - ./cognee_data:/app/cognee/.cognee_system - ./cognee_logs:/app/logs ``` ## Roadmap Planned features: -- [ ] `clawdbot memory status --provider cognee` command -- [ ] `clawdbot memory sync --provider cognee` command - [ ] Hybrid mode (Cognee + SQLite) - [ ] Graph visualization export - [ ] Manual entity management diff --git a/examples/.env.template b/examples/.env.template new file mode 100644 index 000000000..3bf48a935 --- /dev/null +++ b/examples/.env.template @@ -0,0 +1,7 @@ +LLM_API_KEY= +ENABLE_BACKEND_ACCESS_CONTROL="false" + + +COGNEE_API_KEY= + +CLAWDBOT_GATEWAY_TOKEN= \ No newline at end of file diff --git a/examples/cognee-clawdbot-config.yaml b/examples/cognee-clawdbot-config.yaml new file mode 100644 index 000000000..69aa763d3 --- /dev/null +++ b/examples/cognee-clawdbot-config.yaml @@ -0,0 +1,35 @@ +# Sample Clawdbot config for testing Cognee integration +# Copy this to ~/.clawdbot/config.yaml inside the dev container +# +# Inside container: +# mkdir -p ~/.clawdbot +# cp /app/examples/cognee-clawdbot-config.yaml ~/.clawdbot/config.yaml + +agents: + defaults: + # Enable Cognee as the memory provider + memorySearch: + enabled: true + provider: cognee + sources: [memory] # Start with just memory files + + cognee: + # Inside Docker network, Cognee is at this address + baseUrl: http://cognee:8000 + # No API key needed for local Docker setup + # apiKey: "" + + # Dataset name for organizing memories + datasetName: clawdbot-test + + # Search mode: "insights" (recommended), "chunks", or "summaries" + searchType: insights + + # Max results per search + maxResults: 6 + + # Auto-process documents after adding + autoCognify: true + + # Request timeout + timeoutSeconds: 30 diff --git a/examples/cognee-config.yaml b/examples/cognee-config.yaml index b8abc922b..4279c0add 100644 --- a/examples/cognee-config.yaml +++ b/examples/cognee-config.yaml @@ -21,8 +21,8 @@ agents: # Dataset name for organizing memories datasetName: clawdbot - # Search mode: "insights" (recommended), "chunks", or "summaries" - searchType: insights + # Search mode: "GRAPH_COMPLETION" (recommended), "chunks", or "summaries" + searchType: GRAPH_COMPLETION # Maximum search results to return maxResults: 6 diff --git a/examples/cognee-docker-compose.yaml b/examples/cognee-docker-compose.yaml index 045f8a7dc..8269e14ec 100644 --- a/examples/cognee-docker-compose.yaml +++ b/examples/cognee-docker-compose.yaml @@ -1,77 +1,36 @@ -# Docker Compose configuration for running Cognee locally with Clawdbot +# Minimal Docker Compose for Cognee (local-only) # # Usage: -# 1. Copy this file to your preferred location -# 2. Run: docker-compose -f cognee-docker-compose.yaml up -d -# 3. Verify: curl http://localhost:8000/status +# 1. Export your LLM key: export LLM_API_KEY="your-openai-api-key" +# 2. Run: docker compose -f examples/cognee-docker-compose.yaml up -d +# 3. Verify: curl http://localhost:8000/health # 4. Configure Clawdbot with baseUrl: http://localhost:8000 # -# For production, see docs/memory-cognee.md for additional configuration +# Defaults (no extra DB setup): +# - Relational DB: SQLite (file-based) +# - Vector DB: LanceDB (file-based) +# - Graph DB: Kuzu (file-based) -version: '3.8' +version: "3.8" services: - # Cognee API server cognee: - image: topoteretes/cognee:latest + image: cognee/cognee:latest container_name: cognee ports: - - "8000:8000" + - "127.0.0.1:8000:8000" environment: - # Optional: Set API key for authentication - # Remove or comment out for local development without auth - - COGNEE_API_KEY=${COGNEE_API_KEY:-} - - # Database connection - - DATABASE_URL=postgresql://cognee:cognee@postgres:5432/cognee - - # Optional: Configure LLM provider for entity extraction - # Uncomment and set if you want to use specific providers - # - OPENAI_API_KEY=${OPENAI_API_KEY:-} - # - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + - LLM_API_KEY=${LLM_API_KEY} volumes: - - cognee_data:/app/data - - cognee_logs:/app/logs - depends_on: - postgres: - condition: service_healthy + - cognee_data:/app/cognee/.cognee_system restart: unless-stopped healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8000/status"] + test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s - networks: - - cognee-network - - # PostgreSQL database for Cognee - postgres: - image: postgres:15-alpine - container_name: cognee-postgres - environment: - - POSTGRES_USER=cognee - - POSTGRES_PASSWORD=cognee - - POSTGRES_DB=cognee - volumes: - - postgres_data:/var/lib/postgresql/data - restart: unless-stopped - healthcheck: - test: ["CMD-SHELL", "pg_isready -U cognee"] - interval: 10s - timeout: 5s - retries: 5 - networks: - - cognee-network volumes: cognee_data: driver: local - cognee_logs: - driver: local - postgres_data: - driver: local - -networks: - cognee-network: - driver: bridge diff --git a/examples/cognee-test-compose.yaml b/examples/cognee-test-compose.yaml new file mode 100644 index 000000000..865b334c8 --- /dev/null +++ b/examples/cognee-test-compose.yaml @@ -0,0 +1,117 @@ +# Docker Compose for testing Clawdbot + Cognee integration +# +# Usage: +# 1. cd examples +# 2. docker-compose -f cognee-test-compose.yaml up -d +# 3. docker-compose -f cognee-test-compose.yaml exec dev bash +# 4. Inside container: +# - pnpm install # Install dependencies +# - pnpm build # Build Clawdbot +# - pnpm test src/memory/cognee-* # Run unit tests +# - pnpm clawdbot agent --message "test" # Run Clawdbot CLI +# +# To tear down: docker-compose -f cognee-test-compose.yaml down -v + +version: '3.8' + +services: + # Cognee API server + cognee: + image: topoteretes/cognee:latest + container_name: cognee-test + ports: + - "8000:8000" + environment: + # No API key for local testing + - COGNEE_API_KEY= + # Database connection + - DATABASE_URL=postgresql://cognee:cognee@postgres:5432/cognee + volumes: + - cognee_data:/app/data + depends_on: + postgres: + condition: service_healthy + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8000/status"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 60s + networks: + - cognee-test-network + + # PostgreSQL database for Cognee + postgres: + image: postgres:15-alpine + container_name: cognee-test-postgres + environment: + - POSTGRES_USER=cognee + - POSTGRES_PASSWORD=cognee + - POSTGRES_DB=cognee + volumes: + - postgres_data:/var/lib/postgresql/data + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U cognee"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - cognee-test-network + + # Development container for running Clawdbot + tests + dev: + image: node:22-bookworm + container_name: clawdbot-cognee-dev + working_dir: /app + volumes: + # Mount the entire clawdbot repo + - ..:/app + # Use a separate node_modules volume to avoid conflicts + - node_modules:/app/node_modules + # Clawdbot config directory (persisted) + - clawdbot_config:/root/.clawdbot + environment: + - HOME=/root + - TERM=xterm-256color + # Point to Cognee running in Docker network + - COGNEE_BASE_URL=http://cognee:8000 + # Optional: Add your API keys here for full testing + # - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-} + # - OPENAI_API_KEY=${OPENAI_API_KEY:-} + depends_on: + cognee: + condition: service_healthy + stdin_open: true + tty: true + # Install pnpm and keep container running + command: > + bash -c " + corepack enable && + echo '=== Clawdbot + Cognee Test Environment ===' && + echo 'Cognee is available at: http://cognee:8000' && + echo '' && + echo 'Quick start:' && + echo ' pnpm install && pnpm build' && + echo ' pnpm test src/memory/cognee-client.test.ts' && + echo ' pnpm clawdbot --help' && + echo '' && + sleep infinity + " + networks: + - cognee-test-network + +volumes: + cognee_data: + driver: local + postgres_data: + driver: local + node_modules: + driver: local + clawdbot_config: + driver: local + +networks: + cognee-test-network: + driver: bridge diff --git a/examples/test-cognee.sh b/examples/test-cognee.sh new file mode 100644 index 000000000..09a9f1f3c --- /dev/null +++ b/examples/test-cognee.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# Test script for Cognee integration +# Run this inside the dev container after starting docker-compose + +set -e + +echo "=== Clawdbot + Cognee Integration Test ===" +echo "" + +# 1. Check if Cognee is reachable +echo "1. Testing Cognee connection..." +if curl -sf http://cognee:8000/status > /dev/null 2>&1; then + echo " ✓ Cognee is healthy at http://cognee:8000" + curl -s http://cognee:8000/status | head -c 200 + echo "" +else + echo " ✗ Cannot reach Cognee at http://cognee:8000" + echo " Make sure Cognee container is running and healthy" + exit 1 +fi +echo "" + +# 2. Install dependencies if needed +if [ ! -d "node_modules/.pnpm" ]; then + echo "2. Installing dependencies..." + pnpm install +else + echo "2. Dependencies already installed" +fi +echo "" + +# 3. Run unit tests +echo "3. Running Cognee unit tests..." +pnpm test src/memory/cognee-client.test.ts src/memory/cognee-provider.test.ts +echo "" + +# 4. Quick integration check (manual API test) +echo "4. Quick API smoke test..." +echo " Adding test data to Cognee..." + +# Add test data +RESULT=$(curl -sf -X POST http://cognee:8000/add \ + -H "Content-Type: application/json" \ + -d '{ + "data": "Test memory entry: The capital of France is Paris. This is a test document for Clawdbot integration.", + "dataset_name": "clawdbot-test" + }' 2>&1) || true + +if echo "$RESULT" | grep -q "dataset"; then + echo " ✓ Data added successfully" + echo " Response: $RESULT" +else + echo " ⚠ Add may have failed (this is OK if Cognee API differs)" + echo " Response: $RESULT" +fi +echo "" + +echo "=== Test Complete ===" +echo "" +echo "Next steps:" +echo " - The unit tests verify the client/provider logic" +echo " - For full integration, configure Clawdbot with:" +echo " memorySearch:" +echo " provider: cognee" +echo " cognee:" +echo " baseUrl: http://cognee:8000 # or http://localhost:8000 from host" diff --git a/src/agents/memory-search.ts b/src/agents/memory-search.ts index 8fbdb2614..9deaae970 100644 --- a/src/agents/memory-search.ts +++ b/src/agents/memory-search.ts @@ -35,7 +35,7 @@ export type ResolvedMemorySearchConfig = { baseUrl?: string; apiKey?: string; datasetName?: string; - searchType?: "insights" | "chunks" | "summaries"; + searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries"; maxResults?: number; timeoutSeconds?: number; autoCognify?: boolean; diff --git a/src/config/schema.ts b/src/config/schema.ts index b4ec8723b..aacd8f16d 100644 --- a/src/config/schema.ts +++ b/src/config/schema.ts @@ -501,7 +501,8 @@ const FIELD_HELP: Record = { 'Sources to index for memory search (default: ["memory"]; add "sessions" to include session transcripts).', "agents.defaults.memorySearch.experimental.sessionMemory": "Enable experimental session transcript indexing for memory search (default: false).", - "agents.defaults.memorySearch.provider": 'Embedding provider ("openai", "gemini", or "local").', + "agents.defaults.memorySearch.provider": + 'Embedding provider ("openai", "gemini", "local", or "cognee").', "agents.defaults.memorySearch.remote.baseUrl": "Custom base URL for remote embeddings (OpenAI-compatible proxies or Gemini overrides).", "agents.defaults.memorySearch.remote.apiKey": "Custom API key for the remote embedding provider.", @@ -517,10 +518,12 @@ const FIELD_HELP: Record = { "Polling interval in ms for batch status (default: 2000).", "agents.defaults.memorySearch.remote.batch.timeoutMinutes": "Timeout in minutes for batch indexing (default: 60).", + "agents.defaults.memorySearch.cognee": + "Cognee provider configuration (baseUrl, apiKey, datasetName, searchType, maxResults, autoCognify).", "agents.defaults.memorySearch.local.modelPath": "Local GGUF model path or hf: URI (node-llama-cpp).", "agents.defaults.memorySearch.fallback": - 'Fallback provider when embeddings fail ("openai", "gemini", "local", or "none").', + 'Fallback provider when embeddings fail ("openai", "gemini", "local", "cognee", or "none").', "agents.defaults.memorySearch.store.path": "SQLite index path (default: ~/.clawdbot/memory/{agentId}.sqlite).", "agents.defaults.memorySearch.store.vector.enabled": diff --git a/src/config/types.tools.ts b/src/config/types.tools.ts index 7506204b9..85254b80e 100644 --- a/src/config/types.tools.ts +++ b/src/config/types.tools.ts @@ -269,8 +269,8 @@ export type MemorySearchConfig = { apiKey?: string; /** Dataset name for organizing memories (default: "clawdbot"). */ datasetName?: string; - /** Search type: "insights", "chunks", or "summaries" (default: "insights"). */ - searchType?: "insights" | "chunks" | "summaries"; + /** Search type: "GRAPH_COMPLETION", "chunks", or "summaries" (default: "GRAPH_COMPLETION"). */ + searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries"; /** Max results per search query (default: 6). */ maxResults?: number; /** Timeout for API requests in seconds (default: 30). */ diff --git a/src/config/zod-schema.agent-runtime.ts b/src/config/zod-schema.agent-runtime.ts index 7a63e307d..1fcca8524 100644 --- a/src/config/zod-schema.agent-runtime.ts +++ b/src/config/zod-schema.agent-runtime.ts @@ -310,7 +310,9 @@ export const MemorySearchSchema = z }) .strict() .optional(), - provider: z.union([z.literal("openai"), z.literal("local"), z.literal("gemini")]).optional(), + provider: z + .union([z.literal("openai"), z.literal("local"), z.literal("gemini"), z.literal("cognee")]) + .optional(), remote: z .object({ baseUrl: z.string().optional(), @@ -330,9 +332,30 @@ export const MemorySearchSchema = z .strict() .optional(), fallback: z - .union([z.literal("openai"), z.literal("gemini"), z.literal("local"), z.literal("none")]) + .union([ + z.literal("openai"), + z.literal("gemini"), + z.literal("local"), + z.literal("cognee"), + z.literal("none"), + ]) .optional(), model: z.string().optional(), + cognee: z + .object({ + baseUrl: z.string().optional(), + apiKey: z.string().optional(), + datasetName: z.string().optional(), + searchType: z + .union([z.literal("GRAPH_COMPLETION"), z.literal("CHUNKS"), z.literal("SUMMARIES")]) + .optional(), + maxResults: z.number().int().positive().optional(), + autoCognify: z.boolean().optional(), + cognifyBatchSize: z.number().int().positive().optional(), + timeoutSeconds: z.number().int().positive().optional(), + }) + .strict() + .optional(), local: z .object({ modelPath: z.string().optional(), diff --git a/src/memory/cognee-client.test.ts b/src/memory/cognee-client.test.ts index 6bbdb9a3a..822de5dd4 100644 --- a/src/memory/cognee-client.test.ts +++ b/src/memory/cognee-client.test.ts @@ -2,21 +2,21 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest"; import { CogneeClient } from "./cognee-client.js"; import { request } from "undici"; -vi.mock("undici", () => ({ +vi.mock("undici", () => ({ request: vi.fn(), })); -describe("CogneeClient", () => { - beforeEach(() => { +describe("CogneeClient", () => { + beforeEach(() => { vi.clearAllMocks(); }); - afterEach(() => { + afterEach(() => { vi.restoreAllMocks(); }); - describe("add", () => { - it("should add data successfully", async () => { + describe("add", () => { + it("should add data successfully", async () => { const mockResponse = { statusCode: 200, body: { @@ -46,18 +46,18 @@ describe("CogneeClient", () => { message: "Data added successfully", }); expect(request).toHaveBeenCalledWith( - "http://localhost:8000/add", + "http://localhost:8000/api/v1/add", expect.objectContaining({ method: "POST", headers: expect.objectContaining({ - "Content-Type": "application/json", + Authorization: "Bearer test-key", "X-Api-Key": "test-key", }), }), ); }); - it("should handle errors", async () => { + it("should handle errors", async () => { const mockResponse = { statusCode: 500, body: { @@ -77,8 +77,8 @@ describe("CogneeClient", () => { }); }); - describe("cognify", () => { - it("should run cognify successfully", async () => { + describe("cognify", () => { + it("should run cognify successfully", async () => { const mockResponse = { statusCode: 200, body: { @@ -106,8 +106,8 @@ describe("CogneeClient", () => { }); }); - describe("search", () => { - it("should search successfully", async () => { + describe("search", () => { + it("should search successfully", async () => { const mockResponse = { statusCode: 200, body: { @@ -121,7 +121,7 @@ describe("CogneeClient", () => { }, ], query: "test query", - search_type: "insights", + search_type: "GRAPH_COMPLETION", }), text: vi.fn(), }, @@ -132,7 +132,7 @@ describe("CogneeClient", () => { const result = await client.search({ queryText: "test query", - searchType: "insights", + searchType: "GRAPH_COMPLETION", }); expect(result.results).toHaveLength(1); @@ -145,14 +145,14 @@ describe("CogneeClient", () => { expect(result.query).toBe("test query"); }); - it("should use default search type", async () => { + it("should use default search type", async () => { const mockResponse = { statusCode: 200, body: { json: vi.fn().mockResolvedValue({ results: [], query: "test", - search_type: "insights", + search_type: "GRAPH_COMPLETION", }), text: vi.fn(), }, @@ -165,28 +165,18 @@ describe("CogneeClient", () => { expect(request).toHaveBeenCalledWith( expect.any(String), expect.objectContaining({ - body: expect.stringContaining('"search_type":"insights"'), + body: expect.stringContaining('"searchType":"GRAPH_COMPLETION"'), }), ); }); }); - describe("status", () => { - it("should get status successfully", async () => { + describe("status", () => { + it("should get status successfully", async () => { const mockResponse = { statusCode: 200, body: { - json: vi.fn().mockResolvedValue({ - status: "healthy", - version: "1.0.0", - datasets: [ - { - id: "dataset-1", - name: "test-dataset", - document_count: 10, - }, - ], - }), + json: vi.fn().mockResolvedValue({ status: "healthy" }), text: vi.fn(), }, }; @@ -198,20 +188,12 @@ describe("CogneeClient", () => { expect(result).toEqual({ status: "healthy", - version: "1.0.0", - datasets: [ - { - id: "dataset-1", - name: "test-dataset", - documentCount: 10, - }, - ], }); }); }); - describe("healthCheck", () => { - it("should return true when status is successful", async () => { + describe("healthCheck", () => { + it("should return true when status is successful", async () => { const mockResponse = { statusCode: 200, body: { @@ -227,7 +209,7 @@ describe("CogneeClient", () => { expect(result).toBe(true); }); - it("should return false when status fails", async () => { + it("should return false when status fails", async () => { vi.mocked(request).mockRejectedValue(new Error("Connection failed")); const client = new CogneeClient(); diff --git a/src/memory/cognee-client.ts b/src/memory/cognee-client.ts index c3ec3747a..c442460d6 100644 --- a/src/memory/cognee-client.ts +++ b/src/memory/cognee-client.ts @@ -1,10 +1,12 @@ -import { request } from "undici"; +import { Blob } from "buffer"; +import { FormData, request } from "undici"; import { createSubsystemLogger } from "../logging/subsystem.js"; const log = createSubsystemLogger("cognee"); const DEFAULT_BASE_URL = "http://localhost:8000"; const DEFAULT_TIMEOUT_MS = 30_000; +const API_PREFIX = "/api/v1"; export type CogneeClientConfig = { baseUrl?: string; @@ -35,7 +37,7 @@ export type CogneeCognifyResponse = { export type CogneeSearchRequest = { queryText: string; - searchType?: "insights" | "chunks" | "summaries"; + searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries"; datasetIds?: string[]; }; @@ -43,7 +45,7 @@ export type CogneeSearchResult = { id: string; text: string; score: number; - metadata?: Record<string, unknown>; + metadata?: Record; }; export type CogneeSearchResponse = { @@ -55,13 +57,15 @@ export type CogneeSearchResponse = { export type CogneeStatusResponse = { status: string; version?: string; - datasets?: Array<{ + datasets?: Array<{ id: string; name: string; documentCount?: number; - }>; + }>; }; +type CogneeSearchApiType = "SUMMARIES" | "CHUNKS" | "GRAPH_COMPLETION"; + export class CogneeClient { private readonly baseUrl: string; private readonly apiKey?: string; @@ -73,12 +77,11 @@ export class CogneeClient { this.timeoutMs = config.timeoutMs || DEFAULT_TIMEOUT_MS; } - async add(req: CogneeAddRequest): Promise<CogneeAddResponse> { - const url = `${this.baseUrl}/add`; - const headers: Record<string, string> = { - "Content-Type": "application/json", - }; + async add(req: CogneeAddRequest): Promise { + const url = `${this.baseUrl}${API_PREFIX}/add`; + const headers: Record = {}; if (this.apiKey) { + headers.Authorization = `Bearer ${this.apiKey}`; headers["X-Api-Key"] = this.apiKey; } @@ -89,23 +92,27 @@ export class CogneeClient { }); try { + const formData = new FormData(); + const blob = new Blob([req.data], { type: "text/plain" }); + formData.append("data", blob, "clawdbot-memory.txt"); + if (req.datasetName) { + formData.append("datasetName", req.datasetName); + } + if (req.datasetId) { + formData.append("datasetId", req.datasetId); + } + const response = await request(url, { method: "POST", headers, - body: JSON.stringify({ - data: req.data, - dataset_name: req.datasetName, - dataset_id: req.datasetId, - }), + body: formData, bodyTimeout: this.timeoutMs, headersTimeout: this.timeoutMs, }); if (response.statusCode !== 200) { const errorText = await response.body.text(); - throw new Error( - `Cognee add failed with status ${response.statusCode}: ${errorText}`, - ); + throw new Error(`Cognee add failed with status ${response.statusCode}: ${errorText}`); } const data = (await response.body.json()) as { @@ -127,12 +134,13 @@ export class CogneeClient { } } - async cognify(req: CogneeCognifyRequest = {}): Promise<CogneeCognifyResponse> { - const url = `${this.baseUrl}/cognify`; - const headers: Record<string, string> = { + async cognify(req: CogneeCognifyRequest = {}): Promise { + const url = `${this.baseUrl}${API_PREFIX}/cognify`; + const headers: Record = { "Content-Type": "application/json", }; if (this.apiKey) { + headers.Authorization = `Bearer ${this.apiKey}`; headers["X-Api-Key"] = this.apiKey; } @@ -143,7 +151,7 @@ export class CogneeClient { method: "POST", headers, body: JSON.stringify({ - dataset_ids: req.datasetIds, + datasetIds: req.datasetIds, }), bodyTimeout: this.timeoutMs, headersTimeout: this.timeoutMs, @@ -151,9 +159,7 @@ export class CogneeClient { if (response.statusCode !== 200) { const errorText = await response.body.text(); - throw new Error( - `Cognee cognify failed with status ${response.statusCode}: ${errorText}`, - ); + throw new Error(`Cognee cognify failed with status ${response.statusCode}: ${errorText}`); } const data = (await response.body.json()) as { @@ -173,12 +179,13 @@ export class CogneeClient { } } - async search(req: CogneeSearchRequest): Promise<CogneeSearchResponse> { - const url = `${this.baseUrl}/search`; - const headers: Record<string, string> = { + async search(req: CogneeSearchRequest): Promise { + const url = `${this.baseUrl}${API_PREFIX}/search`; + const headers: Record = { "Content-Type": "application/json", }; if (this.apiKey) { + headers.Authorization = `Bearer ${this.apiKey}`; headers["X-Api-Key"] = this.apiKey; } @@ -193,9 +200,9 @@ export class CogneeClient { method: "POST", headers, body: JSON.stringify({ - query_text: req.queryText, - search_type: req.searchType || "insights", - dataset_ids: req.datasetIds, + query: req.queryText, + searchType: this.mapSearchType(req.searchType), + datasetIds: req.datasetIds, }), bodyTimeout: this.timeoutMs, headersTimeout: this.timeoutMs, @@ -203,31 +210,16 @@ export class CogneeClient { if (response.statusCode !== 200) { const errorText = await response.body.text(); - throw new Error( - `Cognee search failed with status ${response.statusCode}: ${errorText}`, - ); + throw new Error(`Cognee search failed with status ${response.statusCode}: ${errorText}`); } - const data = (await response.body.json()) as { - results: Array<{ - id: string; - text: string; - score: number; - metadata?: Record<string, unknown>; - }>; - query: string; - search_type: string; - }; + const data = (await response.body.json()) as unknown; + const results = this.normalizeSearchResults(data); return { - results: data.results.map((r) => ({ - id: r.id, - text: r.text, - score: r.score, - metadata: r.metadata, - })), - query: data.query, - searchType: data.search_type, + results, + query: req.queryText, + searchType: req.searchType || "GRAPH_COMPLETION", }; } catch (error) { log.error("Failed to search Cognee", { error }); @@ -237,10 +229,11 @@ export class CogneeClient { } } - async status(): Promise<CogneeStatusResponse> { - const url = `${this.baseUrl}/status`; - const headers: Record<string, string> = {}; + async status(): Promise { + const url = `${this.baseUrl}/health`; + const headers: Record = {}; if (this.apiKey) { + headers.Authorization = `Bearer ${this.apiKey}`; headers["X-Api-Key"] = this.apiKey; } @@ -256,29 +249,13 @@ export class CogneeClient { if (response.statusCode !== 200) { const errorText = await response.body.text(); - throw new Error( - `Cognee status failed with status ${response.statusCode}: ${errorText}`, - ); + throw new Error(`Cognee status failed with status ${response.statusCode}: ${errorText}`); } - const data = (await response.body.json()) as { - status: string; - version?: string; - datasets?: Array<{ - id: string; - name: string; - document_count?: number; - }>; - }; + const data = (await response.body.json()) as { status?: string }; return { - status: data.status, - version: data.version, - datasets: data.datasets?.map((d) => ({ - id: d.id, - name: d.name, - documentCount: d.document_count, - })), + status: data.status || "healthy", }; } catch (error) { log.error("Failed to get Cognee status", { error }); @@ -288,7 +265,7 @@ export class CogneeClient { } } - async healthCheck(): Promise<boolean> { + async healthCheck(): Promise { try { await this.status(); return true; @@ -296,4 +273,56 @@ export class CogneeClient { return false; } } + + private mapSearchType(type?: CogneeSearchRequest["searchType"]): CogneeSearchApiType { + switch (type) { + case "chunks": + return "CHUNKS"; + case "summaries": + return "SUMMARIES"; + case "GRAPH_COMPLETION": + default: + return "GRAPH_COMPLETION"; + } + } + + private normalizeSearchResults(data: unknown): CogneeSearchResult[] { + if (Array.isArray(data)) { + return data.map((item, index) => { + if (typeof item === "string") { + return { id: `result-${index}`, text: item, score: 0 }; + } + + if (item && typeof item === "object") { + const record = item as Record; + const raw = + record.search_result ?? record.result ?? record.context ?? record.text ?? record; + const text = typeof raw === "string" ? raw : JSON.stringify(raw, null, 2); + const metadata = + record.dataset_name || record.dataset_id + ? { + datasetName: record.dataset_name, + datasetId: record.dataset_id, + } + : undefined; + return { id: `result-${index}`, text, score: 0, metadata }; + } + + return { + id: `result-${index}`, + text: String(item), + score: 0, + }; + }); + } + + if (data && typeof data === "object" && "results" in data) { + const results = (data as { results?: unknown }).results; + if (Array.isArray(results)) { + return this.normalizeSearchResults(results); + } + } + + return []; + } } diff --git a/src/memory/cognee-provider.test.ts b/src/memory/cognee-provider.test.ts index 2b6bd64b6..d0bd31bad 100644 --- a/src/memory/cognee-provider.test.ts +++ b/src/memory/cognee-provider.test.ts @@ -2,8 +2,8 @@ import { describe, expect, it, vi, beforeEach } from "vitest"; import { CogneeMemoryProvider } from "./cognee-provider.js"; import type { ClawdbotConfig } from "../config/config.js"; -vi.mock("./cognee-client.js", () => ({ - CogneeClient: vi.fn().mockImplementation(() => ({ +vi.mock("./cognee-client.js", () => ({ + CogneeClient: vi.fn().mockImplementation(() => ({ healthCheck: vi.fn().mockResolvedValue(true), add: vi.fn().mockResolvedValue({ datasetId: "test-dataset-id", @@ -27,7 +27,7 @@ vi.mock("./cognee-client.js", () => ({ }, ], query: "test query", - searchType: "insights", + searchType: "GRAPH_COMPLETION", }), status: vi.fn().mockResolvedValue({ status: "healthy", @@ -43,13 +43,13 @@ vi.mock("./cognee-client.js", () => ({ })), })); -vi.mock("./internal.js", () => ({ +vi.mock("./internal.js", () => ({ listMemoryFiles: vi.fn().mockResolvedValue([]), buildFileEntry: vi.fn(), hashText: vi.fn().mockReturnValue("test-hash"), })); -vi.mock("node:fs/promises", () => ({ +vi.mock("node:fs/promises", () => ({ default: { readdir: vi.fn().mockResolvedValue([]), readFile: vi.fn().mockResolvedValue("Test file content"), @@ -57,7 +57,7 @@ vi.mock("node:fs/promises", () => ({ }, })); -describe("CogneeMemoryProvider", () => { +describe("CogneeMemoryProvider", () => { const mockConfig: ClawdbotConfig = { agents: { defaults: { @@ -66,18 +66,18 @@ describe("CogneeMemoryProvider", () => { }, }; - beforeEach(() => { + beforeEach(() => { vi.clearAllMocks(); }); - describe("constructor", () => { - it("should initialize with default configuration", () => { + describe("constructor", () => { + it("should initialize with default configuration", () => { const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); expect(provider).toBeDefined(); }); - it("should initialize with custom configuration", () => { + it("should initialize with custom configuration", () => { const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"], { baseUrl: "http://custom:8000", apiKey: "custom-key", @@ -90,8 +90,8 @@ describe("CogneeMemoryProvider", () => { }); }); - describe("healthCheck", () => { - it("should perform health check", async () => { + describe("healthCheck", () => { + it("should perform health check", async () => { const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); const healthy = await provider.healthCheck(); @@ -100,8 +100,8 @@ describe("CogneeMemoryProvider", () => { }); }); - describe("search", () => { - it("should search and transform results", async () => { + describe("search", () => { + it("should search and transform results", async () => { const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); const results = await provider.search("test query"); @@ -115,13 +115,10 @@ describe("CogneeMemoryProvider", () => { }); }); - it("should respect maxResults setting", async () => { - const provider = new CogneeMemoryProvider( - mockConfig, - "test-agent", - ["memory"], - { maxResults: 5 }, - ); + it("should respect maxResults setting", async () => { + const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"], { + maxResults: 5, + }); const results = await provider.search("test query"); @@ -129,16 +126,16 @@ describe("CogneeMemoryProvider", () => { }); }); - describe("cognify", () => { - it("should run cognify", async () => { + describe("cognify", () => { + it("should run cognify", async () => { const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); await expect(provider.cognify()).resolves.not.toThrow(); }); }); - describe("getStatus", () => { - it("should return status information", async () => { + describe("getStatus", () => { + it("should return status information", async () => { const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); const status = await provider.getStatus(); diff --git a/src/memory/cognee-provider.ts b/src/memory/cognee-provider.ts index 0f6050b66..11f224411 100644 --- a/src/memory/cognee-provider.ts +++ b/src/memory/cognee-provider.ts @@ -5,22 +5,20 @@ import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import type { MemorySearchResult } from "./index.js"; -import { - CogneeClient, - type CogneeClientConfig, - type CogneeSearchResult, -} from "./cognee-client.js"; +import { CogneeClient, type CogneeClientConfig, type CogneeSearchResult } from "./cognee-client.js"; import { buildFileEntry, hashText, listMemoryFiles, + isMemoryPath, + normalizeRelPath, type MemoryFileEntry, } from "./internal.js"; const log = createSubsystemLogger("cognee-provider"); const DEFAULT_DATASET_NAME = "clawdbot"; -const DEFAULT_SEARCH_TYPE = "insights"; +const DEFAULT_SEARCH_TYPE = "GRAPH_COMPLETION"; const DEFAULT_MAX_RESULTS = 6; const DEFAULT_TIMEOUT_SECONDS = 30; const DEFAULT_AUTO_COGNIFY = true; @@ -31,7 +29,7 @@ export type CogneeProviderConfig = { baseUrl?: string; apiKey?: string; datasetName?: string; - searchType?: "insights" | "chunks" | "summaries"; + searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries"; maxResults?: number; timeoutSeconds?: number; autoCognify?: boolean; @@ -46,18 +44,18 @@ export class CogneeMemoryProvider { private readonly agentId: string; private readonly workspaceDir: string; private readonly datasetName: string; - private readonly searchType: "insights" | "chunks" | "summaries"; + private readonly searchType: "GRAPH_COMPLETION" | "chunks" | "summaries"; private readonly maxResults: number; private readonly autoCognify: boolean; private readonly cognifyBatchSize: number; - private readonly sources: Set<CogneeMemorySource>; + private readonly sources: Set; private datasetId?: string; - private syncedFiles = new Map<string, string>(); // path -> hash + private syncedFiles = new Map(); // path -> hash constructor( cfg: ClawdbotConfig, agentId: string, - sources: Array<CogneeMemorySource>, + sources: Array, config: CogneeProviderConfig = {}, ) { const timeoutMs = (config.timeoutSeconds || DEFAULT_TIMEOUT_SECONDS) * 1000; @@ -86,11 +84,15 @@ export class CogneeMemoryProvider { }); } - async healthCheck(): Promise<boolean> { + async healthCheck(): Promise { return await this.client.healthCheck(); } - async sync(): Promise<void> { + async sync(params?: { + reason?: string; + force?: boolean; + progress?: (update: { completed: number; total: number; label?: string }) => void; + }): Promise { log.info("Starting Cognee memory sync", { agentId: this.agentId }); let addedCount = 0; @@ -108,7 +110,7 @@ export class CogneeMemoryProvider { } // Run cognify if auto-enabled and files were added - if (this.autoCognify && addedCount > 0) { + if (this.autoCognify && addedCount > 0) { log.info("Running cognify after sync", { addedCount }); await this.cognify(); } @@ -117,9 +119,20 @@ export class CogneeMemoryProvider { agentId: this.agentId, addedCount, }); + + if (params?.progress) { + params.progress({ + completed: addedCount, + total: addedCount, + label: params.reason ? `Synced (${params.reason})` : "Synced", + }); + } } - async search(query: string): Promise<MemorySearchResult[]> { + async search( + query: string, + opts?: { maxResults?: number; minScore?: number; sessionKey?: string }, + ): Promise { log.debug("Searching Cognee memory", { query, searchType: this.searchType }); try { @@ -129,9 +142,12 @@ export class CogneeMemoryProvider { datasetIds: this.datasetId ? [this.datasetId] : undefined, }); + const maxResults = opts?.maxResults ?? this.maxResults; + const minScore = opts?.minScore ?? 0; const results: MemorySearchResult[] = response.results - .slice(0, this.maxResults) - .map((r) => this.transformResult(r)); + .map((r) => this.transformResult(r)) + .filter((r) => r.score >= minScore) + .slice(0, maxResults); log.debug("Cognee search completed", { query, resultCount: results.length }); return results; @@ -141,7 +157,7 @@ export class CogneeMemoryProvider { } } - async cognify(): Promise<void> { + async cognify(): Promise { try { const response = await this.client.cognify({ datasetIds: this.datasetId ? [this.datasetId] : undefined, @@ -153,16 +169,16 @@ export class CogneeMemoryProvider { } } - async getStatus(): Promise<{ + async getStatus(): Promise<{ connected: boolean; datasetId?: string; datasetName: string; syncedFileCount: number; version?: string; - }> { + }> { try { const status = await this.client.status(); - const dataset = status.datasets?.find((d) => d.name === this.datasetName); + const dataset = status.datasets?.find((d) => d.name === this.datasetName); return { connected: true, @@ -181,7 +197,98 @@ export class CogneeMemoryProvider { } } - private async collectMemoryFiles(): Promise<MemoryFileEntry[]> { + status(): { + files: number; + chunks: number; + dirty: boolean; + workspaceDir: string; + dbPath: string; + provider: string; + model: string; + requestedProvider: string; + sources: Array; + sourceCounts: Array<{ source: CogneeMemorySource; files: number; chunks: number }>; + cache?: { enabled: boolean; entries?: number; maxEntries?: number }; + fts?: { enabled: boolean; available: boolean; error?: string }; + fallback?: { from: string; reason?: string }; + vector?: { + enabled: boolean; + available?: boolean; + extensionPath?: string; + loadError?: string; + dims?: number; + }; + batch?: { + enabled: boolean; + failures: number; + limit: number; + wait: boolean; + concurrency: number; + pollIntervalMs: number; + timeoutMs: number; + lastError?: string; + lastProvider?: string; + }; + } { + const sources = Array.from(this.sources); + const files = this.syncedFiles.size; + return { + files, + chunks: 0, + dirty: false, + workspaceDir: this.workspaceDir, + dbPath: "cognee", + provider: "cognee", + model: this.searchType, + requestedProvider: "cognee", + sources, + sourceCounts: sources.map((source) => ({ source, files, chunks: 0 })), + vector: { + enabled: false, + available: false, + }, + fts: { + enabled: false, + available: false, + }, + }; + } + + async readFile(params: { + relPath: string; + from?: number; + lines?: number; + }): Promise<{ text: string; path: string }> { + const relPath = normalizeRelPath(params.relPath); + if (!relPath || !isMemoryPath(relPath)) { + throw new Error("path required"); + } + const absPath = path.resolve(this.workspaceDir, relPath); + if (!absPath.startsWith(this.workspaceDir)) { + throw new Error("path escapes workspace"); + } + const content = await fs.readFile(absPath, "utf-8"); + if (!params.from && !params.lines) { + return { text: content, path: relPath }; + } + const lines = content.split("\n"); + const start = Math.max(1, params.from ?? 1); + const count = Math.max(1, params.lines ?? lines.length); + const slice = lines.slice(start - 1, start - 1 + count); + return { text: slice.join("\n"), path: relPath }; + } + + async probeEmbeddingAvailability(): Promise<{ ok: boolean; error?: string }> { + return { ok: false, error: "Cognee provider does not use embeddings." }; + } + + async probeVectorAvailability(): Promise { + return false; + } + + async close(): Promise {} + + private async collectMemoryFiles(): Promise { const files: MemoryFileEntry[] = []; const memoryPaths = await listMemoryFiles(this.workspaceDir); @@ -197,12 +304,9 @@ export class CogneeMemoryProvider { return files; } - private async collectSessionFiles(): Promise<MemoryFileEntry[]> { + private async collectSessionFiles(): Promise { const files: MemoryFileEntry[] = []; - const transcriptsDir = resolveSessionTranscriptsDirForAgent( - this.cfg, - this.agentId, - ); + const transcriptsDir = resolveSessionTranscriptsDirForAgent(this.agentId); try { const entries = await fs.readdir(transcriptsDir, { withFileTypes: true }); @@ -227,20 +331,17 @@ export class CogneeMemoryProvider { } } } catch (error) { - log.debug("No session transcripts directory", { transcriptsDir }); + log.debug("No session transcripts directory", { transcriptsDir, error }); } return files; } - private async syncFiles( - files: MemoryFileEntry[], - source: CogneeMemorySource, - ): Promise<number> { + private async syncFiles(files: MemoryFileEntry[], source: CogneeMemorySource): Promise { let addedCount = 0; const batchSize = this.cognifyBatchSize; - for (let i = 0; i < files.length; i += batchSize) { + for (let i = 0; i < files.length; i += batchSize) { const batch = files.slice(i, i + batchSize); for (const file of batch) { @@ -295,7 +396,7 @@ export class CogneeMemoryProvider { // Truncate snippet to max chars let snippet = result.text; - if (snippet.length > SNIPPET_MAX_CHARS) { + if (snippet.length > SNIPPET_MAX_CHARS) { snippet = snippet.slice(0, SNIPPET_MAX_CHARS) + "..."; } @@ -313,9 +414,9 @@ export class CogneeMemoryProvider { export async function createCogneeProvider( cfg: ClawdbotConfig, agentId: string, - sources: Array<CogneeMemorySource>, + sources: Array, config: CogneeProviderConfig = {}, -): Promise<CogneeMemoryProvider> { +): Promise { const provider = new CogneeMemoryProvider(cfg, agentId, sources, config); // Verify connection diff --git a/src/memory/manager.ts b/src/memory/manager.ts index 9a9991d10..5fbc3ceea 100644 --- a/src/memory/manager.ts +++ b/src/memory/manager.ts @@ -183,13 +183,15 @@ export class MemoryIndexManager { const key = `${agentId}:${workspaceDir}:${JSON.stringify(settings)}`; const existing = INDEX_CACHE.get(key); if (existing) return existing; + const provider = settings.provider === "cognee" ? "auto" : settings.provider; + const fallback = settings.fallback === "cognee" ? "none" : settings.fallback; const providerResult = await createEmbeddingProvider({ config: cfg, agentDir: resolveAgentDir(cfg, agentId), - provider: settings.provider, + provider, remote: settings.remote, model: settings.model, - fallback: settings.fallback, + fallback, local: settings.local, }); const manager = new MemoryIndexManager({ @@ -197,7 +199,7 @@ export class MemoryIndexManager { cfg, agentId, workspaceDir, - settings, + settings: { ...settings, provider, fallback }, providerResult, }); INDEX_CACHE.set(key, manager); @@ -1261,7 +1263,7 @@ export class MemoryIndexManager { } private async activateFallbackProvider(reason: string): Promise { - const fallback = this.settings.fallback; + const fallback = this.settings.fallback === "cognee" ? "none" : this.settings.fallback; if (!fallback || fallback === "none" || fallback === this.provider.id) return false; if (this.fallbackFrom) return false; const fallbackFrom = this.provider.id as "openai" | "gemini" | "local";