add updates

This commit is contained in:
Hande 2026-01-28 00:59:37 +01:00
parent c1aabbb7ee
commit a57cc024c6
16 changed files with 636 additions and 334 deletions

View File

@ -16,7 +16,7 @@ Cognee is an AI memory framework that:
- Extracts entities (people, places, concepts) from documents - Extracts entities (people, places, concepts) from documents
- Builds a knowledge graph of relationships - Builds a knowledge graph of relationships
- Enables semantic search with LLM-powered reasoning - 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/). 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) ### Option 1: Local Docker (Recommended)
Run Cognee locally using Docker Compose: Use the repo example compose file:
**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**
```bash ```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 ```bash
curl http://localhost:8000/status curl http://localhost:8000/health
# Should return: {"status":"healthy"}
``` ```
### Option 2: Cognee Cloud ### Option 2: Cognee Cloud
@ -87,26 +48,49 @@ Use the hosted Cognee service:
## Configuration ## 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 ```bash
agents: LLM_API_KEY="your-llm-api-key"
defaults: COGNEE_API_KEY="your-cognee-access-token"
memorySearch: CLAWDBOT_GATEWAY_TOKEN="your-random-gateway-token"
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
``` ```
### 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 ```yaml
agents: agents:
@ -119,7 +103,7 @@ agents:
baseUrl: https://cognee--cognee-saas-backend-serve.modal.run baseUrl: https://cognee--cognee-saas-backend-serve.modal.run
apiKey: your-api-key-here # Required for cloud apiKey: your-api-key-here # Required for cloud
datasetName: clawdbot datasetName: clawdbot
searchType: insights searchType: GRAPH_COMPLETION
maxResults: 8 maxResults: 8
autoCognify: true autoCognify: true
timeoutSeconds: 60 timeoutSeconds: 60
@ -130,22 +114,21 @@ agents:
| Option | Type | Default | Description | | Option | Type | Default | Description |
|--------|------|---------|-------------| |--------|------|---------|-------------|
| `baseUrl` | string | `http://localhost:8000` | Cognee API endpoint | | `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 | | `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 | | `maxResults` | number | `6` | Maximum search results returned |
| `autoCognify` | boolean | `true` | Auto-process documents after adding | | `autoCognify` | boolean | `true` | Auto-process documents after adding |
| `cognifyBatchSize` | number | `100` | Batch size for processing | | `cognifyBatchSize` | number | `100` | Batch size for processing |
| `timeoutSeconds` | number | `30` | Request timeout in seconds | | `timeoutSeconds` | number | `180` | Request timeout in seconds |
## Search Types ## Search Types
Cognee offers three search modes: Cognee offers these modes:
### Insights (Recommended) ### GRAPH_COMPLETION
Best for: **High-level understanding and reasoning** Best for: **High-level understanding and reasoning**
- Returns AI-generated insights from knowledge graph - Returns graph-completion outputs over the knowledge graph
- Combines multiple related facts
- Good for: "What projects am I working on?" or "Summarize my notes about X" - Good for: "What projects am I working on?" or "Summarize my notes about X"
### Chunks ### Chunks
@ -181,20 +164,11 @@ agents:
sessionMemory: true sessionMemory: true
``` ```
### Manual Sync ### Manual Sync and Status
Force a memory sync:
```bash ```bash
# Not yet implemented - coming soon # Force sync + cognify for the current agent
clawdbot memory sync --provider cognee clawdbot memory status --index --json
```
### Check Status
```bash
# Not yet implemented - coming soon
clawdbot memory status --provider cognee
``` ```
## How It Works ## How It Works
@ -231,7 +205,7 @@ clawdbot memory status --provider cognee
**Solutions**: **Solutions**:
1. Verify Docker is running: `docker ps | grep cognee` 1. Verify Docker is running: `docker ps | grep cognee`
2. Check Cognee logs: `docker logs 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 4. Ensure port 8000 is not blocked
### Slow Performance ### Slow Performance
@ -250,6 +224,15 @@ clawdbot memory status --provider cognee
3. Process fewer files at once 3. Process fewer files at once
4. Clear old datasets via Cognee API 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 ## Advanced Configuration
### Per-Agent Override ### Per-Agent Override
@ -265,7 +248,7 @@ agents:
memorySearch: memorySearch:
provider: cognee # Override for this agent provider: cognee # Override for this agent
cognee: cognee:
searchType: insights searchType: GRAPH_COMPLETION
maxResults: 10 maxResults: 10
``` ```
@ -285,7 +268,7 @@ Add health checks to docker-compose.yml:
services: services:
cognee: cognee:
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/status"] test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
@ -313,15 +296,13 @@ Mount volumes for persistence:
```yaml ```yaml
volumes: volumes:
- ./cognee_data:/app/data - ./cognee_data:/app/cognee/.cognee_system
- ./cognee_logs:/app/logs - ./cognee_logs:/app/logs
``` ```
## Roadmap ## Roadmap
Planned features: Planned features:
- [ ] `clawdbot memory status --provider cognee` command
- [ ] `clawdbot memory sync --provider cognee` command
- [ ] Hybrid mode (Cognee + SQLite) - [ ] Hybrid mode (Cognee + SQLite)
- [ ] Graph visualization export - [ ] Graph visualization export
- [ ] Manual entity management - [ ] Manual entity management

7
examples/.env.template Normal file
View File

@ -0,0 +1,7 @@
LLM_API_KEY=
ENABLE_BACKEND_ACCESS_CONTROL="false"
COGNEE_API_KEY=
CLAWDBOT_GATEWAY_TOKEN=

View File

@ -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

View File

@ -21,8 +21,8 @@ agents:
# Dataset name for organizing memories # Dataset name for organizing memories
datasetName: clawdbot datasetName: clawdbot
# Search mode: "insights" (recommended), "chunks", or "summaries" # Search mode: "GRAPH_COMPLETION" (recommended), "chunks", or "summaries"
searchType: insights searchType: GRAPH_COMPLETION
# Maximum search results to return # Maximum search results to return
maxResults: 6 maxResults: 6

View File

@ -1,77 +1,36 @@
# Docker Compose configuration for running Cognee locally with Clawdbot # Minimal Docker Compose for Cognee (local-only)
# #
# Usage: # Usage:
# 1. Copy this file to your preferred location # 1. Export your LLM key: export LLM_API_KEY="your-openai-api-key"
# 2. Run: docker-compose -f cognee-docker-compose.yaml up -d # 2. Run: docker compose -f examples/cognee-docker-compose.yaml up -d
# 3. Verify: curl http://localhost:8000/status # 3. Verify: curl http://localhost:8000/health
# 4. Configure Clawdbot with baseUrl: http://localhost:8000 # 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: services:
# Cognee API server
cognee: cognee:
image: topoteretes/cognee:latest image: cognee/cognee:latest
container_name: cognee container_name: cognee
ports: ports:
- "8000:8000" - "127.0.0.1:8000:8000"
environment: environment:
# Optional: Set API key for authentication - LLM_API_KEY=${LLM_API_KEY}
# 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:-}
volumes: volumes:
- cognee_data:/app/data - cognee_data:/app/cognee/.cognee_system
- cognee_logs:/app/logs
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/status"] test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s interval: 30s
timeout: 10s timeout: 10s
retries: 3 retries: 3
start_period: 40s 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: volumes:
cognee_data: cognee_data:
driver: local driver: local
cognee_logs:
driver: local
postgres_data:
driver: local
networks:
cognee-network:
driver: bridge

View File

@ -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

66
examples/test-cognee.sh Normal file
View File

@ -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"

View File

@ -35,7 +35,7 @@ export type ResolvedMemorySearchConfig = {
baseUrl?: string; baseUrl?: string;
apiKey?: string; apiKey?: string;
datasetName?: string; datasetName?: string;
searchType?: "insights" | "chunks" | "summaries"; searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries";
maxResults?: number; maxResults?: number;
timeoutSeconds?: number; timeoutSeconds?: number;
autoCognify?: boolean; autoCognify?: boolean;

View File

@ -501,7 +501,8 @@ const FIELD_HELP: Record<string, string> = {
'Sources to index for memory search (default: ["memory"]; add "sessions" to include session transcripts).', 'Sources to index for memory search (default: ["memory"]; add "sessions" to include session transcripts).',
"agents.defaults.memorySearch.experimental.sessionMemory": "agents.defaults.memorySearch.experimental.sessionMemory":
"Enable experimental session transcript indexing for memory search (default: false).", "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": "agents.defaults.memorySearch.remote.baseUrl":
"Custom base URL for remote embeddings (OpenAI-compatible proxies or Gemini overrides).", "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.", "agents.defaults.memorySearch.remote.apiKey": "Custom API key for the remote embedding provider.",
@ -517,10 +518,12 @@ const FIELD_HELP: Record<string, string> = {
"Polling interval in ms for batch status (default: 2000).", "Polling interval in ms for batch status (default: 2000).",
"agents.defaults.memorySearch.remote.batch.timeoutMinutes": "agents.defaults.memorySearch.remote.batch.timeoutMinutes":
"Timeout in minutes for batch indexing (default: 60).", "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": "agents.defaults.memorySearch.local.modelPath":
"Local GGUF model path or hf: URI (node-llama-cpp).", "Local GGUF model path or hf: URI (node-llama-cpp).",
"agents.defaults.memorySearch.fallback": "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": "agents.defaults.memorySearch.store.path":
"SQLite index path (default: ~/.clawdbot/memory/{agentId}.sqlite).", "SQLite index path (default: ~/.clawdbot/memory/{agentId}.sqlite).",
"agents.defaults.memorySearch.store.vector.enabled": "agents.defaults.memorySearch.store.vector.enabled":

View File

@ -269,8 +269,8 @@ export type MemorySearchConfig = {
apiKey?: string; apiKey?: string;
/** Dataset name for organizing memories (default: "clawdbot"). */ /** Dataset name for organizing memories (default: "clawdbot"). */
datasetName?: string; datasetName?: string;
/** Search type: "insights", "chunks", or "summaries" (default: "insights"). */ /** Search type: "GRAPH_COMPLETION", "chunks", or "summaries" (default: "GRAPH_COMPLETION"). */
searchType?: "insights" | "chunks" | "summaries"; searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries";
/** Max results per search query (default: 6). */ /** Max results per search query (default: 6). */
maxResults?: number; maxResults?: number;
/** Timeout for API requests in seconds (default: 30). */ /** Timeout for API requests in seconds (default: 30). */

View File

@ -310,7 +310,9 @@ export const MemorySearchSchema = z
}) })
.strict() .strict()
.optional(), .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 remote: z
.object({ .object({
baseUrl: z.string().optional(), baseUrl: z.string().optional(),
@ -330,9 +332,30 @@ export const MemorySearchSchema = z
.strict() .strict()
.optional(), .optional(),
fallback: z 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(), .optional(),
model: z.string().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 local: z
.object({ .object({
modelPath: z.string().optional(), modelPath: z.string().optional(),

View File

@ -2,21 +2,21 @@ import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { CogneeClient } from "./cognee-client.js"; import { CogneeClient } from "./cognee-client.js";
import { request } from "undici"; import { request } from "undici";
vi.mock("undici", () =&gt; ({ vi.mock("undici", () => ({
request: vi.fn(), request: vi.fn(),
})); }));
describe("CogneeClient", () =&gt; { describe("CogneeClient", () => {
beforeEach(() =&gt; { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
afterEach(() =&gt; { afterEach(() => {
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
describe("add", () =&gt; { describe("add", () => {
it("should add data successfully", async () =&gt; { it("should add data successfully", async () => {
const mockResponse = { const mockResponse = {
statusCode: 200, statusCode: 200,
body: { body: {
@ -46,18 +46,18 @@ describe("CogneeClient", () =&gt; {
message: "Data added successfully", message: "Data added successfully",
}); });
expect(request).toHaveBeenCalledWith( expect(request).toHaveBeenCalledWith(
"http://localhost:8000/add", "http://localhost:8000/api/v1/add",
expect.objectContaining({ expect.objectContaining({
method: "POST", method: "POST",
headers: expect.objectContaining({ headers: expect.objectContaining({
"Content-Type": "application/json", Authorization: "Bearer test-key",
"X-Api-Key": "test-key", "X-Api-Key": "test-key",
}), }),
}), }),
); );
}); });
it("should handle errors", async () =&gt; { it("should handle errors", async () => {
const mockResponse = { const mockResponse = {
statusCode: 500, statusCode: 500,
body: { body: {
@ -77,8 +77,8 @@ describe("CogneeClient", () =&gt; {
}); });
}); });
describe("cognify", () =&gt; { describe("cognify", () => {
it("should run cognify successfully", async () =&gt; { it("should run cognify successfully", async () => {
const mockResponse = { const mockResponse = {
statusCode: 200, statusCode: 200,
body: { body: {
@ -106,8 +106,8 @@ describe("CogneeClient", () =&gt; {
}); });
}); });
describe("search", () =&gt; { describe("search", () => {
it("should search successfully", async () =&gt; { it("should search successfully", async () => {
const mockResponse = { const mockResponse = {
statusCode: 200, statusCode: 200,
body: { body: {
@ -121,7 +121,7 @@ describe("CogneeClient", () =&gt; {
}, },
], ],
query: "test query", query: "test query",
search_type: "insights", search_type: "GRAPH_COMPLETION",
}), }),
text: vi.fn(), text: vi.fn(),
}, },
@ -132,7 +132,7 @@ describe("CogneeClient", () =&gt; {
const result = await client.search({ const result = await client.search({
queryText: "test query", queryText: "test query",
searchType: "insights", searchType: "GRAPH_COMPLETION",
}); });
expect(result.results).toHaveLength(1); expect(result.results).toHaveLength(1);
@ -145,14 +145,14 @@ describe("CogneeClient", () =&gt; {
expect(result.query).toBe("test query"); expect(result.query).toBe("test query");
}); });
it("should use default search type", async () =&gt; { it("should use default search type", async () => {
const mockResponse = { const mockResponse = {
statusCode: 200, statusCode: 200,
body: { body: {
json: vi.fn().mockResolvedValue({ json: vi.fn().mockResolvedValue({
results: [], results: [],
query: "test", query: "test",
search_type: "insights", search_type: "GRAPH_COMPLETION",
}), }),
text: vi.fn(), text: vi.fn(),
}, },
@ -165,28 +165,18 @@ describe("CogneeClient", () =&gt; {
expect(request).toHaveBeenCalledWith( expect(request).toHaveBeenCalledWith(
expect.any(String), expect.any(String),
expect.objectContaining({ expect.objectContaining({
body: expect.stringContaining('"search_type":"insights"'), body: expect.stringContaining('"searchType":"GRAPH_COMPLETION"'),
}), }),
); );
}); });
}); });
describe("status", () =&gt; { describe("status", () => {
it("should get status successfully", async () =&gt; { it("should get status successfully", async () => {
const mockResponse = { const mockResponse = {
statusCode: 200, statusCode: 200,
body: { body: {
json: vi.fn().mockResolvedValue({ json: vi.fn().mockResolvedValue({ status: "healthy" }),
status: "healthy",
version: "1.0.0",
datasets: [
{
id: "dataset-1",
name: "test-dataset",
document_count: 10,
},
],
}),
text: vi.fn(), text: vi.fn(),
}, },
}; };
@ -198,20 +188,12 @@ describe("CogneeClient", () =&gt; {
expect(result).toEqual({ expect(result).toEqual({
status: "healthy", status: "healthy",
version: "1.0.0",
datasets: [
{
id: "dataset-1",
name: "test-dataset",
documentCount: 10,
},
],
}); });
}); });
}); });
describe("healthCheck", () =&gt; { describe("healthCheck", () => {
it("should return true when status is successful", async () =&gt; { it("should return true when status is successful", async () => {
const mockResponse = { const mockResponse = {
statusCode: 200, statusCode: 200,
body: { body: {
@ -227,7 +209,7 @@ describe("CogneeClient", () =&gt; {
expect(result).toBe(true); expect(result).toBe(true);
}); });
it("should return false when status fails", async () =&gt; { it("should return false when status fails", async () => {
vi.mocked(request).mockRejectedValue(new Error("Connection failed")); vi.mocked(request).mockRejectedValue(new Error("Connection failed"));
const client = new CogneeClient(); const client = new CogneeClient();

View File

@ -1,10 +1,12 @@
import { request } from "undici"; import { Blob } from "buffer";
import { FormData, request } from "undici";
import { createSubsystemLogger } from "../logging/subsystem.js"; import { createSubsystemLogger } from "../logging/subsystem.js";
const log = createSubsystemLogger("cognee"); const log = createSubsystemLogger("cognee");
const DEFAULT_BASE_URL = "http://localhost:8000"; const DEFAULT_BASE_URL = "http://localhost:8000";
const DEFAULT_TIMEOUT_MS = 30_000; const DEFAULT_TIMEOUT_MS = 30_000;
const API_PREFIX = "/api/v1";
export type CogneeClientConfig = { export type CogneeClientConfig = {
baseUrl?: string; baseUrl?: string;
@ -35,7 +37,7 @@ export type CogneeCognifyResponse = {
export type CogneeSearchRequest = { export type CogneeSearchRequest = {
queryText: string; queryText: string;
searchType?: "insights" | "chunks" | "summaries"; searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries";
datasetIds?: string[]; datasetIds?: string[];
}; };
@ -43,7 +45,7 @@ export type CogneeSearchResult = {
id: string; id: string;
text: string; text: string;
score: number; score: number;
metadata?: Record&lt;string, unknown&gt;; metadata?: Record<string, unknown>;
}; };
export type CogneeSearchResponse = { export type CogneeSearchResponse = {
@ -55,13 +57,15 @@ export type CogneeSearchResponse = {
export type CogneeStatusResponse = { export type CogneeStatusResponse = {
status: string; status: string;
version?: string; version?: string;
datasets?: Array&lt;{ datasets?: Array<{
id: string; id: string;
name: string; name: string;
documentCount?: number; documentCount?: number;
}&gt;; }>;
}; };
type CogneeSearchApiType = "SUMMARIES" | "CHUNKS" | "GRAPH_COMPLETION";
export class CogneeClient { export class CogneeClient {
private readonly baseUrl: string; private readonly baseUrl: string;
private readonly apiKey?: string; private readonly apiKey?: string;
@ -73,12 +77,11 @@ export class CogneeClient {
this.timeoutMs = config.timeoutMs || DEFAULT_TIMEOUT_MS; this.timeoutMs = config.timeoutMs || DEFAULT_TIMEOUT_MS;
} }
async add(req: CogneeAddRequest): Promise&lt;CogneeAddResponse&gt; { async add(req: CogneeAddRequest): Promise<CogneeAddResponse> {
const url = `${this.baseUrl}/add`; const url = `${this.baseUrl}${API_PREFIX}/add`;
const headers: Record&lt;string, string&gt; = { const headers: Record<string, string> = {};
"Content-Type": "application/json",
};
if (this.apiKey) { if (this.apiKey) {
headers.Authorization = `Bearer ${this.apiKey}`;
headers["X-Api-Key"] = this.apiKey; headers["X-Api-Key"] = this.apiKey;
} }
@ -89,23 +92,27 @@ export class CogneeClient {
}); });
try { 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, { const response = await request(url, {
method: "POST", method: "POST",
headers, headers,
body: JSON.stringify({ body: formData,
data: req.data,
dataset_name: req.datasetName,
dataset_id: req.datasetId,
}),
bodyTimeout: this.timeoutMs, bodyTimeout: this.timeoutMs,
headersTimeout: this.timeoutMs, headersTimeout: this.timeoutMs,
}); });
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
const errorText = await response.body.text(); const errorText = await response.body.text();
throw new Error( throw new Error(`Cognee add failed with status ${response.statusCode}: ${errorText}`);
`Cognee add failed with status ${response.statusCode}: ${errorText}`,
);
} }
const data = (await response.body.json()) as { const data = (await response.body.json()) as {
@ -127,12 +134,13 @@ export class CogneeClient {
} }
} }
async cognify(req: CogneeCognifyRequest = {}): Promise&lt;CogneeCognifyResponse&gt; { async cognify(req: CogneeCognifyRequest = {}): Promise<CogneeCognifyResponse> {
const url = `${this.baseUrl}/cognify`; const url = `${this.baseUrl}${API_PREFIX}/cognify`;
const headers: Record&lt;string, string&gt; = { const headers: Record<string, string> = {
"Content-Type": "application/json", "Content-Type": "application/json",
}; };
if (this.apiKey) { if (this.apiKey) {
headers.Authorization = `Bearer ${this.apiKey}`;
headers["X-Api-Key"] = this.apiKey; headers["X-Api-Key"] = this.apiKey;
} }
@ -143,7 +151,7 @@ export class CogneeClient {
method: "POST", method: "POST",
headers, headers,
body: JSON.stringify({ body: JSON.stringify({
dataset_ids: req.datasetIds, datasetIds: req.datasetIds,
}), }),
bodyTimeout: this.timeoutMs, bodyTimeout: this.timeoutMs,
headersTimeout: this.timeoutMs, headersTimeout: this.timeoutMs,
@ -151,9 +159,7 @@ export class CogneeClient {
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
const errorText = await response.body.text(); const errorText = await response.body.text();
throw new Error( throw new Error(`Cognee cognify failed with status ${response.statusCode}: ${errorText}`);
`Cognee cognify failed with status ${response.statusCode}: ${errorText}`,
);
} }
const data = (await response.body.json()) as { const data = (await response.body.json()) as {
@ -173,12 +179,13 @@ export class CogneeClient {
} }
} }
async search(req: CogneeSearchRequest): Promise&lt;CogneeSearchResponse&gt; { async search(req: CogneeSearchRequest): Promise<CogneeSearchResponse> {
const url = `${this.baseUrl}/search`; const url = `${this.baseUrl}${API_PREFIX}/search`;
const headers: Record&lt;string, string&gt; = { const headers: Record<string, string> = {
"Content-Type": "application/json", "Content-Type": "application/json",
}; };
if (this.apiKey) { if (this.apiKey) {
headers.Authorization = `Bearer ${this.apiKey}`;
headers["X-Api-Key"] = this.apiKey; headers["X-Api-Key"] = this.apiKey;
} }
@ -193,9 +200,9 @@ export class CogneeClient {
method: "POST", method: "POST",
headers, headers,
body: JSON.stringify({ body: JSON.stringify({
query_text: req.queryText, query: req.queryText,
search_type: req.searchType || "insights", searchType: this.mapSearchType(req.searchType),
dataset_ids: req.datasetIds, datasetIds: req.datasetIds,
}), }),
bodyTimeout: this.timeoutMs, bodyTimeout: this.timeoutMs,
headersTimeout: this.timeoutMs, headersTimeout: this.timeoutMs,
@ -203,31 +210,16 @@ export class CogneeClient {
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
const errorText = await response.body.text(); const errorText = await response.body.text();
throw new Error( throw new Error(`Cognee search failed with status ${response.statusCode}: ${errorText}`);
`Cognee search failed with status ${response.statusCode}: ${errorText}`,
);
} }
const data = (await response.body.json()) as { const data = (await response.body.json()) as unknown;
results: Array&lt;{ const results = this.normalizeSearchResults(data);
id: string;
text: string;
score: number;
metadata?: Record&lt;string, unknown&gt;;
}&gt;;
query: string;
search_type: string;
};
return { return {
results: data.results.map((r) =&gt; ({ results,
id: r.id, query: req.queryText,
text: r.text, searchType: req.searchType || "GRAPH_COMPLETION",
score: r.score,
metadata: r.metadata,
})),
query: data.query,
searchType: data.search_type,
}; };
} catch (error) { } catch (error) {
log.error("Failed to search Cognee", { error }); log.error("Failed to search Cognee", { error });
@ -237,10 +229,11 @@ export class CogneeClient {
} }
} }
async status(): Promise&lt;CogneeStatusResponse&gt; { async status(): Promise<CogneeStatusResponse> {
const url = `${this.baseUrl}/status`; const url = `${this.baseUrl}/health`;
const headers: Record&lt;string, string&gt; = {}; const headers: Record<string, string> = {};
if (this.apiKey) { if (this.apiKey) {
headers.Authorization = `Bearer ${this.apiKey}`;
headers["X-Api-Key"] = this.apiKey; headers["X-Api-Key"] = this.apiKey;
} }
@ -256,29 +249,13 @@ export class CogneeClient {
if (response.statusCode !== 200) { if (response.statusCode !== 200) {
const errorText = await response.body.text(); const errorText = await response.body.text();
throw new Error( throw new Error(`Cognee status failed with status ${response.statusCode}: ${errorText}`);
`Cognee status failed with status ${response.statusCode}: ${errorText}`,
);
} }
const data = (await response.body.json()) as { const data = (await response.body.json()) as { status?: string };
status: string;
version?: string;
datasets?: Array&lt;{
id: string;
name: string;
document_count?: number;
}&gt;;
};
return { return {
status: data.status, status: data.status || "healthy",
version: data.version,
datasets: data.datasets?.map((d) =&gt; ({
id: d.id,
name: d.name,
documentCount: d.document_count,
})),
}; };
} catch (error) { } catch (error) {
log.error("Failed to get Cognee status", { error }); log.error("Failed to get Cognee status", { error });
@ -288,7 +265,7 @@ export class CogneeClient {
} }
} }
async healthCheck(): Promise&lt;boolean&gt; { async healthCheck(): Promise<boolean> {
try { try {
await this.status(); await this.status();
return true; return true;
@ -296,4 +273,56 @@ export class CogneeClient {
return false; 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<string, unknown>;
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 [];
}
} }

View File

@ -2,8 +2,8 @@ import { describe, expect, it, vi, beforeEach } from "vitest";
import { CogneeMemoryProvider } from "./cognee-provider.js"; import { CogneeMemoryProvider } from "./cognee-provider.js";
import type { ClawdbotConfig } from "../config/config.js"; import type { ClawdbotConfig } from "../config/config.js";
vi.mock("./cognee-client.js", () =&gt; ({ vi.mock("./cognee-client.js", () => ({
CogneeClient: vi.fn().mockImplementation(() =&gt; ({ CogneeClient: vi.fn().mockImplementation(() => ({
healthCheck: vi.fn().mockResolvedValue(true), healthCheck: vi.fn().mockResolvedValue(true),
add: vi.fn().mockResolvedValue({ add: vi.fn().mockResolvedValue({
datasetId: "test-dataset-id", datasetId: "test-dataset-id",
@ -27,7 +27,7 @@ vi.mock("./cognee-client.js", () =&gt; ({
}, },
], ],
query: "test query", query: "test query",
searchType: "insights", searchType: "GRAPH_COMPLETION",
}), }),
status: vi.fn().mockResolvedValue({ status: vi.fn().mockResolvedValue({
status: "healthy", status: "healthy",
@ -43,13 +43,13 @@ vi.mock("./cognee-client.js", () =&gt; ({
})), })),
})); }));
vi.mock("./internal.js", () =&gt; ({ vi.mock("./internal.js", () => ({
listMemoryFiles: vi.fn().mockResolvedValue([]), listMemoryFiles: vi.fn().mockResolvedValue([]),
buildFileEntry: vi.fn(), buildFileEntry: vi.fn(),
hashText: vi.fn().mockReturnValue("test-hash"), hashText: vi.fn().mockReturnValue("test-hash"),
})); }));
vi.mock("node:fs/promises", () =&gt; ({ vi.mock("node:fs/promises", () => ({
default: { default: {
readdir: vi.fn().mockResolvedValue([]), readdir: vi.fn().mockResolvedValue([]),
readFile: vi.fn().mockResolvedValue("Test file content"), readFile: vi.fn().mockResolvedValue("Test file content"),
@ -57,7 +57,7 @@ vi.mock("node:fs/promises", () =&gt; ({
}, },
})); }));
describe("CogneeMemoryProvider", () =&gt; { describe("CogneeMemoryProvider", () => {
const mockConfig: ClawdbotConfig = { const mockConfig: ClawdbotConfig = {
agents: { agents: {
defaults: { defaults: {
@ -66,18 +66,18 @@ describe("CogneeMemoryProvider", () =&gt; {
}, },
}; };
beforeEach(() =&gt; { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
}); });
describe("constructor", () =&gt; { describe("constructor", () => {
it("should initialize with default configuration", () =&gt; { it("should initialize with default configuration", () => {
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
expect(provider).toBeDefined(); expect(provider).toBeDefined();
}); });
it("should initialize with custom configuration", () =&gt; { it("should initialize with custom configuration", () => {
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"], { const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"], {
baseUrl: "http://custom:8000", baseUrl: "http://custom:8000",
apiKey: "custom-key", apiKey: "custom-key",
@ -90,8 +90,8 @@ describe("CogneeMemoryProvider", () =&gt; {
}); });
}); });
describe("healthCheck", () =&gt; { describe("healthCheck", () => {
it("should perform health check", async () =&gt; { it("should perform health check", async () => {
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
const healthy = await provider.healthCheck(); const healthy = await provider.healthCheck();
@ -100,8 +100,8 @@ describe("CogneeMemoryProvider", () =&gt; {
}); });
}); });
describe("search", () =&gt; { describe("search", () => {
it("should search and transform results", async () =&gt; { it("should search and transform results", async () => {
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
const results = await provider.search("test query"); const results = await provider.search("test query");
@ -115,13 +115,10 @@ describe("CogneeMemoryProvider", () =&gt; {
}); });
}); });
it("should respect maxResults setting", async () =&gt; { it("should respect maxResults setting", async () => {
const provider = new CogneeMemoryProvider( const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"], {
mockConfig, maxResults: 5,
"test-agent", });
["memory"],
{ maxResults: 5 },
);
const results = await provider.search("test query"); const results = await provider.search("test query");
@ -129,16 +126,16 @@ describe("CogneeMemoryProvider", () =&gt; {
}); });
}); });
describe("cognify", () =&gt; { describe("cognify", () => {
it("should run cognify", async () =&gt; { it("should run cognify", async () => {
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
await expect(provider.cognify()).resolves.not.toThrow(); await expect(provider.cognify()).resolves.not.toThrow();
}); });
}); });
describe("getStatus", () =&gt; { describe("getStatus", () => {
it("should return status information", async () =&gt; { it("should return status information", async () => {
const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]); const provider = new CogneeMemoryProvider(mockConfig, "test-agent", ["memory"]);
const status = await provider.getStatus(); const status = await provider.getStatus();

View File

@ -5,22 +5,20 @@ import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js"; import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
import { createSubsystemLogger } from "../logging/subsystem.js"; import { createSubsystemLogger } from "../logging/subsystem.js";
import type { MemorySearchResult } from "./index.js"; import type { MemorySearchResult } from "./index.js";
import { import { CogneeClient, type CogneeClientConfig, type CogneeSearchResult } from "./cognee-client.js";
CogneeClient,
type CogneeClientConfig,
type CogneeSearchResult,
} from "./cognee-client.js";
import { import {
buildFileEntry, buildFileEntry,
hashText, hashText,
listMemoryFiles, listMemoryFiles,
isMemoryPath,
normalizeRelPath,
type MemoryFileEntry, type MemoryFileEntry,
} from "./internal.js"; } from "./internal.js";
const log = createSubsystemLogger("cognee-provider"); const log = createSubsystemLogger("cognee-provider");
const DEFAULT_DATASET_NAME = "clawdbot"; const DEFAULT_DATASET_NAME = "clawdbot";
const DEFAULT_SEARCH_TYPE = "insights"; const DEFAULT_SEARCH_TYPE = "GRAPH_COMPLETION";
const DEFAULT_MAX_RESULTS = 6; const DEFAULT_MAX_RESULTS = 6;
const DEFAULT_TIMEOUT_SECONDS = 30; const DEFAULT_TIMEOUT_SECONDS = 30;
const DEFAULT_AUTO_COGNIFY = true; const DEFAULT_AUTO_COGNIFY = true;
@ -31,7 +29,7 @@ export type CogneeProviderConfig = {
baseUrl?: string; baseUrl?: string;
apiKey?: string; apiKey?: string;
datasetName?: string; datasetName?: string;
searchType?: "insights" | "chunks" | "summaries"; searchType?: "GRAPH_COMPLETION" | "chunks" | "summaries";
maxResults?: number; maxResults?: number;
timeoutSeconds?: number; timeoutSeconds?: number;
autoCognify?: boolean; autoCognify?: boolean;
@ -46,18 +44,18 @@ export class CogneeMemoryProvider {
private readonly agentId: string; private readonly agentId: string;
private readonly workspaceDir: string; private readonly workspaceDir: string;
private readonly datasetName: string; private readonly datasetName: string;
private readonly searchType: "insights" | "chunks" | "summaries"; private readonly searchType: "GRAPH_COMPLETION" | "chunks" | "summaries";
private readonly maxResults: number; private readonly maxResults: number;
private readonly autoCognify: boolean; private readonly autoCognify: boolean;
private readonly cognifyBatchSize: number; private readonly cognifyBatchSize: number;
private readonly sources: Set&lt;CogneeMemorySource&gt;; private readonly sources: Set<CogneeMemorySource>;
private datasetId?: string; private datasetId?: string;
private syncedFiles = new Map&lt;string, string&gt;(); // path -&gt; hash private syncedFiles = new Map<string, string>(); // path -> hash
constructor( constructor(
cfg: ClawdbotConfig, cfg: ClawdbotConfig,
agentId: string, agentId: string,
sources: Array&lt;CogneeMemorySource&gt;, sources: Array<CogneeMemorySource>,
config: CogneeProviderConfig = {}, config: CogneeProviderConfig = {},
) { ) {
const timeoutMs = (config.timeoutSeconds || DEFAULT_TIMEOUT_SECONDS) * 1000; const timeoutMs = (config.timeoutSeconds || DEFAULT_TIMEOUT_SECONDS) * 1000;
@ -86,11 +84,15 @@ export class CogneeMemoryProvider {
}); });
} }
async healthCheck(): Promise&lt;boolean&gt; { async healthCheck(): Promise<boolean> {
return await this.client.healthCheck(); return await this.client.healthCheck();
} }
async sync(): Promise&lt;void&gt; { async sync(params?: {
reason?: string;
force?: boolean;
progress?: (update: { completed: number; total: number; label?: string }) => void;
}): Promise<void> {
log.info("Starting Cognee memory sync", { agentId: this.agentId }); log.info("Starting Cognee memory sync", { agentId: this.agentId });
let addedCount = 0; let addedCount = 0;
@ -108,7 +110,7 @@ export class CogneeMemoryProvider {
} }
// Run cognify if auto-enabled and files were added // Run cognify if auto-enabled and files were added
if (this.autoCognify &amp;&amp; addedCount &gt; 0) { if (this.autoCognify && addedCount > 0) {
log.info("Running cognify after sync", { addedCount }); log.info("Running cognify after sync", { addedCount });
await this.cognify(); await this.cognify();
} }
@ -117,9 +119,20 @@ export class CogneeMemoryProvider {
agentId: this.agentId, agentId: this.agentId,
addedCount, addedCount,
}); });
if (params?.progress) {
params.progress({
completed: addedCount,
total: addedCount,
label: params.reason ? `Synced (${params.reason})` : "Synced",
});
}
} }
async search(query: string): Promise&lt;MemorySearchResult[]&gt; { async search(
query: string,
opts?: { maxResults?: number; minScore?: number; sessionKey?: string },
): Promise<MemorySearchResult[]> {
log.debug("Searching Cognee memory", { query, searchType: this.searchType }); log.debug("Searching Cognee memory", { query, searchType: this.searchType });
try { try {
@ -129,9 +142,12 @@ export class CogneeMemoryProvider {
datasetIds: this.datasetId ? [this.datasetId] : undefined, datasetIds: this.datasetId ? [this.datasetId] : undefined,
}); });
const maxResults = opts?.maxResults ?? this.maxResults;
const minScore = opts?.minScore ?? 0;
const results: MemorySearchResult[] = response.results const results: MemorySearchResult[] = response.results
.slice(0, this.maxResults) .map((r) => this.transformResult(r))
.map((r) =&gt; this.transformResult(r)); .filter((r) => r.score >= minScore)
.slice(0, maxResults);
log.debug("Cognee search completed", { query, resultCount: results.length }); log.debug("Cognee search completed", { query, resultCount: results.length });
return results; return results;
@ -141,7 +157,7 @@ export class CogneeMemoryProvider {
} }
} }
async cognify(): Promise&lt;void&gt; { async cognify(): Promise<void> {
try { try {
const response = await this.client.cognify({ const response = await this.client.cognify({
datasetIds: this.datasetId ? [this.datasetId] : undefined, datasetIds: this.datasetId ? [this.datasetId] : undefined,
@ -153,16 +169,16 @@ export class CogneeMemoryProvider {
} }
} }
async getStatus(): Promise&lt;{ async getStatus(): Promise<{
connected: boolean; connected: boolean;
datasetId?: string; datasetId?: string;
datasetName: string; datasetName: string;
syncedFileCount: number; syncedFileCount: number;
version?: string; version?: string;
}&gt; { }> {
try { try {
const status = await this.client.status(); const status = await this.client.status();
const dataset = status.datasets?.find((d) =&gt; d.name === this.datasetName); const dataset = status.datasets?.find((d) => d.name === this.datasetName);
return { return {
connected: true, connected: true,
@ -181,7 +197,98 @@ export class CogneeMemoryProvider {
} }
} }
private async collectMemoryFiles(): Promise&lt;MemoryFileEntry[]&gt; { status(): {
files: number;
chunks: number;
dirty: boolean;
workspaceDir: string;
dbPath: string;
provider: string;
model: string;
requestedProvider: string;
sources: Array<CogneeMemorySource>;
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<boolean> {
return false;
}
async close(): Promise<void> {}
private async collectMemoryFiles(): Promise<MemoryFileEntry[]> {
const files: MemoryFileEntry[] = []; const files: MemoryFileEntry[] = [];
const memoryPaths = await listMemoryFiles(this.workspaceDir); const memoryPaths = await listMemoryFiles(this.workspaceDir);
@ -197,12 +304,9 @@ export class CogneeMemoryProvider {
return files; return files;
} }
private async collectSessionFiles(): Promise&lt;MemoryFileEntry[]&gt; { private async collectSessionFiles(): Promise<MemoryFileEntry[]> {
const files: MemoryFileEntry[] = []; const files: MemoryFileEntry[] = [];
const transcriptsDir = resolveSessionTranscriptsDirForAgent( const transcriptsDir = resolveSessionTranscriptsDirForAgent(this.agentId);
this.cfg,
this.agentId,
);
try { try {
const entries = await fs.readdir(transcriptsDir, { withFileTypes: true }); const entries = await fs.readdir(transcriptsDir, { withFileTypes: true });
@ -227,20 +331,17 @@ export class CogneeMemoryProvider {
} }
} }
} catch (error) { } catch (error) {
log.debug("No session transcripts directory", { transcriptsDir }); log.debug("No session transcripts directory", { transcriptsDir, error });
} }
return files; return files;
} }
private async syncFiles( private async syncFiles(files: MemoryFileEntry[], source: CogneeMemorySource): Promise<number> {
files: MemoryFileEntry[],
source: CogneeMemorySource,
): Promise&lt;number&gt; {
let addedCount = 0; let addedCount = 0;
const batchSize = this.cognifyBatchSize; const batchSize = this.cognifyBatchSize;
for (let i = 0; i &lt; files.length; i += batchSize) { for (let i = 0; i < files.length; i += batchSize) {
const batch = files.slice(i, i + batchSize); const batch = files.slice(i, i + batchSize);
for (const file of batch) { for (const file of batch) {
@ -295,7 +396,7 @@ export class CogneeMemoryProvider {
// Truncate snippet to max chars // Truncate snippet to max chars
let snippet = result.text; let snippet = result.text;
if (snippet.length &gt; SNIPPET_MAX_CHARS) { if (snippet.length > SNIPPET_MAX_CHARS) {
snippet = snippet.slice(0, SNIPPET_MAX_CHARS) + "..."; snippet = snippet.slice(0, SNIPPET_MAX_CHARS) + "...";
} }
@ -313,9 +414,9 @@ export class CogneeMemoryProvider {
export async function createCogneeProvider( export async function createCogneeProvider(
cfg: ClawdbotConfig, cfg: ClawdbotConfig,
agentId: string, agentId: string,
sources: Array&lt;CogneeMemorySource&gt;, sources: Array<CogneeMemorySource>,
config: CogneeProviderConfig = {}, config: CogneeProviderConfig = {},
): Promise&lt;CogneeMemoryProvider&gt; { ): Promise<CogneeMemoryProvider> {
const provider = new CogneeMemoryProvider(cfg, agentId, sources, config); const provider = new CogneeMemoryProvider(cfg, agentId, sources, config);
// Verify connection // Verify connection

View File

@ -183,13 +183,15 @@ export class MemoryIndexManager {
const key = `${agentId}:${workspaceDir}:${JSON.stringify(settings)}`; const key = `${agentId}:${workspaceDir}:${JSON.stringify(settings)}`;
const existing = INDEX_CACHE.get(key); const existing = INDEX_CACHE.get(key);
if (existing) return existing; if (existing) return existing;
const provider = settings.provider === "cognee" ? "auto" : settings.provider;
const fallback = settings.fallback === "cognee" ? "none" : settings.fallback;
const providerResult = await createEmbeddingProvider({ const providerResult = await createEmbeddingProvider({
config: cfg, config: cfg,
agentDir: resolveAgentDir(cfg, agentId), agentDir: resolveAgentDir(cfg, agentId),
provider: settings.provider, provider,
remote: settings.remote, remote: settings.remote,
model: settings.model, model: settings.model,
fallback: settings.fallback, fallback,
local: settings.local, local: settings.local,
}); });
const manager = new MemoryIndexManager({ const manager = new MemoryIndexManager({
@ -197,7 +199,7 @@ export class MemoryIndexManager {
cfg, cfg,
agentId, agentId,
workspaceDir, workspaceDir,
settings, settings: { ...settings, provider, fallback },
providerResult, providerResult,
}); });
INDEX_CACHE.set(key, manager); INDEX_CACHE.set(key, manager);
@ -1261,7 +1263,7 @@ export class MemoryIndexManager {
} }
private async activateFallbackProvider(reason: string): Promise<boolean> { private async activateFallbackProvider(reason: string): Promise<boolean> {
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 (!fallback || fallback === "none" || fallback === this.provider.id) return false;
if (this.fallbackFrom) return false; if (this.fallbackFrom) return false;
const fallbackFrom = this.provider.id as "openai" | "gemini" | "local"; const fallbackFrom = this.provider.id as "openai" | "gemini" | "local";