Spaces:
Running
Running
RemanenetSpy commited on
Commit ·
33ef0bb
1
Parent(s): d6b7d79
feat: add MCP server and update README for Smriti
Browse files- README.md +252 -378
- docs/README (4).md +80 -0
- docs/chronos_brain_evolution.md +282 -0
- docs/design.md +25 -0
- docs/error.txt +825 -0
- docs/global_insights_plan.md +48 -0
- docs/idea.md +120 -0
- docs/implementation_plan.md +285 -0
- docs/model_comparison.md +203 -0
- docs/modeldata.md +54 -0
- docs/precision-mem-bench.md +341 -0
- mcp/README.md +241 -0
- mcp/__init__.py +10 -0
- mcp/__main__.py +51 -0
- mcp/claude_desktop_config.json +14 -0
- mcp/client.py +277 -0
- mcp/config.py +62 -0
- mcp/cursor_config.json +14 -0
- mcp/requirements.txt +4 -0
- mcp/server.py +633 -0
- tests/test_mcp_client.py +329 -0
- tests/test_mcp_server.py +408 -0
README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
emoji: 🕰️
|
| 4 |
colorFrom: red
|
| 5 |
colorTo: gray
|
|
@@ -9,326 +9,261 @@ pinned: false
|
|
| 9 |
|
| 10 |
<p align="center">
|
| 11 |
<img src="https://img.shields.io/badge/Status-Live-4ADE80?style=for-the-badge" alt="Live" />
|
| 12 |
-
<img src="https://img.shields.io/badge/
|
| 13 |
-
<img src="https://img.shields.io/badge/Free%20Tier-10K%20events%2Fmo-
|
|
|
|
| 14 |
</p>
|
| 15 |
|
| 16 |
-
<h1 align="center">🕰️
|
| 17 |
-
<p align="center"><em>
|
| 18 |
<p align="center">
|
| 19 |
-
<strong>Give any AI agent
|
| 20 |
-
|
| 21 |
-
and lets agents query what happened, when, and why —
|
|
|
|
| 22 |
</p>
|
| 23 |
|
|
|
|
|
|
|
| 24 |
---
|
| 25 |
|
| 26 |
## Table of Contents
|
| 27 |
|
| 28 |
- [What is Smriti?](#what-is-smriti)
|
| 29 |
-
- [Architecture & Deployments](#architecture--deployments)
|
| 30 |
- [Quick Start (5 Minutes)](#quick-start-5-minutes)
|
| 31 |
-
- [
|
|
|
|
|
|
|
| 32 |
- [Dashboard Guide](#dashboard-guide)
|
| 33 |
-
- [Agent System](#agent-system)
|
| 34 |
- [Pricing Tiers](#pricing-tiers)
|
| 35 |
- [Configuration](#configuration)
|
|
|
|
| 36 |
- [Troubleshooting](#troubleshooting)
|
| 37 |
|
| 38 |
---
|
| 39 |
|
| 40 |
-
##
|
| 41 |
-
|
| 42 |
-
To maximize performance while keeping the service accessible, this project uses a **decoupled, dual-deployment architecture**:
|
| 43 |
-
|
| 44 |
-
1. **Frontend Dashboard (Vercel)**: `https://smriti-kaal.vercel.app`
|
| 45 |
-
* Provides the blazing-fast Next.js web interface, API key generation, and visual memory management.
|
| 46 |
-
2. **Backend API (Hugging Face Spaces)**: `https://spy9191-chronos-api-backend.hf.space`
|
| 47 |
-
* Houses the heavy FastAPI inference engine, memory vector database, and LangGraph agent pipelines.
|
| 48 |
|
| 49 |
-
**
|
| 50 |
|
| 51 |
-
--
|
| 52 |
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
|
| 55 |
-
|
| 56 |
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
| 64 |
|
| 65 |
### Real Example
|
| 66 |
|
| 67 |
```
|
| 68 |
INPUT: "Acme Corp signed a $50,000 contract for Q2 2026"
|
| 69 |
|
| 70 |
-
|
| 71 |
Subject: Acme Corp
|
| 72 |
Verb: signed
|
| 73 |
Object: a $50,000 contract for Q2 2026
|
| 74 |
When: Q2 2026
|
| 75 |
|
| 76 |
-
LATER, ANY AGENT CAN ASK:
|
| 77 |
-
"What
|
| 78 |
-
→ [2026-04-12] Acme Corp signed a $50,000 contract for Q2 2026
|
| 79 |
```
|
| 80 |
|
| 81 |
---
|
| 82 |
|
| 83 |
-
##
|
| 84 |
|
| 85 |
-
``
|
| 86 |
-
|
| 87 |
-
│ KAAL v0.1.0 │
|
| 88 |
-
├──────────────────────────────────────────────────────────────┤
|
| 89 |
-
│ │
|
| 90 |
-
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
| 91 |
-
│ │ POST /ingest│ │ POST /query │ │ POST /agent/run │ │
|
| 92 |
-
│ │ Raw text → │ │ NL search → │ │ Prompt → Memory │ │
|
| 93 |
-
│ │ SVO events │ │ Hybrid rank │ │ → LLM → Response │ │
|
| 94 |
-
│ └──────┬──────┘ └──────┬───────┘ └─────────┬──────────┘ │
|
| 95 |
-
│ │ │ │ │
|
| 96 |
-
│ ┌──────▼────────────────▼─────────────────────▼──────────┐ │
|
| 97 |
-
│ │ 🧠 Memory Core │ │
|
| 98 |
-
│ │ ┌──────────────────┐ ┌──────────────────────────┐ │ │
|
| 99 |
-
│ │ │ Event Calendar │ │ Embedding Index │ │ │
|
| 100 |
-
│ │ │ (PostgreSQL) │ │ (pgvector) │ │ │
|
| 101 |
-
│ │ │ • SVO tuples │ │ • Semantic vectors │ │ │
|
| 102 |
-
│ │ │ • Timestamps │ │ • Cosine similarity │ │ │
|
| 103 |
-
│ │ │ • Turn history │ │ • Fast pgvector scan │ │ │
|
| 104 |
-
│ │ └──────────────────┘ └──────────────────────────┘ │ │
|
| 105 |
-
│ └────────────────────────────────────────────────────────┘ │
|
| 106 |
-
│ │
|
| 107 |
-
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
| 108 |
-
│ │ SVO Parser │ │ Auth + Tiers │ │ Razorpay Billing │ │
|
| 109 |
-
│ │ GPT 120B │ │ API keys │ │ Explorer/Builder/ │ │
|
| 110 |
-
│ │ + Llama 3.1 │ │ SHA-256 hash │ │ Scale │ │
|
| 111 |
-
│ └──────────────┘ └──────────────┘ └────────────────────┘ │
|
| 112 |
-
└──────────────────────────────────────────────────────────────┘
|
| 113 |
-
```
|
| 114 |
|
| 115 |
-
###
|
| 116 |
|
| 117 |
-
```
|
| 118 |
-
chronos-
|
| 119 |
-
|
| 120 |
-
│ ├── models.py Pydantic models, tier config, pricing
|
| 121 |
-
│ ├── svo_parser.py AI event extraction (Groq / Llama 3.1)
|
| 122 |
-
│ ├── memory_store.py PostgreSQL dual calendars (events + turns)
|
| 123 |
-
│ └── vector_store.py pgvector semantic search (sentence-transformers)
|
| 124 |
-
│
|
| 125 |
-
├── api/ 🌐 FastAPI Gateway
|
| 126 |
-
│ ├── main.py App entry, CORS, lifespan
|
| 127 |
-
│ ├── auth.py API key auth, tier quota enforcement
|
| 128 |
-
│ ├── deps.py Dependency injection (singletons)
|
| 129 |
-
│ └── routes/
|
| 130 |
-
│ ├── ingest.py POST /ingest — feed events
|
| 131 |
-
│ ├── query.py POST /query — search memory
|
| 132 |
-
│ ├── connectors.py POST /connect — register SaaS tools
|
| 133 |
-
│ ├── agent.py POST /agent/run — chat with memory
|
| 134 |
-
│ └── billing.py Razorpay checkout, usage, key generation
|
| 135 |
-
│
|
| 136 |
-
├── agent/ 🤖 LangGraph Agent Runner
|
| 137 |
-
│ ├── graph.py State graph: memory → model → response
|
| 138 |
-
│ ├── nodes.py Processing nodes (retrieve, call LLM)
|
| 139 |
-
│ └── tools.py Built-in tools for agents
|
| 140 |
-
│
|
| 141 |
-
├── chronos-ui/ 📊 Next.js Web Dashboard
|
| 142 |
-
│ └── src/app Full app UI (navy+gold Chronos design)
|
| 143 |
-
│
|
| 144 |
-
├── .env Environment variables (your keys)
|
| 145 |
-
├── .env.example Template
|
| 146 |
-
├── requirements.txt Python dependencies
|
| 147 |
-
└── test_quick.py Integration test script
|
| 148 |
```
|
| 149 |
|
| 150 |
-
--
|
| 151 |
-
|
| 152 |
-
## Quick Start (5 Minutes)
|
| 153 |
|
| 154 |
-
###
|
| 155 |
-
|
| 156 |
-
- Python 3.11+
|
| 157 |
-
- A free Groq API key from [console.groq.com](https://console.groq.com)
|
| 158 |
-
|
| 159 |
-
### Step 1 — Install
|
| 160 |
|
| 161 |
```bash
|
| 162 |
-
|
| 163 |
-
|
|
|
|
|
|
|
| 164 |
```
|
| 165 |
|
| 166 |
-
### Step
|
| 167 |
|
| 168 |
```bash
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
#
|
| 174 |
```
|
| 175 |
|
| 176 |
-
###
|
| 177 |
|
| 178 |
```bash
|
|
|
|
|
|
|
|
|
|
|
|
|
| 179 |
python -m uvicorn api.main:app --port 8000
|
| 180 |
```
|
| 181 |
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
|
| 188 |
-
|
|
|
|
|
|
|
| 189 |
|
| 190 |
```bash
|
| 191 |
-
#
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
```
|
| 196 |
|
| 197 |
-
|
| 198 |
|
| 199 |
-
|
|
|
|
| 200 |
|
| 201 |
-
```
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
```
|
| 204 |
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
```
|
| 207 |
-
=== GENERATING API KEY ===
|
| 208 |
-
Key: chrn_abc123...
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 213 |
|
| 214 |
-
|
| 215 |
-
|
|
|
|
|
|
|
|
|
|
| 216 |
|
| 217 |
-
|
|
|
|
|
|
|
|
|
|
| 218 |
```
|
| 219 |
|
|
|
|
|
|
|
| 220 |
---
|
| 221 |
|
| 222 |
-
## API Reference
|
| 223 |
|
| 224 |
**Base URL:** `https://spy9191-chronos-api-backend.hf.space`
|
| 225 |
-
**Auth:**
|
| 226 |
-
**
|
| 227 |
|
| 228 |
---
|
| 229 |
|
| 230 |
### `POST /billing/keys` — Generate API Key
|
| 231 |
|
| 232 |
-
No auth required.
|
| 233 |
|
| 234 |
```bash
|
| 235 |
curl -X POST "https://spy9191-chronos-api-backend.hf.space/billing/keys?tier=explorer"
|
| 236 |
```
|
| 237 |
|
| 238 |
-
**Response:**
|
| 239 |
```json
|
| 240 |
{
|
| 241 |
"api_key": "chrn_abc123...",
|
| 242 |
-
"source_id": "src_7277b1709a854375",
|
| 243 |
"tier": "explorer",
|
| 244 |
-
"message": "
|
| 245 |
}
|
| 246 |
```
|
| 247 |
|
| 248 |
---
|
| 249 |
|
| 250 |
-
### `POST /ingest` —
|
| 251 |
-
|
| 252 |
-
Send raw text; the AI extracts structured SVO events automatically.
|
| 253 |
-
|
| 254 |
-
```bash
|
| 255 |
-
curl -X POST https://spy9191-chronos-api-backend.hf.space/ingest \
|
| 256 |
-
-H "X-API-Key: chrn_your_key" \
|
| 257 |
-
-H "Content-Type: application/json" \
|
| 258 |
-
-d '{
|
| 259 |
-
"source_id": "my-crm",
|
| 260 |
-
"events": [
|
| 261 |
-
{"text": "Acme Corp signed a $50,000 contract for Q2 2026"},
|
| 262 |
-
{"text": "Jane was promoted to VP of Engineering on March 15"},
|
| 263 |
-
{"text": "Server migration completed from AWS to Railway on April 1"}
|
| 264 |
-
],
|
| 265 |
-
"parse_svo": true
|
| 266 |
-
}'
|
| 267 |
-
```
|
| 268 |
-
|
| 269 |
-
**Response:**
|
| 270 |
-
```json
|
| 271 |
-
{
|
| 272 |
-
"ingested_count": 3,
|
| 273 |
-
"event_ids": ["abc123", "def456", "ghi789"],
|
| 274 |
-
"svo_tuples": [
|
| 275 |
-
{
|
| 276 |
-
"subject": "Acme Corp",
|
| 277 |
-
"verb": "signed",
|
| 278 |
-
"object": "a $50,000 contract for Q2 2026",
|
| 279 |
-
"confidence": 0.95
|
| 280 |
-
}
|
| 281 |
-
],
|
| 282 |
-
"turn_ids": ["turn_1", "turn_2", "turn_3"]
|
| 283 |
-
}
|
| 284 |
-
```
|
| 285 |
-
|
| 286 |
-
**Parameters:**
|
| 287 |
|
| 288 |
| Field | Type | Required | Description |
|
| 289 |
|---|---|---|---|
|
| 290 |
-
| `source_id` | string | ✅ |
|
| 291 |
| `events` | array | ✅ | List of `{text, timestamp?, metadata?}` objects |
|
| 292 |
-
| `parse_svo` | bool | No | Enable AI extraction (default: `true`) |
|
|
|
|
| 293 |
|
| 294 |
---
|
| 295 |
|
| 296 |
### `POST /query` — Search Memory
|
| 297 |
|
| 298 |
-
Natural language search with optional time filtering.
|
| 299 |
-
|
| 300 |
-
```bash
|
| 301 |
-
curl -X POST https://spy9191-chronos-api-backend.hf.space/query \
|
| 302 |
-
-H "X-API-Key: chrn_your_key" \
|
| 303 |
-
-H "Content-Type: application/json" \
|
| 304 |
-
-d '{
|
| 305 |
-
"query": "What happened with contracts?",
|
| 306 |
-
"max_results": 10
|
| 307 |
-
}'
|
| 308 |
-
```
|
| 309 |
-
|
| 310 |
-
**Response:**
|
| 311 |
-
```json
|
| 312 |
-
{
|
| 313 |
-
"results": [
|
| 314 |
-
{
|
| 315 |
-
"event": {
|
| 316 |
-
"subject": "Acme Corp",
|
| 317 |
-
"verb": "signed",
|
| 318 |
-
"object": "a $50,000 contract for Q2 2026",
|
| 319 |
-
"timestamp": "2026-04-12T18:28:17Z"
|
| 320 |
-
},
|
| 321 |
-
"relevance_score": 0.92,
|
| 322 |
-
"provenance": "semantic"
|
| 323 |
-
}
|
| 324 |
-
],
|
| 325 |
-
"total_found": 1,
|
| 326 |
-
"query_time_ms": 80.0
|
| 327 |
-
}
|
| 328 |
-
```
|
| 329 |
-
|
| 330 |
-
**Parameters:**
|
| 331 |
-
|
| 332 |
| Field | Type | Required | Description |
|
| 333 |
|---|---|---|---|
|
| 334 |
| `query` | string | ✅ | Natural language question |
|
|
@@ -339,157 +274,112 @@ curl -X POST https://spy9191-chronos-api-backend.hf.space/query \
|
|
| 339 |
|
| 340 |
---
|
| 341 |
|
| 342 |
-
### `POST /agent/run` — Chat with
|
| 343 |
-
|
| 344 |
-
Conversational AI with full temporal memory access.
|
| 345 |
|
| 346 |
```bash
|
| 347 |
curl -X POST https://spy9191-chronos-api-backend.hf.space/agent/run \
|
| 348 |
-H "X-API-Key: chrn_your_key" \
|
| 349 |
-H "Content-Type: application/json" \
|
| 350 |
-
-d '{
|
| 351 |
-
"prompt": "Summarize everything that happened recently",
|
| 352 |
-
"thread_id": null
|
| 353 |
-
}'
|
| 354 |
-
```
|
| 355 |
-
|
| 356 |
-
**Response:**
|
| 357 |
-
```json
|
| 358 |
-
{
|
| 359 |
-
"thread_id": "thread_abc123",
|
| 360 |
-
"response": "Based on your temporal memory, here's what happened recently...",
|
| 361 |
-
"events_retrieved": 4,
|
| 362 |
-
"events_created": 0
|
| 363 |
-
}
|
| 364 |
```
|
| 365 |
|
| 366 |
---
|
| 367 |
|
| 368 |
-
### `
|
| 369 |
|
| 370 |
```bash
|
| 371 |
-
curl
|
| 372 |
-
|
| 373 |
-
-H "Content-Type: application/json" \
|
| 374 |
-
-d '{
|
| 375 |
-
"name": "Stripe",
|
| 376 |
-
"description": "Payment processing",
|
| 377 |
-
"base_url": "https://api.stripe.com",
|
| 378 |
-
"auth_header": "Authorization",
|
| 379 |
-
"endpoints": [
|
| 380 |
-
{"method": "GET", "path": "/v1/invoices", "description": "List invoices"}
|
| 381 |
-
]
|
| 382 |
-
}'
|
| 383 |
```
|
| 384 |
|
| 385 |
---
|
| 386 |
|
| 387 |
-
##
|
| 388 |
|
| 389 |
-
```
|
| 390 |
-
|
| 391 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
```
|
| 393 |
|
| 394 |
-
|
| 395 |
|
| 396 |
-
|
|
|
|
|
|
|
|
|
|
| 397 |
|
| 398 |
-
|
| 399 |
-
curl https://spy9191-chronos-api-backend.hf.space/health
|
| 400 |
-
```
|
| 401 |
|
| 402 |
-
```
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 407 |
```
|
| 408 |
|
| 409 |
---
|
| 410 |
|
| 411 |
## Dashboard Guide
|
| 412 |
|
| 413 |
-
|
| 414 |
|
| 415 |
| Page | What It Does |
|
| 416 |
|---|---|
|
| 417 |
| 🏠 **Overview** | System health, event count, embedding count, AI status |
|
| 418 |
| 📥 **Ingest Events** | Write events in plain English → AI extracts SVO tuples |
|
| 419 |
-
| 🔍 **Query Memory** | Natural language search with date filters and relevance |
|
| 420 |
-
| 🤖 **Agent Chat** | Conversational AI that reasons with
|
| 421 |
| 🔗 **Connect Tool** | Register any SaaS API for agent discovery |
|
| 422 |
| 📊 **Usage & Billing** | Tier badge, usage meters, pricing table |
|
| 423 |
-
| 🔑 **API Keys** | Generate
|
| 424 |
-
|
| 425 |
-
### How to Use the Dashboard
|
| 426 |
-
|
| 427 |
-
1. Open **http://localhost:3000**
|
| 428 |
-
2. Click **🔑 API Keys** → Click **Generate Key** → Copy the key
|
| 429 |
-
3. Paste the key into the **🔑 API Key** field in the sidebar
|
| 430 |
-
4. Go to **📥 Ingest Events** → Type events → Click **Ingest Into Memory**
|
| 431 |
-
5. Go to **🔍 Query Memory** → Ask a question → See results
|
| 432 |
-
6. Go to **🤖 Agent Chat** → Have a conversation with memory-aware AI
|
| 433 |
-
|
| 434 |
-
---
|
| 435 |
-
|
| 436 |
-
## Agent System
|
| 437 |
-
|
| 438 |
-
Chronos includes a **LangGraph-based agent** that automatically:
|
| 439 |
-
|
| 440 |
-
1. **Retrieves** relevant memories from ChromaDB before responding
|
| 441 |
-
2. **Injects** temporal context into the LLM's system prompt
|
| 442 |
-
3. **Responds** with awareness of past events, dates, and relationships
|
| 443 |
-
|
| 444 |
-
### How It Works
|
| 445 |
-
|
| 446 |
-
```
|
| 447 |
-
User: "What's our biggest contract?"
|
| 448 |
-
│
|
| 449 |
-
▼
|
| 450 |
-
┌─────────────────────┐
|
| 451 |
-
│ retrieve_memory_node │ ← Searches pgvector + Postgres
|
| 452 |
-
│ ("contract" → Acme) │ for relevant past events
|
| 453 |
-
└────────┬────────────┘
|
| 454 |
-
│
|
| 455 |
-
▼
|
| 456 |
-
┌─────────────────────┐
|
| 457 |
-
│ call_model_node │ ← GPT 120B
|
| 458 |
-
│ System prompt + │ with memory context injected
|
| 459 |
-
│ memory context + │
|
| 460 |
-
│ user question │
|
| 461 |
-
└────────┬────────────┘
|
| 462 |
-
│
|
| 463 |
-
▼
|
| 464 |
-
"Based on your records, Acme Corp signed a $50K contract for Q2 2026."
|
| 465 |
-
```
|
| 466 |
-
|
| 467 |
-
### AI Model
|
| 468 |
-
|
| 469 |
-
| Property | Value |
|
| 470 |
-
|---|---|
|
| 471 |
-
| **Model** | GPT OSS 120B (Agent) + Llama 3.1 8B (Parsing) |
|
| 472 |
-
| **Provider** | Zhipu AI & Groq |
|
| 473 |
-
| **Speed** | 2,100+ tokens/second |
|
| 474 |
-
| **Daily Limit** | 1,000,000 requests/day |
|
| 475 |
-
| **Cost** | Free |
|
| 476 |
|
| 477 |
---
|
| 478 |
|
| 479 |
## Pricing Tiers
|
| 480 |
|
| 481 |
-
KAAL uses a three-tier model with metered overage:
|
| 482 |
-
|
| 483 |
| Feature | Explorer | Builder | Scale |
|
| 484 |
|---|---|---|---|
|
| 485 |
| **Price** | Free | $49/month | $249/month |
|
| 486 |
| **Events/month** | 10,000 | 500,000 | 5,000,000 |
|
|
|
|
| 487 |
| **Orchestration calls** | 100 | 10,000 | Unlimited |
|
| 488 |
| **Connected tools** | 3 | 25 | Unlimited |
|
| 489 |
| **Data retention** | 30 days | 1 year | Unlimited |
|
| 490 |
-
| **Agent threads** | 5 | 100 | Unlimited |
|
| 491 |
-
| **Event overage (per 1K)** | Hard cap | $0.05 | $0.03 |
|
| 492 |
-
| **Orchestration overage** | Hard cap | $0.10 | $0.07 |
|
| 493 |
| **Support** | Community | Priority email | Dedicated Slack |
|
| 494 |
|
| 495 |
---
|
|
@@ -500,90 +390,62 @@ KAAL uses a three-tier model with metered overage:
|
|
| 500 |
|
| 501 |
| Variable | Required | Description |
|
| 502 |
|---|---|---|
|
| 503 |
-
| `GROQ_API_KEY` | ✅ | Free
|
|
|
|
| 504 |
| `CHRONOS_DB_URL` | No | Postgres DB URL |
|
| 505 |
-
| `PGVECTOR_DB_URL` | No |
|
| 506 |
| `API_SECRET_KEY` | No | Secret for signing API keys |
|
| 507 |
-
|
| 508 |
-
|
| 509 |
-
|
| 510 |
-
|
|
| 511 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
|
| 513 |
### Tech Stack
|
| 514 |
|
| 515 |
| Component | Technology |
|
| 516 |
|---|---|
|
| 517 |
| **API Framework** | FastAPI + Uvicorn |
|
|
|
|
| 518 |
| **Structured Storage** | Neon PostgreSQL |
|
| 519 |
-
| **Vector Search** |
|
| 520 |
| **Embedding Model** | all-MiniLM-L6-v2 (HuggingFace) |
|
| 521 |
-
| **LLM** |
|
| 522 |
| **Agent Framework** | LangGraph + LangChain |
|
| 523 |
| **Dashboard** | Next.js + React (Vercel) |
|
| 524 |
-
| **Billing** | Razorpay |
|
| 525 |
| **Auth** | SHA-256 hashed API keys |
|
| 526 |
|
| 527 |
---
|
| 528 |
|
| 529 |
-
## Troubleshooting
|
| 530 |
-
|
| 531 |
-
| Problem | Solution |
|
| 532 |
-
|---|---|
|
| 533 |
-
| **"Cannot connect to API"** | Make sure the API is running: `python -m uvicorn api.main:app --port 8000` |
|
| 534 |
-
| **Dashboard won't load** | Start it: `python -m streamlit run dashboard/app.py --server.port 8501` |
|
| 535 |
-
| **"No GROQ_API_KEY set"** | Create `.env` file with `GROQ_API_KEY=gsk_your_key` |
|
| 536 |
-
| **"Missing API key" on requests** | Add `X-API-Key: chrn_...` header to all API calls |
|
| 537 |
-
| **Query returns 0 results** | Ingest some events first via `POST /ingest` |
|
| 538 |
-
| **Slow first startup** | The embedding model (~80MB) downloads once from HuggingFace on first run |
|
| 539 |
-
| **Agent gives generic response** | Make sure there are ingested events for the agent to recall |
|
| 540 |
-
|
| 541 |
-
### Running Both Servers
|
| 542 |
-
|
| 543 |
-
You need **two terminals** running simultaneously:
|
| 544 |
-
|
| 545 |
-
**Terminal 1 — API Server:**
|
| 546 |
-
```bash
|
| 547 |
-
cd chronos-hub
|
| 548 |
-
python -m uvicorn api.main:app --port 8000
|
| 549 |
-
```
|
| 550 |
-
|
| 551 |
-
**Terminal 2 — Dashboard:**
|
| 552 |
-
```bash
|
| 553 |
-
cd chronos-ui
|
| 554 |
-
npm run dev
|
| 555 |
-
```
|
| 556 |
-
|
| 557 |
-
---
|
| 558 |
-
|
| 559 |
## Integration Examples
|
| 560 |
|
| 561 |
-
### Python
|
| 562 |
|
| 563 |
```python
|
| 564 |
import httpx
|
| 565 |
|
| 566 |
-
API = "
|
| 567 |
KEY = "chrn_your_key_here"
|
| 568 |
HEADERS = {"X-API-Key": KEY}
|
| 569 |
|
| 570 |
-
#
|
| 571 |
httpx.post(f"{API}/ingest", headers=HEADERS, json={
|
| 572 |
"source_id": "my-app",
|
| 573 |
-
"events": [{"text": "User completed onboarding on
|
| 574 |
})
|
| 575 |
|
| 576 |
-
#
|
| 577 |
result = httpx.post(f"{API}/query", headers=HEADERS, json={
|
| 578 |
"query": "What did the user do?"
|
| 579 |
})
|
| 580 |
-
print(result.json()["results"])
|
| 581 |
-
|
| 582 |
-
# Chat with agent
|
| 583 |
-
response = httpx.post(f"{API}/agent/run", headers=HEADERS, json={
|
| 584 |
-
"prompt": "Summarize user activity"
|
| 585 |
-
})
|
| 586 |
-
print(response.json()["response"])
|
| 587 |
```
|
| 588 |
|
| 589 |
### JavaScript / Node.js
|
|
@@ -592,7 +454,6 @@ print(response.json()["response"])
|
|
| 592 |
const API = "https://spy9191-chronos-api-backend.hf.space";
|
| 593 |
const headers = { "X-API-Key": "chrn_your_key", "Content-Type": "application/json" };
|
| 594 |
|
| 595 |
-
// Ingest
|
| 596 |
await fetch(`${API}/ingest`, {
|
| 597 |
method: "POST", headers,
|
| 598 |
body: JSON.stringify({
|
|
@@ -601,7 +462,6 @@ await fetch(`${API}/ingest`, {
|
|
| 601 |
})
|
| 602 |
});
|
| 603 |
|
| 604 |
-
// Query
|
| 605 |
const res = await fetch(`${API}/query`, {
|
| 606 |
method: "POST", headers,
|
| 607 |
body: JSON.stringify({ query: "Who upgraded recently?" })
|
|
@@ -611,7 +471,21 @@ console.log(await res.json());
|
|
| 611 |
|
| 612 |
---
|
| 613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
<p align="center">
|
| 615 |
-
<em>🕰️
|
| 616 |
-
<strong>© 2026 Chronos Labs</strong>
|
| 617 |
</p>
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Smriti — Temporal Memory API for AI Agents
|
| 3 |
emoji: 🕰️
|
| 4 |
colorFrom: red
|
| 5 |
colorTo: gray
|
|
|
|
| 9 |
|
| 10 |
<p align="center">
|
| 11 |
<img src="https://img.shields.io/badge/Status-Live-4ADE80?style=for-the-badge" alt="Live" />
|
| 12 |
+
<img src="https://img.shields.io/badge/MCP-Claude%20%7C%20Cursor%20%7C%20VS%20Code-6B7194?style=for-the-badge" alt="MCP" />
|
| 13 |
+
<img src="https://img.shields.io/badge/Free%20Tier-10K%20events%2Fmo-C7AB6B?style=for-the-badge" alt="Free" />
|
| 14 |
+
<img src="https://img.shields.io/badge/Memory-SVO%20%2B%20pgvector-4ADE80?style=for-the-badge" alt="Memory" />
|
| 15 |
</p>
|
| 16 |
|
| 17 |
+
<h1 align="center">🕰️ Smriti — Temporal AI Memory</h1>
|
| 18 |
+
<p align="center"><em>Long-term memory for AI agents. Structured. Causal. Searchable.</em></p>
|
| 19 |
<p align="center">
|
| 20 |
+
<strong>Give any AI agent persistent, temporal long-term memory in 5 minutes.</strong><br>
|
| 21 |
+
Smriti decomposes text into Subject-Verb-Object (SVO) causal events, stores them in<br>
|
| 22 |
+
PostgreSQL + pgvector, and lets agents query what happened, when, and why —<br>
|
| 23 |
+
across any session, tool, or conversation.
|
| 24 |
</p>
|
| 25 |
|
| 26 |
+
> **Keywords:** `mcp-server` `ai-agent-memory` `long-term-memory` `persistent-memory` `claude-memory` `cursor-mcp` `llm-memory` `agent-memory` `memory-mcp` `temporal-memory` `svo-extraction` `pgvector` `langchain-memory` `mem0-alternative` `ai-memory-api` `claude-desktop-mcp` `cursor-ai-memory` `ai-agent-persistent-memory`
|
| 27 |
+
|
| 28 |
---
|
| 29 |
|
| 30 |
## Table of Contents
|
| 31 |
|
| 32 |
- [What is Smriti?](#what-is-smriti)
|
|
|
|
| 33 |
- [Quick Start (5 Minutes)](#quick-start-5-minutes)
|
| 34 |
+
- [MCP Server — Claude, Cursor, VS Code](#-mcp-server--claude-cursor-vs-code)
|
| 35 |
+
- [REST API Reference](#rest-api-reference)
|
| 36 |
+
- [Architecture](#architecture)
|
| 37 |
- [Dashboard Guide](#dashboard-guide)
|
|
|
|
| 38 |
- [Pricing Tiers](#pricing-tiers)
|
| 39 |
- [Configuration](#configuration)
|
| 40 |
+
- [Integration Examples](#integration-examples)
|
| 41 |
- [Troubleshooting](#troubleshooting)
|
| 42 |
|
| 43 |
---
|
| 44 |
|
| 45 |
+
## What is Smriti?
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
**The problem:** AI agents are stateless. Every session starts from zero. No memory of decisions made last week, no recall of past conversations, no awareness of what changed. Every agent you build is a goldfish.
|
| 48 |
|
| 49 |
+
**The solution:** Smriti is a **temporal memory API** — a plug-in long-term memory layer for any AI agent, MCP host, or SaaS product. It:
|
| 50 |
|
| 51 |
+
1. **Ingests** text from any source — chat, CRM, email, code commits, documents
|
| 52 |
+
2. **Decomposes** it into structured `Subject → Verb → Object` causal events using AI
|
| 53 |
+
3. **Stores** events in dual stores — PostgreSQL (temporal queries) + pgvector (semantic search)
|
| 54 |
+
4. **Retrieves** relevant memories via natural language — *"What happened with Acme Corp last quarter?"*
|
| 55 |
+
5. **Powers** agents that actually remember — memory persists across sessions, tools, and models
|
| 56 |
|
| 57 |
+
### What Makes Smriti Different
|
| 58 |
|
| 59 |
+
| Feature | Smriti | Typical RAG | Mem0 | Zep |
|
| 60 |
+
|---|---|---|---|---|
|
| 61 |
+
| SVO causal extraction | ✅ | ❌ | ❌ | ❌ |
|
| 62 |
+
| Temporal bi-calendar | ✅ | ❌ | ❌ | Partial |
|
| 63 |
+
| MCP server built-in | ✅ | ❌ | ❌ | ❌ |
|
| 64 |
+
| Never deletes (supersession) | ✅ | ❌ | ❌ | ❌ |
|
| 65 |
+
| Free tier | ✅ 10K events | ❌ | ✅ limited | Paid |
|
| 66 |
+
| Self-hostable | ✅ | ✅ | ❌ | ✅ |
|
| 67 |
|
| 68 |
### Real Example
|
| 69 |
|
| 70 |
```
|
| 71 |
INPUT: "Acme Corp signed a $50,000 contract for Q2 2026"
|
| 72 |
|
| 73 |
+
SMRITI EXTRACTS:
|
| 74 |
Subject: Acme Corp
|
| 75 |
Verb: signed
|
| 76 |
Object: a $50,000 contract for Q2 2026
|
| 77 |
When: Q2 2026
|
| 78 |
|
| 79 |
+
MONTHS LATER, ANY AGENT CAN ASK:
|
| 80 |
+
"What are our biggest contracts?"
|
| 81 |
+
→ [2026-04-12] Acme Corp signed a $50,000 contract for Q2 2026 (score: 0.92)
|
| 82 |
```
|
| 83 |
|
| 84 |
---
|
| 85 |
|
| 86 |
+
## Quick Start (5 Minutes)
|
| 87 |
|
| 88 |
+
**Live API:** `https://spy9191-chronos-api-backend.hf.space`
|
| 89 |
+
**Dashboard:** `https://smriti-kaal.vercel.app`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
+
### Step 1 — Get a Free API Key
|
| 92 |
|
| 93 |
+
```bash
|
| 94 |
+
curl -X POST "https://spy9191-chronos-api-backend.hf.space/billing/keys?tier=explorer"
|
| 95 |
+
# Returns: {"api_key": "chrn_...", "tier": "explorer"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 96 |
```
|
| 97 |
|
| 98 |
+
Or visit [smriti-kaal.vercel.app](https://smriti-kaal.vercel.app) and click **Generate Key**.
|
|
|
|
|
|
|
| 99 |
|
| 100 |
+
### Step 2 — Store a Memory
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
```bash
|
| 103 |
+
curl -X POST https://spy9191-chronos-api-backend.hf.space/ingest \
|
| 104 |
+
-H "X-API-Key: chrn_your_key" \
|
| 105 |
+
-H "Content-Type: application/json" \
|
| 106 |
+
-d '{"source_id": "quickstart", "events": [{"text": "Alice joined the team as lead engineer on July 15"}]}'
|
| 107 |
```
|
| 108 |
|
| 109 |
+
### Step 3 — Recall It
|
| 110 |
|
| 111 |
```bash
|
| 112 |
+
curl -X POST https://spy9191-chronos-api-backend.hf.space/query \
|
| 113 |
+
-H "X-API-Key: chrn_your_key" \
|
| 114 |
+
-H "Content-Type: application/json" \
|
| 115 |
+
-d '{"query": "Who joined the team recently?"}'
|
| 116 |
+
# Returns: Alice joined the team as lead engineer on July 15 (score: 0.94)
|
| 117 |
```
|
| 118 |
|
| 119 |
+
### Local Setup
|
| 120 |
|
| 121 |
```bash
|
| 122 |
+
git clone https://github.com/RemanenetSpy/smriti
|
| 123 |
+
cd smriti
|
| 124 |
+
pip install -r requirements.txt
|
| 125 |
+
cp .env.example .env # Add your GROQ_API_KEY
|
| 126 |
python -m uvicorn api.main:app --port 8000
|
| 127 |
```
|
| 128 |
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## 🔌 MCP Server — Claude, Cursor, VS Code
|
| 132 |
+
|
| 133 |
+
Smriti ships a **Model Context Protocol (MCP)** server that gives any MCP-compatible AI host instant persistent long-term memory — zero custom integration needed.
|
| 134 |
|
| 135 |
+
> Works with: **Claude Desktop**, **Cursor**, **VS Code + Copilot**, **Windsurf**, **Claude Code**, **Continue**, any MCP-compatible client
|
| 136 |
+
|
| 137 |
+
### 3-Step Setup
|
| 138 |
|
| 139 |
```bash
|
| 140 |
+
# 1. Install MCP dependencies
|
| 141 |
+
pip install -r mcp/requirements.txt
|
| 142 |
+
|
| 143 |
+
# 2. Set your API key
|
| 144 |
+
export SMRITI_API_KEY="chrn_your_api_key_here"
|
| 145 |
+
|
| 146 |
+
# 3. Test with MCP Inspector
|
| 147 |
+
npx @modelcontextprotocol/inspector python -m smriti.mcp
|
| 148 |
```
|
| 149 |
|
| 150 |
+
### Claude Desktop Config
|
| 151 |
|
| 152 |
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
|
| 153 |
+
or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
| 154 |
|
| 155 |
+
```json
|
| 156 |
+
{
|
| 157 |
+
"mcpServers": {
|
| 158 |
+
"smriti": {
|
| 159 |
+
"command": "python",
|
| 160 |
+
"args": ["-m", "smriti.mcp"],
|
| 161 |
+
"cwd": "/path/to/smriti",
|
| 162 |
+
"env": {
|
| 163 |
+
"SMRITI_API_KEY": "chrn_your_key_here",
|
| 164 |
+
"SMRITI_SOURCE_ID": "claude-desktop"
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
```
|
| 170 |
|
| 171 |
+
### Cursor Config
|
| 172 |
+
|
| 173 |
+
Edit `.cursor/mcp.json` in your project root:
|
| 174 |
+
|
| 175 |
+
```json
|
| 176 |
+
{
|
| 177 |
+
"mcpServers": {
|
| 178 |
+
"smriti": {
|
| 179 |
+
"command": "python",
|
| 180 |
+
"args": ["-m", "smriti.mcp"],
|
| 181 |
+
"cwd": "/path/to/smriti",
|
| 182 |
+
"env": {
|
| 183 |
+
"SMRITI_API_KEY": "chrn_your_key_here",
|
| 184 |
+
"SMRITI_SOURCE_ID": "my-project"
|
| 185 |
+
}
|
| 186 |
+
}
|
| 187 |
+
}
|
| 188 |
+
}
|
| 189 |
```
|
|
|
|
|
|
|
| 190 |
|
| 191 |
+
### MCP Tools
|
| 192 |
+
|
| 193 |
+
| Tool | What It Does | Key Arguments |
|
| 194 |
+
|------|-------------|---------------|
|
| 195 |
+
| `smriti_remember` | Store a memory — text auto-decomposes into SVO causal events | `text`, `source_id`, `scope`, `timestamp` |
|
| 196 |
+
| `smriti_recall` | Hybrid search: semantic + temporal + entity across all memories | `query`, `max_results`, `time_range_start`, `time_range_end` |
|
| 197 |
+
| `smriti_timeline` | Chronological event timeline for a time window | `time_range_start`, `time_range_end`, `scope` |
|
| 198 |
+
| `smriti_forget` | Find memories to supersede — history is preserved, never deleted | `query`, `scope` |
|
| 199 |
+
| `smriti_health` | Service health check | — |
|
| 200 |
+
| `smriti_usage` | API usage stats and tier limits | — |
|
| 201 |
+
|
| 202 |
+
### MCP Resources
|
| 203 |
+
|
| 204 |
+
| URI | Description |
|
| 205 |
+
|-----|-------------|
|
| 206 |
+
| `smriti://status` | Live service health and memory counts |
|
| 207 |
+
| `smriti://usage` | Current API usage stats and tier limits |
|
| 208 |
+
| `smriti://config` | MCP server configuration (non-sensitive) |
|
| 209 |
+
|
| 210 |
+
### MCP Prompts
|
| 211 |
|
| 212 |
+
| Name | Purpose |
|
| 213 |
+
|------|---------|
|
| 214 |
+
| `memory-chat` | System prompt for memory-augmented conversations |
|
| 215 |
+
| `daily-recap` | Timeline-based daily/weekly recap template |
|
| 216 |
+
| `knowledge-extraction` | Bulk knowledge ingestion from documents |
|
| 217 |
|
| 218 |
+
### SSE Transport (Remote / Multi-Client)
|
| 219 |
+
|
| 220 |
+
```bash
|
| 221 |
+
python -m smriti.mcp --transport sse --port 8080
|
| 222 |
```
|
| 223 |
|
| 224 |
+
> 📖 Full MCP docs: [`mcp/README.md`](mcp/README.md)
|
| 225 |
+
|
| 226 |
---
|
| 227 |
|
| 228 |
+
## REST API Reference
|
| 229 |
|
| 230 |
**Base URL:** `https://spy9191-chronos-api-backend.hf.space`
|
| 231 |
+
**Auth:** `X-API-Key: chrn_your_key` header on all requests.
|
| 232 |
+
**Interactive docs:** [/docs](https://spy9191-chronos-api-backend.hf.space/docs)
|
| 233 |
|
| 234 |
---
|
| 235 |
|
| 236 |
### `POST /billing/keys` — Generate API Key
|
| 237 |
|
| 238 |
+
No auth required.
|
| 239 |
|
| 240 |
```bash
|
| 241 |
curl -X POST "https://spy9191-chronos-api-backend.hf.space/billing/keys?tier=explorer"
|
| 242 |
```
|
| 243 |
|
|
|
|
| 244 |
```json
|
| 245 |
{
|
| 246 |
"api_key": "chrn_abc123...",
|
|
|
|
| 247 |
"tier": "explorer",
|
| 248 |
+
"message": "Save this API key now — it cannot be retrieved later."
|
| 249 |
}
|
| 250 |
```
|
| 251 |
|
| 252 |
---
|
| 253 |
|
| 254 |
+
### `POST /ingest` — Store Memories
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
|
| 256 |
| Field | Type | Required | Description |
|
| 257 |
|---|---|---|---|
|
| 258 |
+
| `source_id` | string | ✅ | Data source identifier |
|
| 259 |
| `events` | array | ✅ | List of `{text, timestamp?, metadata?}` objects |
|
| 260 |
+
| `parse_svo` | bool | No | Enable AI SVO extraction (default: `true`) |
|
| 261 |
+
| `scope` | string | No | Memory scope / namespace |
|
| 262 |
|
| 263 |
---
|
| 264 |
|
| 265 |
### `POST /query` — Search Memory
|
| 266 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
| Field | Type | Required | Description |
|
| 268 |
|---|---|---|---|
|
| 269 |
| `query` | string | ✅ | Natural language question |
|
|
|
|
| 274 |
|
| 275 |
---
|
| 276 |
|
| 277 |
+
### `POST /agent/run` — Chat with Memory-Aware AI
|
|
|
|
|
|
|
| 278 |
|
| 279 |
```bash
|
| 280 |
curl -X POST https://spy9191-chronos-api-backend.hf.space/agent/run \
|
| 281 |
-H "X-API-Key: chrn_your_key" \
|
| 282 |
-H "Content-Type: application/json" \
|
| 283 |
+
-d '{"prompt": "Summarize everything that happened recently", "thread_id": null}'
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
```
|
| 285 |
|
| 286 |
---
|
| 287 |
|
| 288 |
+
### `GET /health` — Health Check
|
| 289 |
|
| 290 |
```bash
|
| 291 |
+
curl https://spy9191-chronos-api-backend.hf.space/health
|
| 292 |
+
# {"status": "healthy", "stores": {"postgres_events": 4, "pgvector_embeddings": 4}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 293 |
```
|
| 294 |
|
| 295 |
---
|
| 296 |
|
| 297 |
+
## Architecture
|
| 298 |
|
| 299 |
+
```
|
| 300 |
+
┌──────────────────────────────────────────────────────────────┐
|
| 301 |
+
│ Smriti v0.1.0 │
|
| 302 |
+
├──────────────────────────────────────────────────────────────┤
|
| 303 |
+
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
| 304 |
+
│ │ MCP Server │ │ REST API │ │ Agent (LangGraph) │ │
|
| 305 |
+
│ │ 6 tools │ │ FastAPI │ │ POST /agent/run │ │
|
| 306 |
+
│ │ Claude/Cursor│ │ /ingest │ │ Memory → LLM │ │
|
| 307 |
+
│ └──────┬───────┘ │ /query │ └─────────┬──────────┘ │
|
| 308 |
+
│ │ └──────┬───────┘ │ │
|
| 309 |
+
│ ┌──────▼──────────────────▼────────────────────▼──────────┐ │
|
| 310 |
+
│ │ 🧠 Memory Core │ │
|
| 311 |
+
│ │ ┌──────────────────────┐ ┌──────────────────────────┐ │ │
|
| 312 |
+
│ │ │ Event Calendar │ │ Embedding Index │ │ │
|
| 313 |
+
│ │ │ (PostgreSQL/Neon) │ │ (pgvector) │ │ │
|
| 314 |
+
│ │ │ • SVO tuples │ │ • Semantic vectors │ │ │
|
| 315 |
+
│ │ │ • Timestamps │ │ • Cosine similarity │ │ │
|
| 316 |
+
│ │ │ • Supersession log │ │ • all-MiniLM-L6-v2 │ │ │
|
| 317 |
+
│ │ └──────────────────────┘ └──────────────────────────┘ │ │
|
| 318 |
+
│ └────────────────────────────────────────────────────────┘ │
|
| 319 |
+
│ ┌──────────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
| 320 |
+
│ │ SVO Parser │ │ Auth + Tiers │ │ Next.js Dashboard │ │
|
| 321 |
+
│ │ Groq LLM │ │ API keys │ │ smriti-kaal.vercel │ │
|
| 322 |
+
│ └──────────────┘ └──────────────┘ └────────────────────┘ │
|
| 323 |
+
└──────────────────────────────────────────────────────────────┘
|
| 324 |
```
|
| 325 |
|
| 326 |
+
### Deployments
|
| 327 |
|
| 328 |
+
| Component | Platform | URL |
|
| 329 |
+
|---|---|---|
|
| 330 |
+
| **Frontend Dashboard** | Vercel (Next.js) | [smriti-kaal.vercel.app](https://smriti-kaal.vercel.app) |
|
| 331 |
+
| **Backend API** | Hugging Face Spaces (Docker) | [spy9191-chronos-api-backend.hf.space](https://spy9191-chronos-api-backend.hf.space) |
|
| 332 |
|
| 333 |
+
### File Structure
|
|
|
|
|
|
|
| 334 |
|
| 335 |
+
```
|
| 336 |
+
smriti/
|
| 337 |
+
├── api/ REST API (FastAPI)
|
| 338 |
+
├── mcp/ MCP Server (Claude / Cursor / VS Code)
|
| 339 |
+
│ ├── server.py FastMCP — 6 tools, 3 resources, 3 prompts
|
| 340 |
+
│ ├── client.py HTTP client for Smriti REST API
|
| 341 |
+
│ ├── config.py Environment variable configuration
|
| 342 |
+
│ ├── __main__.py CLI entry (python -m smriti.mcp)
|
| 343 |
+
│ ├── claude_desktop_config.json
|
| 344 |
+
│ ├── cursor_config.json
|
| 345 |
+
│ └── README.md
|
| 346 |
+
├── chronos_core/ Memory Core (SVO parser, stores)
|
| 347 |
+
├── agent/ LangGraph Agent
|
| 348 |
+
├── chronos-ui/ Next.js Dashboard (Vercel)
|
| 349 |
+
├── benchmark/ PrecisionMemBench eval harness
|
| 350 |
+
├── docs/ Technical docs
|
| 351 |
+
├── Dockerfile
|
| 352 |
+
���── requirements.txt
|
| 353 |
```
|
| 354 |
|
| 355 |
---
|
| 356 |
|
| 357 |
## Dashboard Guide
|
| 358 |
|
| 359 |
+
Dashboard at **https://smriti-kaal.vercel.app** (or `http://localhost:3000` locally).
|
| 360 |
|
| 361 |
| Page | What It Does |
|
| 362 |
|---|---|
|
| 363 |
| 🏠 **Overview** | System health, event count, embedding count, AI status |
|
| 364 |
| 📥 **Ingest Events** | Write events in plain English → AI extracts SVO tuples |
|
| 365 |
+
| 🔍 **Query Memory** | Natural language search with date filters and relevance scores |
|
| 366 |
+
| 🤖 **Agent Chat** | Conversational AI that reasons with temporal memory |
|
| 367 |
| 🔗 **Connect Tool** | Register any SaaS API for agent discovery |
|
| 368 |
| 📊 **Usage & Billing** | Tier badge, usage meters, pricing table |
|
| 369 |
+
| 🔑 **API Keys** | Generate and manage keys |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 370 |
|
| 371 |
---
|
| 372 |
|
| 373 |
## Pricing Tiers
|
| 374 |
|
|
|
|
|
|
|
| 375 |
| Feature | Explorer | Builder | Scale |
|
| 376 |
|---|---|---|---|
|
| 377 |
| **Price** | Free | $49/month | $249/month |
|
| 378 |
| **Events/month** | 10,000 | 500,000 | 5,000,000 |
|
| 379 |
+
| **MCP sessions** | Unlimited | Unlimited | Unlimited |
|
| 380 |
| **Orchestration calls** | 100 | 10,000 | Unlimited |
|
| 381 |
| **Connected tools** | 3 | 25 | Unlimited |
|
| 382 |
| **Data retention** | 30 days | 1 year | Unlimited |
|
|
|
|
|
|
|
|
|
|
| 383 |
| **Support** | Community | Priority email | Dedicated Slack |
|
| 384 |
|
| 385 |
---
|
|
|
|
| 390 |
|
| 391 |
| Variable | Required | Description |
|
| 392 |
|---|---|---|
|
| 393 |
+
| `GROQ_API_KEY` | ✅ | Free from [console.groq.com](https://console.groq.com) |
|
| 394 |
+
| `SMRITI_API_KEY` | MCP only | Your Smriti API key (`chrn_...`) |
|
| 395 |
| `CHRONOS_DB_URL` | No | Postgres DB URL |
|
| 396 |
+
| `PGVECTOR_DB_URL` | No | pgvector DB URL |
|
| 397 |
| `API_SECRET_KEY` | No | Secret for signing API keys |
|
| 398 |
+
|
| 399 |
+
### MCP Environment Variables
|
| 400 |
+
|
| 401 |
+
| Variable | Default | Description |
|
| 402 |
+
|---|---|---|
|
| 403 |
+
| `SMRITI_API_KEY` | *(required)* | API key (`chrn_...`) |
|
| 404 |
+
| `SMRITI_BASE_URL` | `https://spy9191-chronos-api-backend.hf.space` | API base URL |
|
| 405 |
+
| `SMRITI_SOURCE_ID` | `mcp-client` | Default source namespace |
|
| 406 |
+
| `SMRITI_SCOPE` | `default` | Default memory scope |
|
| 407 |
+
| `SMRITI_MAX_RESULTS` | `20` | Default max results |
|
| 408 |
+
| `SMRITI_PARSE_SVO` | `true` | Enable SVO extraction |
|
| 409 |
+
| `SMRITI_TIMEOUT` | `30.0` | HTTP timeout (seconds) |
|
| 410 |
|
| 411 |
### Tech Stack
|
| 412 |
|
| 413 |
| Component | Technology |
|
| 414 |
|---|---|
|
| 415 |
| **API Framework** | FastAPI + Uvicorn |
|
| 416 |
+
| **MCP Framework** | FastMCP (Model Context Protocol) |
|
| 417 |
| **Structured Storage** | Neon PostgreSQL |
|
| 418 |
+
| **Vector Search** | pgvector + sentence-transformers |
|
| 419 |
| **Embedding Model** | all-MiniLM-L6-v2 (HuggingFace) |
|
| 420 |
+
| **SVO Parser / LLM** | Llama 3.x via Groq |
|
| 421 |
| **Agent Framework** | LangGraph + LangChain |
|
| 422 |
| **Dashboard** | Next.js + React (Vercel) |
|
|
|
|
| 423 |
| **Auth** | SHA-256 hashed API keys |
|
| 424 |
|
| 425 |
---
|
| 426 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 427 |
## Integration Examples
|
| 428 |
|
| 429 |
+
### Python
|
| 430 |
|
| 431 |
```python
|
| 432 |
import httpx
|
| 433 |
|
| 434 |
+
API = "https://spy9191-chronos-api-backend.hf.space"
|
| 435 |
KEY = "chrn_your_key_here"
|
| 436 |
HEADERS = {"X-API-Key": KEY}
|
| 437 |
|
| 438 |
+
# Store a memory
|
| 439 |
httpx.post(f"{API}/ingest", headers=HEADERS, json={
|
| 440 |
"source_id": "my-app",
|
| 441 |
+
"events": [{"text": "User completed onboarding on July 15"}]
|
| 442 |
})
|
| 443 |
|
| 444 |
+
# Recall it
|
| 445 |
result = httpx.post(f"{API}/query", headers=HEADERS, json={
|
| 446 |
"query": "What did the user do?"
|
| 447 |
})
|
| 448 |
+
print(result.json()["results"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 449 |
```
|
| 450 |
|
| 451 |
### JavaScript / Node.js
|
|
|
|
| 454 |
const API = "https://spy9191-chronos-api-backend.hf.space";
|
| 455 |
const headers = { "X-API-Key": "chrn_your_key", "Content-Type": "application/json" };
|
| 456 |
|
|
|
|
| 457 |
await fetch(`${API}/ingest`, {
|
| 458 |
method: "POST", headers,
|
| 459 |
body: JSON.stringify({
|
|
|
|
| 462 |
})
|
| 463 |
});
|
| 464 |
|
|
|
|
| 465 |
const res = await fetch(`${API}/query`, {
|
| 466 |
method: "POST", headers,
|
| 467 |
body: JSON.stringify({ query: "Who upgraded recently?" })
|
|
|
|
| 471 |
|
| 472 |
---
|
| 473 |
|
| 474 |
+
## Troubleshooting
|
| 475 |
+
|
| 476 |
+
| Problem | Solution |
|
| 477 |
+
|---|---|
|
| 478 |
+
| **"Cannot connect to API"** | Check `python -m uvicorn api.main:app --port 8000` is running |
|
| 479 |
+
| **"No GROQ_API_KEY set"** | Add `GROQ_API_KEY=gsk_...` to `.env` |
|
| 480 |
+
| **"Missing API key"** | Add `X-API-Key: chrn_...` header to all requests |
|
| 481 |
+
| **Query returns 0 results** | Ingest some events first via `POST /ingest` |
|
| 482 |
+
| **Slow first startup** | Embedding model (~80MB) downloads once on first run |
|
| 483 |
+
| **MCP not connecting** | Run `npx @modelcontextprotocol/inspector python -m smriti.mcp` to test |
|
| 484 |
+
| **MCP tools not appearing** | Check `SMRITI_API_KEY` is set in the env block of your host config |
|
| 485 |
+
|
| 486 |
+
---
|
| 487 |
+
|
| 488 |
<p align="center">
|
| 489 |
+
<em>🕰️ Memory that persists. Context that continues.</em><br>
|
| 490 |
+
<strong>© 2026 Smriti / Chronos Labs</strong>
|
| 491 |
</p>
|
docs/README (4).md
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Chronos: Temporal Legacy Vault
|
| 3 |
+
|
| 4 |
+
> "Capturing the fragments of today for the clarity of tomorrow."
|
| 5 |
+
|
| 6 |
+
**Chronos** is a luxury minimalist digital sanctuary designed for intentional reflection. It acts as a **nervous system regulator**—an anti-doomscrolling tool that allows you to compose letters, attach "visual echoes" (photos), and record audio to be delivered to your future self. By locking memories behind a temporal gate, Chronos transforms social posting into a private ritual of mindfulness, quarantining your mental noise.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## 📜 The Purpose
|
| 11 |
+
|
| 12 |
+
In an era of instant gratification and ephemeral content, Chronos asks you to slow down. Human brains hate "open loops." If you have an intrusive thought and put it in a standard notes app, you just keep re-reading it. If you lock it in Chronos and the math physically refuses to let you see it for a month, your brain is forced to drop the loop.
|
| 13 |
+
|
| 14 |
+
## ✨ Key Features
|
| 15 |
+
|
| 16 |
+
- **Compose Across Time**: Write rich, serif-styled letters to the future with an elegant editor.
|
| 17 |
+
- **Visual & Voice Echoes**: Attach images and audio recordings to your capsules to ground your memories in reality.
|
| 18 |
+
- **Temporal Gates**: Choose your horizon—from a single "Moment" (1 day) to a full "Decade."
|
| 19 |
+
- **The Vault**: A curated gallery of your pending and revealed history, styled as an elegant stationery collection with a dramatic editorial layout.
|
| 20 |
+
- **Reflections**: Once a capsule is revealed, add a modern perspective to your past thoughts to see how much you've grown.
|
| 21 |
+
- **Premium Waitlist**: Long-term horizons (Year, Decade) and unlimited Moon capsules are locked behind a premium tier (currently a demo waitlist).
|
| 22 |
+
- **Share the Void**: Generate a high-resolution, cryptographic-style "tombstone" card to share on social media, proving you locked a thought without revealing the content.
|
| 23 |
+
|
| 24 |
+
---
|
| 25 |
+
|
| 26 |
+
## ⚙️ Technical Deep Dive & Architecture
|
| 27 |
+
|
| 28 |
+
Chronos is built as a **Local-First Single Page Application (SPA)**. The architecture is designed to prioritize absolute user privacy, meaning the application logic runs entirely within the client's browser.
|
| 29 |
+
|
| 30 |
+
### 1. Data Persistence & Media Handling
|
| 31 |
+
- **Storage Engine**: Data is persisted using the browser's native `localStorage` API.
|
| 32 |
+
- **Media Encoding**: Images and audio are not uploaded to a CDN. Instead, the `FileReader` API converts images, and the `MediaRecorder` API converts microphone streams (`audio/webm`), directly into **Base64 Data URIs**. These strings are then encrypted and stored locally.
|
| 33 |
+
- *Note on Scaling*: `localStorage` typically has a 5MB limit. Future iterations will migrate to `IndexedDB` to support larger media payloads.
|
| 34 |
+
|
| 35 |
+
### 2. Security & Encryption (Local-First)
|
| 36 |
+
- **AES-256 Encryption at Rest**: Before any capsule is committed to `localStorage`, the sensitive fields (`content`, `reflection`, `imageUrl`, `audioUrl`) are mathematically locked using `crypto-js` AES encryption.
|
| 37 |
+
- **Targeted Encryption**: The UI metadata (`unlockAt`, `title`, `id`) remains in plain text. This allows the React frontend to render the Vault gallery, calculate countdown timers, and manage state without needing to decrypt the private payloads.
|
| 38 |
+
- **Zero-Knowledge**: Your data never leaves your device. There is no database, no cloud sync, and no backend API receiving your thoughts. You can verify this by monitoring the Network tab in your browser's Developer Tools.
|
| 39 |
+
- *MVP Note*: The current iteration uses a static client-side key for AES encryption. A production release will implement PBKDF2 to derive a unique encryption key from a user-provided master password.
|
| 40 |
+
|
| 41 |
+
### 3. "Share the Void" Card Generation
|
| 42 |
+
To allow users to share their commitment on social media without compromising privacy, Chronos features a client-side image generator.
|
| 43 |
+
- **html2canvas**: When a user clicks "Share the Void", the app renders a hidden DOM element containing the "tombstone" design (using the *Spectral* typeface).
|
| 44 |
+
- **Canvas Export**: `html2canvas` traverses this DOM node, paints it to an HTML5 `<canvas>` at 3x scale for high-resolution retina displays, and exports it as an `image/png` Data URI, triggering a native browser download.
|
| 45 |
+
|
| 46 |
+
### 4. Tech Stack
|
| 47 |
+
- **Frontend Framework**: React 19, TypeScript, Vite
|
| 48 |
+
- **Styling**: Tailwind CSS
|
| 49 |
+
- **Typography**: Cormorant Garamond (Serif), Spectral (Serif), Inter (Sans), JetBrains Mono (Mono)
|
| 50 |
+
- **Cryptography**: `crypto-js`
|
| 51 |
+
- **Backend (Payments Only)**: Express.js server utilizing the `stripe` Node SDK to generate secure Checkout Sessions for the Premium tier.
|
| 52 |
+
|
| 53 |
+
---
|
| 54 |
+
|
| 55 |
+
## 🚀 Getting Started
|
| 56 |
+
|
| 57 |
+
### Prerequisites
|
| 58 |
+
|
| 59 |
+
- Node.js (v18+)
|
| 60 |
+
- npm or yarn
|
| 61 |
+
|
| 62 |
+
### Installation
|
| 63 |
+
|
| 64 |
+
1. Clone the repository
|
| 65 |
+
2. Install dependencies:
|
| 66 |
+
```bash
|
| 67 |
+
npm install
|
| 68 |
+
```
|
| 69 |
+
3. Start the development server:
|
| 70 |
+
```bash
|
| 71 |
+
npm run dev
|
| 72 |
+
```
|
| 73 |
+
|
| 74 |
+
## ⚖️ License
|
| 75 |
+
|
| 76 |
+
Distributed under the **MIT License**. See the footer of the application for more details. This project is open for anyone to use, modify, and chronicle their own journeys.
|
| 77 |
+
|
| 78 |
+
---
|
| 79 |
+
|
| 80 |
+
*Curated with continuity in mind.*
|
docs/chronos_brain_evolution.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🧠 Chronos OS → Chronos Brain: The Evolution
|
| 2 |
+
|
| 3 |
+
> *"We built the memory. Now we build the mind."*
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## The Opportunity
|
| 8 |
+
|
| 9 |
+
Right now, Chronos OS is a **temporal memory API** — it stores events, retrieves them, and lets agents query the past. That's already more than Gemini, Claude, or Grok can do natively.
|
| 10 |
+
|
| 11 |
+
But with the free frontier models you have access to (Ollama Cloud, NVIDIA NIM, Cerebras, Groq), we can evolve Chronos from a **passive memory store** into an **active, self-learning cognitive system** — a brain that doesn't just remember, but *understands*, *predicts*, *adapts*, and *acts*.
|
| 12 |
+
|
| 13 |
+
No one has built this. Not OpenAI. Not Google. Not Anthropic. Their models are brilliant in the moment but **amnesiac across time**. Chronos already solved the amnesia. Now we give it intelligence layers.
|
| 14 |
+
|
| 15 |
+
---
|
| 16 |
+
|
| 17 |
+
## The 5 Layers of the Chronos Brain
|
| 18 |
+
|
| 19 |
+
```
|
| 20 |
+
┌─────────────────────────────────────────────────────────┐
|
| 21 |
+
│ CHRONOS BRAIN v1.0 │
|
| 22 |
+
├─────────────────────────────────────────────────────────┤
|
| 23 |
+
│ │
|
| 24 |
+
│ Layer 5: 🤖 AUTONOMOUS ORCHESTRATION │
|
| 25 |
+
│ ├── Multi-agent coordination across SaaS tools │
|
| 26 |
+
│ ├── Self-dispatching task execution │
|
| 27 |
+
│ └── Model: Nemotron-3-Super 120B (NVIDIA NIM) │
|
| 28 |
+
│ │
|
| 29 |
+
│ Layer 4: 🕸️ SELF-EVOLVING KNOWLEDGE GRAPH │
|
| 30 |
+
│ ├── Entity relationship mapping across all events │
|
| 31 |
+
│ ├── Automatic concept linking & ontology building │
|
| 32 |
+
│ └── Model: DeepSeek-V3.2 671B (Ollama Cloud) │
|
| 33 |
+
│ │
|
| 34 |
+
│ Layer 3: 👁️ MULTI-MODAL MEMORY │
|
| 35 |
+
│ ├── Ingest images, screenshots, documents, audio │
|
| 36 |
+
│ ├── Extract temporal events from ANY media type │
|
| 37 |
+
│ └── Model: Kimi-K2.5 / Gemma 4 31B (Ollama Cloud) │
|
| 38 |
+
│ │
|
| 39 |
+
│ Layer 2: 🔮 PREDICTIVE INTELLIGENCE │
|
| 40 |
+
│ ├── Pattern detection across temporal event streams │
|
| 41 |
+
│ ├── "What will happen next?" forecasting │
|
| 42 |
+
│ └── Model: GLM-5.1 / Qwen 3.5 122B (Ollama Cloud) │
|
| 43 |
+
│ │
|
| 44 |
+
│ Layer 1: 💾 MEMORY CONSOLIDATION (Sleep Cycle) │
|
| 45 |
+
│ ├── Nightly compression of raw events → insights │
|
| 46 |
+
│ ├── Forgetting curve: fade irrelevant, strengthen key │
|
| 47 |
+
│ └── Model: Qwen 3 235B (Cerebras) + Llama 3.1 (Groq) │
|
| 48 |
+
│ │
|
| 49 |
+
│ Layer 0: 📅 TEMPORAL MEMORY (Already Built!) │
|
| 50 |
+
│ ├── SVO event extraction & dual calendar storage │
|
| 51 |
+
│ ├── Hybrid semantic + temporal retrieval │
|
| 52 |
+
│ └── PostgreSQL + pgvector + sentence-transformers │
|
| 53 |
+
│ │
|
| 54 |
+
└─────────────────────────────────────────────────────────┘
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
---
|
| 58 |
+
|
| 59 |
+
## Layer 1: 💾 Memory Consolidation (The Sleep Cycle)
|
| 60 |
+
|
| 61 |
+
> **"The human brain doesn't remember everything. It replays, compresses, and strengthens what matters while you sleep. Chronos should too."**
|
| 62 |
+
|
| 63 |
+
### What It Does
|
| 64 |
+
Every night (or every N hours), a background job runs that:
|
| 65 |
+
1. **Replays** all recent events for each tenant
|
| 66 |
+
2. **Extracts patterns**: "User mentioned Acme Corp 14 times this week, always about contracts"
|
| 67 |
+
3. **Generates insights**: "Acme Corp negotiation is escalating — confidence dropped from 0.95 to 0.6 over 3 days"
|
| 68 |
+
4. **Compresses**: Merges redundant events into consolidated summaries
|
| 69 |
+
5. **Assigns decay scores**: Events that are never queried slowly fade (like human forgetting)
|
| 70 |
+
|
| 71 |
+
### Models Used
|
| 72 |
+
| Task | Model | Provider | Cost |
|
| 73 |
+
|---|---|---|---|
|
| 74 |
+
| Event replay & compression | Qwen 3 235B | Cerebras | Free (1M tokens/day) |
|
| 75 |
+
| Fast batch processing | Llama 3.1 8B | Groq | Free (14.4K req/day) |
|
| 76 |
+
|
| 77 |
+
### New API Endpoint
|
| 78 |
+
```
|
| 79 |
+
GET /memory/insights?period=7d
|
| 80 |
+
→ Returns auto-generated weekly intelligence briefing for this API key owner
|
| 81 |
+
```
|
| 82 |
+
|
| 83 |
+
### Why It's Revolutionary
|
| 84 |
+
No AI system today does this. ChatGPT's "memory" is a flat list of facts. Chronos would have **temporal depth** — it knows not just *what* happened, but how things *changed over time* and what the *trajectory* suggests.
|
| 85 |
+
|
| 86 |
+
---
|
| 87 |
+
|
| 88 |
+
## Layer 2: 🔮 Predictive Intelligence
|
| 89 |
+
|
| 90 |
+
> **"Memory isn't just about the past. A true brain uses patterns from the past to predict the future."**
|
| 91 |
+
|
| 92 |
+
### What It Does
|
| 93 |
+
After Layer 1 consolidates memories into patterns, Layer 2 runs prediction:
|
| 94 |
+
1. **Trend Detection**: "Revenue discussions increased 300% this month vs last month"
|
| 95 |
+
2. **Anomaly Alerts**: "This client usually responds within 2 days. It's been 8 days — flag it"
|
| 96 |
+
3. **Forecasting**: "Based on past Q2 patterns, expect a 40% spike in contract renewals"
|
| 97 |
+
4. **Causal Chains**: "Every time X happened, Y followed within 2 weeks"
|
| 98 |
+
|
| 99 |
+
### Models Used
|
| 100 |
+
| Task | Model | Provider | Cost |
|
| 101 |
+
|---|---|---|---|
|
| 102 |
+
| Deep temporal reasoning | GLM-5.1 Cloud (strongest reasoning) | Ollama Cloud | Free |
|
| 103 |
+
| Large-scale pattern analysis | Qwen 3.5 122B Cloud | Ollama Cloud | Free |
|
| 104 |
+
|
| 105 |
+
### New API Endpoint
|
| 106 |
+
```
|
| 107 |
+
POST /brain/predict
|
| 108 |
+
Body: { "question": "What will happen with the Acme Corp deal?" }
|
| 109 |
+
→ Returns prediction with confidence score + supporting temporal evidence chain
|
| 110 |
+
```
|
| 111 |
+
|
| 112 |
+
### Why It's Revolutionary
|
| 113 |
+
This turns Chronos from **reactive** (answer questions about the past) to **proactive** (warn you about the future). No memory system does this. You're essentially building **temporal intuition** for AI agents.
|
| 114 |
+
|
| 115 |
+
---
|
| 116 |
+
|
| 117 |
+
## Layer 3: 👁️ Multi-Modal Memory
|
| 118 |
+
|
| 119 |
+
> **"Humans don't just remember words. We remember faces, documents, screenshots, sounds. Chronos should too."**
|
| 120 |
+
|
| 121 |
+
### What It Does
|
| 122 |
+
Expand `/ingest` to accept:
|
| 123 |
+
- 📸 **Images/Screenshots** → Extract text + scene description → SVO events
|
| 124 |
+
- 📄 **PDFs/Documents** → Parse + decompose into temporal events
|
| 125 |
+
- 🎤 **Audio clips** → Transcribe + extract events
|
| 126 |
+
- 📊 **Charts/Graphs** → Interpret trends and convert to structured data
|
| 127 |
+
|
| 128 |
+
### Models Used
|
| 129 |
+
| Task | Model | Provider | Cost |
|
| 130 |
+
|---|---|---|---|
|
| 131 |
+
| Vision + Language reasoning | Kimi-K2.5 Cloud (multimodal thinking) | Ollama Cloud | Free |
|
| 132 |
+
| Fast vision processing | Gemma 4 31B Cloud (vision/audio) | Ollama Cloud | Free |
|
| 133 |
+
| Speed-optimized vision | Gemini-3-Flash-Preview Cloud | Ollama Cloud | Free |
|
| 134 |
+
|
| 135 |
+
### New API Endpoint
|
| 136 |
+
```
|
| 137 |
+
POST /ingest/multimodal
|
| 138 |
+
Body: { "file": <base64_image>, "type": "screenshot", "context": "Client meeting whiteboard" }
|
| 139 |
+
→ Extracts SVO events from the image and stores them in memory
|
| 140 |
+
```
|
| 141 |
+
|
| 142 |
+
### Why It's Revolutionary
|
| 143 |
+
Imagine: a SaaS product takes a screenshot of a Slack conversation, sends it to Chronos, and the agent can later answer "What did the team discuss about the launch timeline?" — from a *screenshot*. That is alien-level memory.
|
| 144 |
+
|
| 145 |
+
---
|
| 146 |
+
|
| 147 |
+
## Layer 4: 🕸️ Self-Evolving Knowledge Graph
|
| 148 |
+
|
| 149 |
+
> **"Individual memories are data points. Connected memories are intelligence."**
|
| 150 |
+
|
| 151 |
+
### What It Does
|
| 152 |
+
Run a background process that:
|
| 153 |
+
1. **Scans all events** for an owner and builds an entity relationship graph
|
| 154 |
+
2. **Links concepts**: "Acme Corp" → "John (VP Sales)" → "$50K contract" → "Q2 deadline"
|
| 155 |
+
3. **Discovers hidden connections**: "The engineer who left in March was working on the same module that failed in April"
|
| 156 |
+
4. **Auto-generates ontologies**: Learns the user's domain vocabulary and relationships without any configuration
|
| 157 |
+
|
| 158 |
+
### Models Used
|
| 159 |
+
| Task | Model | Provider | Cost |
|
| 160 |
+
|---|---|---|---|
|
| 161 |
+
| Deep entity extraction & linking | DeepSeek-V3.2 671B Cloud | Ollama Cloud | Free |
|
| 162 |
+
| Graph reasoning at scale | GLM-5.1 Cloud | Ollama Cloud | Free |
|
| 163 |
+
|
| 164 |
+
### New API Endpoint
|
| 165 |
+
```
|
| 166 |
+
GET /brain/graph?entity=Acme+Corp
|
| 167 |
+
→ Returns full relationship web: people, events, timelines, predictions connected to Acme Corp
|
| 168 |
+
|
| 169 |
+
POST /brain/reason
|
| 170 |
+
Body: { "question": "How is the Q2 target connected to the engineering delays?" }
|
| 171 |
+
→ Multi-hop reasoning across the knowledge graph with temporal evidence
|
| 172 |
+
```
|
| 173 |
+
|
| 174 |
+
### Why It's Revolutionary
|
| 175 |
+
This is the difference between a **filing cabinet** and a **brain**. Filing cabinets store things. Brains *connect* things. When Chronos can say "The Q2 revenue target is at risk because the engineer who was building the key feature left, and the replacement won't be onboarded until May based on your historical hiring timeline" — that's not memory. That's **understanding**.
|
| 176 |
+
|
| 177 |
+
---
|
| 178 |
+
|
| 179 |
+
## Layer 5: 🤖 Autonomous Orchestration
|
| 180 |
+
|
| 181 |
+
> **"A brain that remembers, predicts, sees, and connects — but can also ACT."**
|
| 182 |
+
|
| 183 |
+
### What It Does
|
| 184 |
+
Using the connected SaaS tools from `/connect`, the brain can:
|
| 185 |
+
1. **Self-dispatch actions**: "Your Stripe subscription for Client X expires tomorrow. Based on past behavior, they always renew late. Sending a reminder email via the connected email tool."
|
| 186 |
+
2. **Multi-agent coordination**: Spin up specialized sub-agents for different tasks, each with full temporal memory access
|
| 187 |
+
3. **Learn from outcomes**: Track what actions worked and adapt strategies
|
| 188 |
+
|
| 189 |
+
### Models Used
|
| 190 |
+
| Task | Model | Provider | Cost |
|
| 191 |
+
|---|---|---|---|
|
| 192 |
+
| Multi-agent orchestration | Nemotron-3-Super 120B (NVIDIA NIM) | NVIDIA | Free credits |
|
| 193 |
+
| Complex agentic workflows | MiniMax-M2.7 Cloud | Ollama Cloud | Free |
|
| 194 |
+
| Code generation for tool calls | Qwen3.6-Coder-Next Cloud | Ollama Cloud | Free |
|
| 195 |
+
|
| 196 |
+
### New API Endpoint
|
| 197 |
+
```
|
| 198 |
+
POST /brain/act
|
| 199 |
+
Body: { "directive": "Keep all SaaS subscriptions active and negotiate renewals proactively" }
|
| 200 |
+
→ Brain runs autonomously, using memory + predictions + connected tools
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
---
|
| 204 |
+
|
| 205 |
+
## Model-to-Task Master Map
|
| 206 |
+
|
| 207 |
+
| Layer | Task | Best Model | Provider | Free? |
|
| 208 |
+
|---|---|---|---|---|
|
| 209 |
+
| 0 | SVO Extraction (fast) | Llama 3.1 8B | Groq | ✅ 14.4K req/day |
|
| 210 |
+
| 0 | Agent Reasoning | Qwen 3 235B | Cerebras | ✅ 1M tokens/day |
|
| 211 |
+
| 1 | Memory Consolidation | Qwen 3 235B | Cerebras | ✅ |
|
| 212 |
+
| 2 | Predictive Reasoning | GLM-5.1 671B | Ollama Cloud | ✅ |
|
| 213 |
+
| 2 | Pattern Analysis | Qwen 3.5 122B | Ollama Cloud | ✅ |
|
| 214 |
+
| 3 | Vision/Multimodal | Kimi-K2.5 | Ollama Cloud | ✅ |
|
| 215 |
+
| 3 | Fast Vision | Gemma 4 31B | Ollama Cloud | ✅ |
|
| 216 |
+
| 4 | Entity Extraction | DeepSeek-V3.2 671B | Ollama Cloud | ✅ |
|
| 217 |
+
| 4 | Graph Reasoning | GLM-5.1 | Ollama Cloud | ✅ |
|
| 218 |
+
| 5 | Multi-Agent Orchestration | Nemotron-3-Super 120B | NVIDIA NIM | ✅ Free credits |
|
| 219 |
+
| 5 | Tool-Use Coding | Qwen3.6-Coder-Next 480B | Ollama Cloud | ✅ |
|
| 220 |
+
|
| 221 |
+
**Total infrastructure cost: $0/month** (all on free tiers)
|
| 222 |
+
|
| 223 |
+
---
|
| 224 |
+
|
| 225 |
+
## Implementation Roadmap
|
| 226 |
+
|
| 227 |
+
### Phase 1: Foundation (Already Done ✅)
|
| 228 |
+
- [x] SVO extraction pipeline
|
| 229 |
+
- [x] Dual calendar storage (PostgreSQL + pgvector)
|
| 230 |
+
- [x] Hybrid query (semantic + temporal + entity)
|
| 231 |
+
- [x] Tenant isolation
|
| 232 |
+
- [x] API key billing system
|
| 233 |
+
|
| 234 |
+
### Phase 2: The Sleep Cycle (1-2 weeks)
|
| 235 |
+
- [ ] Background consolidation job (cron or scheduled task)
|
| 236 |
+
- [ ] Insight generation endpoint
|
| 237 |
+
- [ ] Decay scoring for event relevance
|
| 238 |
+
- [ ] `GET /memory/insights` API
|
| 239 |
+
|
| 240 |
+
### Phase 3: Predictive Brain (2-3 weeks)
|
| 241 |
+
- [ ] Pattern detection pipeline
|
| 242 |
+
- [ ] Anomaly alerting system
|
| 243 |
+
- [ ] `POST /brain/predict` API
|
| 244 |
+
- [ ] Ollama Cloud integration for GLM-5.1
|
| 245 |
+
|
| 246 |
+
### Phase 4: Multi-Modal Ingestion (2-3 weeks)
|
| 247 |
+
- [ ] Image/screenshot → SVO pipeline
|
| 248 |
+
- [ ] PDF/document parser
|
| 249 |
+
- [ ] `POST /ingest/multimodal` API
|
| 250 |
+
- [ ] Kimi-K2.5 / Gemma 4 integration
|
| 251 |
+
|
| 252 |
+
### Phase 5: Knowledge Graph (3-4 weeks)
|
| 253 |
+
- [ ] Entity relationship extraction
|
| 254 |
+
- [ ] Graph database layer (or PostgreSQL recursive CTEs)
|
| 255 |
+
- [ ] `GET /brain/graph` + `POST /brain/reason` APIs
|
| 256 |
+
- [ ] DeepSeek-V3.2 integration
|
| 257 |
+
|
| 258 |
+
### Phase 6: Autonomous Actions (4-6 weeks)
|
| 259 |
+
- [ ] Action planning from memory + predictions
|
| 260 |
+
- [ ] Multi-agent spawning
|
| 261 |
+
- [ ] Outcome tracking & learning loop
|
| 262 |
+
- [ ] `POST /brain/act` API
|
| 263 |
+
|
| 264 |
+
---
|
| 265 |
+
|
| 266 |
+
## The Pitch (One Sentence)
|
| 267 |
+
|
| 268 |
+
> **"Chronos OS is the first AI infrastructure that doesn't just store memories — it consolidates them like sleep, predicts futures from patterns, sees through images, connects knowledge into graphs, and acts autonomously. It is the temporal brain that every AI agent in the world is missing."**
|
| 269 |
+
|
| 270 |
+
---
|
| 271 |
+
|
| 272 |
+
## Why No One Can Copy This
|
| 273 |
+
|
| 274 |
+
1. **Network effect**: Every tenant's usage makes the system smarter at extraction and prediction
|
| 275 |
+
2. **Temporal moat**: The longer someone uses Chronos, the more irreplaceable it becomes (months/years of structured temporal memory)
|
| 276 |
+
3. **Free compute**: By aggregating free tiers from 4+ providers, you get frontier-level AI at $0 — competitors paying for GPT-4o can't match your margins
|
| 277 |
+
4. **Research foundation**: Built on the actual Chronos paper (arXiv 2603.16862) — not a toy wrapper
|
| 278 |
+
|
| 279 |
+
---
|
| 280 |
+
|
| 281 |
+
*This is the letter to the future that the big labs haven't written yet.*
|
| 282 |
+
*Let's write it.* 🕰️
|
docs/design.md
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
DESIGN SYSTEM — Each article uses a distinct aesthetic
|
| 2 |
+
|
| 3 |
+
**Rule:** Every article has a unique font pair, color palette, and layout. Never repeat. Rotate between light/dark themes.
|
| 4 |
+
|
| 5 |
+
**Used so far:**
|
| 6 |
+
- Dark/gold, Forest green, Plum, Black+yellow, Ochre/concrete
|
| 7 |
+
- Swiss white+red, Burgundy+cream, Brutalist lime+black, Navy+gold
|
| 8 |
+
- Dark amber candlelight, Sage+linen, Rose+midnight, Stone+copper
|
| 9 |
+
|
| 10 |
+
**Available for upcoming:** Teal, terracotta, deep emerald, arctic blue, warm charcoal, dusty lavender, burnt orange, ice white
|
| 11 |
+
|
| 12 |
+
**Font pairs used:**
|
| 13 |
+
- Spectral + Syne, Instrument Serif + Sans, EB Garamond + Figtree
|
| 14 |
+
- Playfair Display + Barlow, Cardo + Mulish, Italiana + Jost
|
| 15 |
+
- Playfair + Lato, Big Shoulders + IBM Plex Mono, Crimson Pro + DM Sans
|
| 16 |
+
- Cinzel + Gentium Plus, Cormorant + Karla, Abril Fatface + Nunito
|
| 17 |
+
- IM Fell English + Raleway
|
| 18 |
+
|
| 19 |
+
---
|
| 20 |
+
|
| 21 |
+
## ARTICLE CTA PHILOSOPHY
|
| 22 |
+
- No pricing mention, no "free", no "coming soon"
|
| 23 |
+
- Each CTA continues the article's voice
|
| 24 |
+
- Always ends with a sealed/lock metaphor
|
| 25 |
+
- Button text: "Open CHRONOS"
|
docs/error.txt
ADDED
|
@@ -0,0 +1,825 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
===== Application Startup at 2026-04-20 19:16:52 =====
|
| 2 |
+
|
| 3 |
+
>>> SVO PARSER v9 LOADED — _extract_json active <<<
|
| 4 |
+
INFO: Started server process [1]
|
| 5 |
+
INFO: Waiting for application startup.
|
| 6 |
+
2026-04-20 19:17:49,475 | chronos.api | INFO | 🕰️ Chronos OS starting up...
|
| 7 |
+
2026-04-20 19:17:52,762 | chronos.memory_store | INFO | Memory store initialized (Neon PostgreSQL)
|
| 8 |
+
2026-04-20 19:17:53,191 | chronos.vector_store | INFO | Vector store initialized (pgvector 384d)
|
| 9 |
+
2026-04-20 19:17:56,114 | chronos.svo_parser | INFO | SVO parser initialized with fast pipeline: groq/llama-3.1-8b-instant
|
| 10 |
+
2026-04-20 19:17:56,114 | chronos.api | INFO | ✅ Chronos OS ready — Systems online
|
| 11 |
+
INFO: Application startup complete.
|
| 12 |
+
INFO: Uvicorn running on http://0.0.0.0:7860 (Press CTRL+C to quit)
|
| 13 |
+
INFO: 10.16.24.44:53206 - "GET /?logs=container HTTP/1.1" 200 OK
|
| 14 |
+
INFO: 10.16.44.111:5417 - "GET /?logs=container HTTP/1.1" 200 OK
|
| 15 |
+
2026-04-20 19:17:58,345 | sentence_transformers.base.model | INFO | No device provided, using cpu
|
| 16 |
+
2026-04-20 19:17:58,368 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/modules.json "HTTP/1.1 307 Temporary Redirect"
|
| 17 |
+
2026-04-20 19:17:58,375 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/modules.json "HTTP/1.1 200 OK"
|
| 18 |
+
2026-04-20 19:17:58,384 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/modules.json "HTTP/1.1 200 OK"
|
| 19 |
+
2026-04-20 19:17:58,392 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config_sentence_transformers.json "HTTP/1.1 307 Temporary Redirect"
|
| 20 |
+
2026-04-20 19:17:58,398 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config_sentence_transformers.json "HTTP/1.1 200 OK"
|
| 21 |
+
2026-04-20 19:17:58,406 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config_sentence_transformers.json "HTTP/1.1 200 OK"
|
| 22 |
+
2026-04-20 19:17:58,407 | sentence_transformers.base.model | INFO | Loading SentenceTransformer model from sentence-transformers/all-MiniLM-L6-v2.
|
| 23 |
+
2026-04-20 19:17:58,415 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config_sentence_transformers.json "HTTP/1.1 307 Temporary Redirect"
|
| 24 |
+
2026-04-20 19:17:58,425 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config_sentence_transformers.json "HTTP/1.1 200 OK"
|
| 25 |
+
2026-04-20 19:17:58,432 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/README.md "HTTP/1.1 307 Temporary Redirect"
|
| 26 |
+
Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
|
| 27 |
+
2026-04-20 19:17:58,432 | huggingface_hub.utils._http | WARNING | Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.
|
| 28 |
+
2026-04-20 19:17:58,438 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/README.md "HTTP/1.1 200 OK"
|
| 29 |
+
2026-04-20 19:17:58,447 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/README.md "HTTP/1.1 200 OK"
|
| 30 |
+
2026-04-20 19:17:58,454 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/modules.json "HTTP/1.1 307 Temporary Redirect"
|
| 31 |
+
2026-04-20 19:17:58,459 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/modules.json "HTTP/1.1 200 OK"
|
| 32 |
+
2026-04-20 19:17:58,466 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/sentence_bert_config.json "HTTP/1.1 307 Temporary Redirect"
|
| 33 |
+
2026-04-20 19:17:58,471 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/sentence_bert_config.json "HTTP/1.1 200 OK"
|
| 34 |
+
2026-04-20 19:17:58,478 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/sentence_bert_config.json "HTTP/1.1 200 OK"
|
| 35 |
+
2026-04-20 19:17:58,487 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/adapter_config.json "HTTP/1.1 404 Not Found"
|
| 36 |
+
2026-04-20 19:17:58,495 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config.json "HTTP/1.1 307 Temporary Redirect"
|
| 37 |
+
2026-04-20 19:17:58,501 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config.json "HTTP/1.1 200 OK"
|
| 38 |
+
2026-04-20 19:17:58,508 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config.json "HTTP/1.1 200 OK"
|
| 39 |
+
2026-04-20 19:17:58,556 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/model.safetensors "HTTP/1.1 302 Found"
|
| 40 |
+
2026-04-20 19:17:58,572 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2/xet-read-token/c9745ed1d9f207416be6d2e6f8de32d1f16199bf "HTTP/1.1 200 OK"
|
| 41 |
+
Loading weights: 0%| | 0/103 [00:00<?, ?it/s]
|
| 42 |
+
Loading weights: 100%|██████████| 103/103 [00:00<00:00, 4758.33it/s]
|
| 43 |
+
BertModel LOAD REPORT from: sentence-transformers/all-MiniLM-L6-v2
|
| 44 |
+
Key | Status | |
|
| 45 |
+
------------------------+------------+--+-
|
| 46 |
+
embeddings.position_ids | UNEXPECTED | |
|
| 47 |
+
|
| 48 |
+
Notes:
|
| 49 |
+
- UNEXPECTED: can be ignored when loading from different task/architecture; not ok if you expect identical arch.
|
| 50 |
+
2026-04-20 19:17:59,792 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/processor_config.json "HTTP/1.1 404 Not Found"
|
| 51 |
+
2026-04-20 19:17:59,801 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/preprocessor_config.json "HTTP/1.1 404 Not Found"
|
| 52 |
+
2026-04-20 19:17:59,810 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/video_preprocessor_config.json "HTTP/1.1 404 Not Found"
|
| 53 |
+
2026-04-20 19:17:59,818 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/preprocessor_config.json "HTTP/1.1 404 Not Found"
|
| 54 |
+
2026-04-20 19:17:59,824 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer_config.json "HTTP/1.1 307 Temporary Redirect"
|
| 55 |
+
2026-04-20 19:17:59,830 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/tokenizer_config.json "HTTP/1.1 200 OK"
|
| 56 |
+
2026-04-20 19:17:59,839 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/tokenizer_config.json "HTTP/1.1 200 OK"
|
| 57 |
+
2026-04-20 19:17:59,850 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config.json "HTTP/1.1 307 Temporary Redirect"
|
| 58 |
+
2026-04-20 19:17:59,856 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config.json "HTTP/1.1 200 OK"
|
| 59 |
+
2026-04-20 19:17:59,877 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/config.json "HTTP/1.1 307 Temporary Redirect"
|
| 60 |
+
2026-04-20 19:17:59,883 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/config.json "HTTP/1.1 200 OK"
|
| 61 |
+
2026-04-20 19:17:59,891 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer_config.json "HTTP/1.1 307 Temporary Redirect"
|
| 62 |
+
2026-04-20 19:17:59,896 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/tokenizer_config.json "HTTP/1.1 200 OK"
|
| 63 |
+
2026-04-20 19:17:59,905 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2/tree/main/additional_chat_templates?recursive=false&expand=false "HTTP/1.1 404 Not Found"
|
| 64 |
+
2026-04-20 19:17:59,923 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2/tree/main?recursive=true&expand=false "HTTP/1.1 200 OK"
|
| 65 |
+
2026-04-20 19:17:59,933 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/vocab.txt "HTTP/1.1 307 Temporary Redirect"
|
| 66 |
+
2026-04-20 19:17:59,939 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/vocab.txt "HTTP/1.1 200 OK"
|
| 67 |
+
2026-04-20 19:17:59,948 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/vocab.txt "HTTP/1.1 200 OK"
|
| 68 |
+
2026-04-20 19:17:59,959 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/tokenizer.json "HTTP/1.1 307 Temporary Redirect"
|
| 69 |
+
2026-04-20 19:17:59,965 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/tokenizer.json "HTTP/1.1 200 OK"
|
| 70 |
+
2026-04-20 19:17:59,974 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/tokenizer.json "HTTP/1.1 200 OK"
|
| 71 |
+
2026-04-20 19:17:59,994 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/added_tokens.json "HTTP/1.1 404 Not Found"
|
| 72 |
+
2026-04-20 19:18:00,016 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/special_tokens_map.json "HTTP/1.1 307 Temporary Redirect"
|
| 73 |
+
2026-04-20 19:18:00,032 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/special_tokens_map.json "HTTP/1.1 200 OK"
|
| 74 |
+
2026-04-20 19:18:00,050 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/special_tokens_map.json "HTTP/1.1 200 OK"
|
| 75 |
+
2026-04-20 19:18:00,061 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/chat_template.jinja "HTTP/1.1 404 Not Found"
|
| 76 |
+
2026-04-20 19:18:00,105 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/sentence-transformers/all-MiniLM-L6-v2/resolve/main/1_Pooling/config.json "HTTP/1.1 307 Temporary Redirect"
|
| 77 |
+
2026-04-20 19:18:00,111 | httpx | INFO | HTTP Request: HEAD https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/1_Pooling%2Fconfig.json "HTTP/1.1 200 OK"
|
| 78 |
+
2026-04-20 19:18:00,118 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/resolve-cache/models/sentence-transformers/all-MiniLM-L6-v2/c9745ed1d9f207416be6d2e6f8de32d1f16199bf/1_Pooling%2Fconfig.json "HTTP/1.1 200 OK"
|
| 79 |
+
2026-04-20 19:18:00,129 | httpx | INFO | HTTP Request: GET https://huggingface.co/api/models/sentence-transformers/all-MiniLM-L6-v2 "HTTP/1.1 200 OK"
|
| 80 |
+
2026-04-20 19:18:00,136 | chronos.vector_store | INFO | Embedding model loaded: all-MiniLM-L6-v2
|
| 81 |
+
2026-04-20 19:18:20,846 | chronos.svo_parser | INFO | [DEBUG] Calling litellm.acompletion attempt 1...
|
| 82 |
+
19:18:20 - LiteLLM:INFO: utils.py:4007 -
|
| 83 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 84 |
+
2026-04-20 19:18:20,858 | LiteLLM | INFO |
|
| 85 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 86 |
+
|
| 87 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 88 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 89 |
+
|
| 90 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 91 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 92 |
+
2026-04-20 19:18:21,071 | LiteLLM | INFO |
|
| 93 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 94 |
+
|
| 95 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 96 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 97 |
+
|
| 98 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 99 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 100 |
+
2026-04-20 19:18:21,088 | LiteLLM | INFO |
|
| 101 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 102 |
+
|
| 103 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 104 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 105 |
+
|
| 106 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 107 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 108 |
+
2026-04-20 19:18:21,104 | LiteLLM | INFO |
|
| 109 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 110 |
+
|
| 111 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 112 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 113 |
+
|
| 114 |
+
19:18:21 - LiteLLM:ERROR: fallback_utils.py:68 - Fallback attempt failed for model groq/llama-3.1-8b-instant: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 115 |
+
Traceback (most recent call last):
|
| 116 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 176, in _make_common_async_call
|
| 117 |
+
response = await async_httpx_client.post(
|
| 118 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 119 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/logging_utils.py", line 297, in async_wrapper
|
| 120 |
+
result = await func(*args, **kwargs)
|
| 121 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 122 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 515, in post
|
| 123 |
+
raise e
|
| 124 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 468, in post
|
| 125 |
+
response = await self.client.send(req, stream=stream)
|
| 126 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 127 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
| 128 |
+
response = await self._send_handling_auth(
|
| 129 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 130 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
| 131 |
+
response = await self._send_handling_redirects(
|
| 132 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 133 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
| 134 |
+
response = await self._send_single_request(request)
|
| 135 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 136 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
| 137 |
+
response = await transport.handle_async_request(request)
|
| 138 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 139 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 307, in handle_async_request
|
| 140 |
+
response = await self._make_aiohttp_request(
|
| 141 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 142 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 285, in _make_aiohttp_request
|
| 143 |
+
response = await client_session.request(**request_kwargs).__aenter__()
|
| 144 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 145 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 1521, in __aenter__
|
| 146 |
+
self._resp: _RetType = await self._coro
|
| 147 |
+
^^^^^^^^^^^^^^^^
|
| 148 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 788, in _request
|
| 149 |
+
resp = await handler(req)
|
| 150 |
+
^^^^^^^^^^^^^^^^^^
|
| 151 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 764, in _connect_and_send_request
|
| 152 |
+
resp = await req.send(conn)
|
| 153 |
+
^^^^^^^^^^^^^^^^^^^^
|
| 154 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client_reqrep.py", line 1470, in send
|
| 155 |
+
await writer.write_headers(status_line, self.headers)
|
| 156 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/http_writer.py", line 217, in write_headers
|
| 157 |
+
buf = _serialize_headers(status_line, headers)
|
| 158 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 159 |
+
File "aiohttp/_http_writer.pyx", line 148, in aiohttp._http_writer._serialize_headers
|
| 160 |
+
File "aiohttp/_http_writer.pyx", line 115, in aiohttp._http_writer._write_str_raise_on_nlcr
|
| 161 |
+
ValueError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 162 |
+
|
| 163 |
+
During handling of the above exception, another exception occurred:
|
| 164 |
+
|
| 165 |
+
Traceback (most recent call last):
|
| 166 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 620, in acompletion
|
| 167 |
+
response = await init_response
|
| 168 |
+
^^^^^^^^^^^^^^^^^^^
|
| 169 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 308, in async_completion
|
| 170 |
+
response = await self._make_common_async_call(
|
| 171 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 172 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 203, in _make_common_async_call
|
| 173 |
+
raise self._handle_error(e=e, provider_config=provider_config)
|
| 174 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 175 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 4721, in _handle_error
|
| 176 |
+
raise provider_config.get_error_class(
|
| 177 |
+
litellm.llms.openai.common_utils.OpenAIError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 178 |
+
|
| 179 |
+
During handling of the above exception, another exception occurred:
|
| 180 |
+
|
| 181 |
+
Traceback (most recent call last):
|
| 182 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/fallback_utils.py", line 58, in async_completion_with_fallbacks
|
| 183 |
+
response = await litellm.acompletion(
|
| 184 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 185 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 2093, in wrapper_async
|
| 186 |
+
raise e
|
| 187 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 1892, in wrapper_async
|
| 188 |
+
result = await original_function(*args, **kwargs)
|
| 189 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 190 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 639, in acompletion
|
| 191 |
+
raise exception_type(
|
| 192 |
+
^^^^^^^^^^^^^^^
|
| 193 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 2456, in exception_type
|
| 194 |
+
raise e
|
| 195 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 588, in exception_type
|
| 196 |
+
raise InternalServerError(
|
| 197 |
+
litellm.exceptions.InternalServerError: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 198 |
+
2026-04-20 19:18:21,115 | LiteLLM | ERROR | Fallback attempt failed for model groq/llama-3.1-8b-instant: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 199 |
+
Traceback (most recent call last):
|
| 200 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 176, in _make_common_async_call
|
| 201 |
+
response = await async_httpx_client.post(
|
| 202 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 203 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/logging_utils.py", line 297, in async_wrapper
|
| 204 |
+
result = await func(*args, **kwargs)
|
| 205 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 206 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 515, in post
|
| 207 |
+
raise e
|
| 208 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 468, in post
|
| 209 |
+
response = await self.client.send(req, stream=stream)
|
| 210 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 211 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
| 212 |
+
response = await self._send_handling_auth(
|
| 213 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 214 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
| 215 |
+
response = await self._send_handling_redirects(
|
| 216 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 217 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
| 218 |
+
response = await self._send_single_request(request)
|
| 219 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 220 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
| 221 |
+
response = await transport.handle_async_request(request)
|
| 222 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 223 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 307, in handle_async_request
|
| 224 |
+
response = await self._make_aiohttp_request(
|
| 225 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 226 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 285, in _make_aiohttp_request
|
| 227 |
+
response = await client_session.request(**request_kwargs).__aenter__()
|
| 228 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 229 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 1521, in __aenter__
|
| 230 |
+
self._resp: _RetType = await self._coro
|
| 231 |
+
^^^^^^^^^^^^^^^^
|
| 232 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 788, in _request
|
| 233 |
+
resp = await handler(req)
|
| 234 |
+
^^^^^^^^^^^^^^^^^^
|
| 235 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 764, in _connect_and_send_request
|
| 236 |
+
resp = await req.send(conn)
|
| 237 |
+
^^^^^^^^^^^^^^^^^^^^
|
| 238 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client_reqrep.py", line 1470, in send
|
| 239 |
+
await writer.write_headers(status_line, self.headers)
|
| 240 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/http_writer.py", line 217, in write_headers
|
| 241 |
+
buf = _serialize_headers(status_line, headers)
|
| 242 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 243 |
+
File "aiohttp/_http_writer.pyx", line 148, in aiohttp._http_writer._serialize_headers
|
| 244 |
+
File "aiohttp/_http_writer.pyx", line 115, in aiohttp._http_writer._write_str_raise_on_nlcr
|
| 245 |
+
ValueError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 246 |
+
|
| 247 |
+
During handling of the above exception, another exception occurred:
|
| 248 |
+
|
| 249 |
+
Traceback (most recent call last):
|
| 250 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 620, in acompletion
|
| 251 |
+
response = await init_response
|
| 252 |
+
^^^^^^^^^^^^^^^^^^^
|
| 253 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 308, in async_completion
|
| 254 |
+
response = await self._make_common_async_call(
|
| 255 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 256 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 203, in _make_common_async_call
|
| 257 |
+
raise self._handle_error(e=e, provider_config=provider_config)
|
| 258 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 259 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 4721, in _handle_error
|
| 260 |
+
raise provider_config.get_error_class(
|
| 261 |
+
litellm.llms.openai.common_utils.OpenAIError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 262 |
+
|
| 263 |
+
During handling of the above exception, another exception occurred:
|
| 264 |
+
|
| 265 |
+
Traceback (most recent call last):
|
| 266 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/fallback_utils.py", line 58, in async_completion_with_fallbacks
|
| 267 |
+
response = await litellm.acompletion(
|
| 268 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 269 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 2093, in wrapper_async
|
| 270 |
+
raise e
|
| 271 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 1892, in wrapper_async
|
| 272 |
+
result = await original_function(*args, **kwargs)
|
| 273 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 274 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 639, in acompletion
|
| 275 |
+
raise exception_type(
|
| 276 |
+
^^^^^^^^^^^^^^^
|
| 277 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 2456, in exception_type
|
| 278 |
+
raise e
|
| 279 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 588, in exception_type
|
| 280 |
+
raise InternalServerError(
|
| 281 |
+
litellm.exceptions.InternalServerError: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 282 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 283 |
+
LiteLLM completion() model= llama3.1-8b; provider = cerebras
|
| 284 |
+
2026-04-20 19:18:21,123 | LiteLLM | INFO |
|
| 285 |
+
LiteLLM completion() model= llama3.1-8b; provider = cerebras
|
| 286 |
+
2026-04-20 19:18:21,543 | chronos.svo_parser | INFO | [DEBUG] Response received. Type: <class 'litellm.types.utils.ModelResponse'>
|
| 287 |
+
2026-04-20 19:18:21,543 | chronos.svo_parser | INFO | [DEBUG] Message type: <class 'litellm.types.utils.Message'>, content type: <class 'str'>
|
| 288 |
+
2026-04-20 19:18:21,543 | chronos.svo_parser | INFO | [DEBUG] Content repr: '[\n {\n "subject": "I",\n "verb": "scheduled",\n "object": "a marketing activity",\n "timestamp": "2026-04-20T19:18:14.891000+00:00",\n "datetime_start": "2026-04-20T22:30:00+00:00",\
|
| 289 |
+
2026-04-20 19:18:21,543 | chronos.svo_parser | INFO | [DEBUG] Calling _extract_json with 531 chars
|
| 290 |
+
2026-04-20 19:18:21,543 | chronos.svo_parser | INFO | _extract_json called with 531 chars: [
|
| 291 |
+
{
|
| 292 |
+
"subject": "I",
|
| 293 |
+
"verb": "scheduled",
|
| 294 |
+
"object": "a marketing ac...
|
| 295 |
+
2026-04-20 19:18:21,543 | chronos.svo_parser | INFO | [DEBUG] _extract_json returned type: <class 'list'>
|
| 296 |
+
2026-04-20 19:18:21,543 | chronos.svo_parser | INFO | Fast Pipeline (groq/llama-3.1-8b-instant) extracted 2 SVO tuples from text (112 chars)
|
| 297 |
+
2026-04-20 19:18:21,717 | chronos.svo_parser | INFO | [DEBUG] Calling litellm.acompletion attempt 1...
|
| 298 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 299 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 300 |
+
2026-04-20 19:18:21,719 | LiteLLM | INFO |
|
| 301 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 302 |
+
|
| 303 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 304 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 305 |
+
|
| 306 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 307 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 308 |
+
2026-04-20 19:18:21,737 | LiteLLM | INFO |
|
| 309 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 310 |
+
|
| 311 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 312 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 313 |
+
|
| 314 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 315 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 316 |
+
2026-04-20 19:18:21,749 | LiteLLM | INFO |
|
| 317 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 318 |
+
|
| 319 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 320 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 321 |
+
|
| 322 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 323 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 324 |
+
2026-04-20 19:18:21,761 | LiteLLM | INFO |
|
| 325 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 326 |
+
|
| 327 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 328 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 329 |
+
|
| 330 |
+
19:18:21 - LiteLLM:ERROR: fallback_utils.py:68 - Fallback attempt failed for model groq/llama-3.1-8b-instant: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 331 |
+
Traceback (most recent call last):
|
| 332 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 176, in _make_common_async_call
|
| 333 |
+
response = await async_httpx_client.post(
|
| 334 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 335 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/logging_utils.py", line 297, in async_wrapper
|
| 336 |
+
result = await func(*args, **kwargs)
|
| 337 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 338 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 515, in post
|
| 339 |
+
raise e
|
| 340 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 468, in post
|
| 341 |
+
response = await self.client.send(req, stream=stream)
|
| 342 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 343 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
| 344 |
+
response = await self._send_handling_auth(
|
| 345 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 346 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
| 347 |
+
response = await self._send_handling_redirects(
|
| 348 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 349 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
| 350 |
+
response = await self._send_single_request(request)
|
| 351 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 352 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
| 353 |
+
response = await transport.handle_async_request(request)
|
| 354 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 355 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 307, in handle_async_request
|
| 356 |
+
response = await self._make_aiohttp_request(
|
| 357 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 358 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 285, in _make_aiohttp_request
|
| 359 |
+
response = await client_session.request(**request_kwargs).__aenter__()
|
| 360 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 361 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 1521, in __aenter__
|
| 362 |
+
self._resp: _RetType = await self._coro
|
| 363 |
+
^^^^^^^^^^^^^^^^
|
| 364 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 788, in _request
|
| 365 |
+
resp = await handler(req)
|
| 366 |
+
^^^^^^^^^^^^^^^^^^
|
| 367 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 764, in _connect_and_send_request
|
| 368 |
+
resp = await req.send(conn)
|
| 369 |
+
^^^^^^^^^^^^^^^^^^^^
|
| 370 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client_reqrep.py", line 1470, in send
|
| 371 |
+
await writer.write_headers(status_line, self.headers)
|
| 372 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/http_writer.py", line 217, in write_headers
|
| 373 |
+
buf = _serialize_headers(status_line, headers)
|
| 374 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 375 |
+
File "aiohttp/_http_writer.pyx", line 148, in aiohttp._http_writer._serialize_headers
|
| 376 |
+
File "aiohttp/_http_writer.pyx", line 115, in aiohttp._http_writer._write_str_raise_on_nlcr
|
| 377 |
+
ValueError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 378 |
+
|
| 379 |
+
During handling of the above exception, another exception occurred:
|
| 380 |
+
|
| 381 |
+
Traceback (most recent call last):
|
| 382 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 620, in acompletion
|
| 383 |
+
response = await init_response
|
| 384 |
+
^^^^^^^^^^^^^^^^^^^
|
| 385 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 308, in async_completion
|
| 386 |
+
response = await self._make_common_async_call(
|
| 387 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 388 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 203, in _make_common_async_call
|
| 389 |
+
raise self._handle_error(e=e, provider_config=provider_config)
|
| 390 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 391 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 4721, in _handle_error
|
| 392 |
+
raise provider_config.get_error_class(
|
| 393 |
+
litellm.llms.openai.common_utils.OpenAIError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 394 |
+
|
| 395 |
+
During handling of the above exception, another exception occurred:
|
| 396 |
+
|
| 397 |
+
Traceback (most recent call last):
|
| 398 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/fallback_utils.py", line 58, in async_completion_with_fallbacks
|
| 399 |
+
response = await litellm.acompletion(
|
| 400 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 401 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 2093, in wrapper_async
|
| 402 |
+
raise e
|
| 403 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 1892, in wrapper_async
|
| 404 |
+
result = await original_function(*args, **kwargs)
|
| 405 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 406 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 639, in acompletion
|
| 407 |
+
raise exception_type(
|
| 408 |
+
^^^^^^^^^^^^^^^
|
| 409 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 2456, in exception_type
|
| 410 |
+
raise e
|
| 411 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 588, in exception_type
|
| 412 |
+
raise InternalServerError(
|
| 413 |
+
litellm.exceptions.InternalServerError: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 414 |
+
2026-04-20 19:18:21,771 | LiteLLM | ERROR | Fallback attempt failed for model groq/llama-3.1-8b-instant: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 415 |
+
Traceback (most recent call last):
|
| 416 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 176, in _make_common_async_call
|
| 417 |
+
response = await async_httpx_client.post(
|
| 418 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 419 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/logging_utils.py", line 297, in async_wrapper
|
| 420 |
+
result = await func(*args, **kwargs)
|
| 421 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 422 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 515, in post
|
| 423 |
+
raise e
|
| 424 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 468, in post
|
| 425 |
+
response = await self.client.send(req, stream=stream)
|
| 426 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 427 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
| 428 |
+
response = await self._send_handling_auth(
|
| 429 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 430 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
| 431 |
+
response = await self._send_handling_redirects(
|
| 432 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 433 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
| 434 |
+
response = await self._send_single_request(request)
|
| 435 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 436 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
| 437 |
+
response = await transport.handle_async_request(request)
|
| 438 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 439 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 307, in handle_async_request
|
| 440 |
+
response = await self._make_aiohttp_request(
|
| 441 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 442 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 285, in _make_aiohttp_request
|
| 443 |
+
response = await client_session.request(**request_kwargs).__aenter__()
|
| 444 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 445 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 1521, in __aenter__
|
| 446 |
+
self._resp: _RetType = await self._coro
|
| 447 |
+
^^^^^^^^^^^^^^^^
|
| 448 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 788, in _request
|
| 449 |
+
resp = await handler(req)
|
| 450 |
+
^^^^^^^^^^^^^^^^^^
|
| 451 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 764, in _connect_and_send_request
|
| 452 |
+
resp = await req.send(conn)
|
| 453 |
+
^^^^^^^^^^^^^^^^^^^^
|
| 454 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client_reqrep.py", line 1470, in send
|
| 455 |
+
await writer.write_headers(status_line, self.headers)
|
| 456 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/http_writer.py", line 217, in write_headers
|
| 457 |
+
buf = _serialize_headers(status_line, headers)
|
| 458 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 459 |
+
File "aiohttp/_http_writer.pyx", line 148, in aiohttp._http_writer._serialize_headers
|
| 460 |
+
File "aiohttp/_http_writer.pyx", line 115, in aiohttp._http_writer._write_str_raise_on_nlcr
|
| 461 |
+
ValueError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 462 |
+
|
| 463 |
+
During handling of the above exception, another exception occurred:
|
| 464 |
+
|
| 465 |
+
Traceback (most recent call last):
|
| 466 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 620, in acompletion
|
| 467 |
+
response = await init_response
|
| 468 |
+
^^^^^^^^^^^^^^^^^^^
|
| 469 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 308, in async_completion
|
| 470 |
+
response = await self._make_common_async_call(
|
| 471 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 472 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 203, in _make_common_async_call
|
| 473 |
+
raise self._handle_error(e=e, provider_config=provider_config)
|
| 474 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 475 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 4721, in _handle_error
|
| 476 |
+
raise provider_config.get_error_class(
|
| 477 |
+
litellm.llms.openai.common_utils.OpenAIError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 478 |
+
|
| 479 |
+
During handling of the above exception, another exception occurred:
|
| 480 |
+
|
| 481 |
+
Traceback (most recent call last):
|
| 482 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/fallback_utils.py", line 58, in async_completion_with_fallbacks
|
| 483 |
+
response = await litellm.acompletion(
|
| 484 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 485 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 2093, in wrapper_async
|
| 486 |
+
raise e
|
| 487 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 1892, in wrapper_async
|
| 488 |
+
result = await original_function(*args, **kwargs)
|
| 489 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 490 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 639, in acompletion
|
| 491 |
+
raise exception_type(
|
| 492 |
+
^^^^^^^^^^^^^^^
|
| 493 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 2456, in exception_type
|
| 494 |
+
raise e
|
| 495 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 588, in exception_type
|
| 496 |
+
raise InternalServerError(
|
| 497 |
+
litellm.exceptions.InternalServerError: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 498 |
+
19:18:21 - LiteLLM:INFO: utils.py:4007 -
|
| 499 |
+
LiteLLM completion() model= llama3.1-8b; provider = cerebras
|
| 500 |
+
2026-04-20 19:18:21,777 | LiteLLM | INFO |
|
| 501 |
+
LiteLLM completion() model= llama3.1-8b; provider = cerebras
|
| 502 |
+
2026-04-20 19:18:22,078 | chronos.svo_parser | INFO | [DEBUG] Response received. Type: <class 'litellm.types.utils.ModelResponse'>
|
| 503 |
+
2026-04-20 19:18:22,078 | chronos.svo_parser | INFO | [DEBUG] Message type: <class 'litellm.types.utils.Message'>, content type: <class 'str'>
|
| 504 |
+
2026-04-20 19:18:22,078 | chronos.svo_parser | INFO | [DEBUG] Content repr: '[\n {\n "subject": "I",\n "verb": "posted",\n "object": "a marketing activity",\n "timestamp": "2026-04-20T19:18:17.239000+00:00",\n "datetime_start": "2026-04-20T19:18:17.239000+00:0
|
| 505 |
+
2026-04-20 19:18:22,079 | chronos.svo_parser | INFO | [DEBUG] Calling _extract_json with 867 chars
|
| 506 |
+
2026-04-20 19:18:22,079 | chronos.svo_parser | INFO | _extract_json called with 867 chars: [
|
| 507 |
+
{
|
| 508 |
+
"subject": "I",
|
| 509 |
+
"verb": "posted",
|
| 510 |
+
"object": "a marketing activ...
|
| 511 |
+
2026-04-20 19:18:22,079 | chronos.svo_parser | INFO | [DEBUG] _extract_json returned type: <class 'list'>
|
| 512 |
+
2026-04-20 19:18:22,079 | chronos.svo_parser | INFO | Fast Pipeline (groq/llama-3.1-8b-instant) extracted 3 SVO tuples from text (109 chars)
|
| 513 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 514 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 41.88it/s]
|
| 515 |
+
2026-04-20 19:18:22,689 | chronos.svo_parser | INFO | [DEBUG] Calling litellm.acompletion attempt 1...
|
| 516 |
+
19:18:22 - LiteLLM:INFO: utils.py:4007 -
|
| 517 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 518 |
+
2026-04-20 19:18:22,691 | LiteLLM | INFO |
|
| 519 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 520 |
+
|
| 521 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 522 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 523 |
+
|
| 524 |
+
19:18:22 - LiteLLM:INFO: utils.py:4007 -
|
| 525 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 526 |
+
2026-04-20 19:18:22,709 | LiteLLM | INFO |
|
| 527 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 528 |
+
|
| 529 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 530 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 531 |
+
|
| 532 |
+
19:18:22 - LiteLLM:INFO: utils.py:4007 -
|
| 533 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 534 |
+
2026-04-20 19:18:22,724 | LiteLLM | INFO |
|
| 535 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 536 |
+
|
| 537 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 538 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 539 |
+
|
| 540 |
+
19:18:22 - LiteLLM:INFO: utils.py:4007 -
|
| 541 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 542 |
+
2026-04-20 19:18:22,735 | LiteLLM | INFO |
|
| 543 |
+
LiteLLM completion() model= llama-3.1-8b-instant; provider = groq
|
| 544 |
+
|
| 545 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 546 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 547 |
+
|
| 548 |
+
19:18:22 - LiteLLM:ERROR: fallback_utils.py:68 - Fallback attempt failed for model groq/llama-3.1-8b-instant: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 549 |
+
Traceback (most recent call last):
|
| 550 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 176, in _make_common_async_call
|
| 551 |
+
response = await async_httpx_client.post(
|
| 552 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 553 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/logging_utils.py", line 297, in async_wrapper
|
| 554 |
+
result = await func(*args, **kwargs)
|
| 555 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 556 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 515, in post
|
| 557 |
+
raise e
|
| 558 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 468, in post
|
| 559 |
+
response = await self.client.send(req, stream=stream)
|
| 560 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 561 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
| 562 |
+
response = await self._send_handling_auth(
|
| 563 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 564 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
| 565 |
+
response = await self._send_handling_redirects(
|
| 566 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 567 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
| 568 |
+
response = await self._send_single_request(request)
|
| 569 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 570 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
| 571 |
+
response = await transport.handle_async_request(request)
|
| 572 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 573 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 307, in handle_async_request
|
| 574 |
+
response = await self._make_aiohttp_request(
|
| 575 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 576 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 285, in _make_aiohttp_request
|
| 577 |
+
response = await client_session.request(**request_kwargs).__aenter__()
|
| 578 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 579 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 1521, in __aenter__
|
| 580 |
+
self._resp: _RetType = await self._coro
|
| 581 |
+
^^^^^^^^^^^^^^^^
|
| 582 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 788, in _request
|
| 583 |
+
resp = await handler(req)
|
| 584 |
+
^^^^^^^^^^^^^^^^^^
|
| 585 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 764, in _connect_and_send_request
|
| 586 |
+
resp = await req.send(conn)
|
| 587 |
+
^^^^^^^^^^^^^^^^^^^^
|
| 588 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client_reqrep.py", line 1470, in send
|
| 589 |
+
await writer.write_headers(status_line, self.headers)
|
| 590 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/http_writer.py", line 217, in write_headers
|
| 591 |
+
buf = _serialize_headers(status_line, headers)
|
| 592 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 593 |
+
File "aiohttp/_http_writer.pyx", line 148, in aiohttp._http_writer._serialize_headers
|
| 594 |
+
File "aiohttp/_http_writer.pyx", line 115, in aiohttp._http_writer._write_str_raise_on_nlcr
|
| 595 |
+
ValueError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 596 |
+
|
| 597 |
+
During handling of the above exception, another exception occurred:
|
| 598 |
+
|
| 599 |
+
Traceback (most recent call last):
|
| 600 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 620, in acompletion
|
| 601 |
+
response = await init_response
|
| 602 |
+
^^^^^^^^^^^^^^^^^^^
|
| 603 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 308, in async_completion
|
| 604 |
+
response = await self._make_common_async_call(
|
| 605 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 606 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 203, in _make_common_async_call
|
| 607 |
+
raise self._handle_error(e=e, provider_config=provider_config)
|
| 608 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 609 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 4721, in _handle_error
|
| 610 |
+
raise provider_config.get_error_class(
|
| 611 |
+
litellm.llms.openai.common_utils.OpenAIError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 612 |
+
|
| 613 |
+
During handling of the above exception, another exception occurred:
|
| 614 |
+
|
| 615 |
+
Traceback (most recent call last):
|
| 616 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/fallback_utils.py", line 58, in async_completion_with_fallbacks
|
| 617 |
+
response = await litellm.acompletion(
|
| 618 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 619 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 2093, in wrapper_async
|
| 620 |
+
raise e
|
| 621 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 1892, in wrapper_async
|
| 622 |
+
result = await original_function(*args, **kwargs)
|
| 623 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 624 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 639, in acompletion
|
| 625 |
+
raise exception_type(
|
| 626 |
+
^^^^^^^^^^^^^^^
|
| 627 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 2456, in exception_type
|
| 628 |
+
raise e
|
| 629 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 588, in exception_type
|
| 630 |
+
raise InternalServerError(
|
| 631 |
+
litellm.exceptions.InternalServerError: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 632 |
+
2026-04-20 19:18:22,744 | LiteLLM | ERROR | Fallback attempt failed for model groq/llama-3.1-8b-instant: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 633 |
+
Traceback (most recent call last):
|
| 634 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 176, in _make_common_async_call
|
| 635 |
+
response = await async_httpx_client.post(
|
| 636 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 637 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/logging_utils.py", line 297, in async_wrapper
|
| 638 |
+
result = await func(*args, **kwargs)
|
| 639 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 640 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 515, in post
|
| 641 |
+
raise e
|
| 642 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/http_handler.py", line 468, in post
|
| 643 |
+
response = await self.client.send(req, stream=stream)
|
| 644 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 645 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1629, in send
|
| 646 |
+
response = await self._send_handling_auth(
|
| 647 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 648 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1657, in _send_handling_auth
|
| 649 |
+
response = await self._send_handling_redirects(
|
| 650 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 651 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1694, in _send_handling_redirects
|
| 652 |
+
response = await self._send_single_request(request)
|
| 653 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 654 |
+
File "/home/user/.local/lib/python3.11/site-packages/httpx/_client.py", line 1730, in _send_single_request
|
| 655 |
+
response = await transport.handle_async_request(request)
|
| 656 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 657 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 307, in handle_async_request
|
| 658 |
+
response = await self._make_aiohttp_request(
|
| 659 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 660 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/aiohttp_transport.py", line 285, in _make_aiohttp_request
|
| 661 |
+
response = await client_session.request(**request_kwargs).__aenter__()
|
| 662 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 663 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 1521, in __aenter__
|
| 664 |
+
self._resp: _RetType = await self._coro
|
| 665 |
+
^^^^^^^^^^^^^^^^
|
| 666 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 788, in _request
|
| 667 |
+
resp = await handler(req)
|
| 668 |
+
^^^^^^^^^^^^^^^^^^
|
| 669 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client.py", line 764, in _connect_and_send_request
|
| 670 |
+
resp = await req.send(conn)
|
| 671 |
+
^^^^^^^^^^^^^^^^^^^^
|
| 672 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/client_reqrep.py", line 1470, in send
|
| 673 |
+
await writer.write_headers(status_line, self.headers)
|
| 674 |
+
File "/home/user/.local/lib/python3.11/site-packages/aiohttp/http_writer.py", line 217, in write_headers
|
| 675 |
+
buf = _serialize_headers(status_line, headers)
|
| 676 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 677 |
+
File "aiohttp/_http_writer.pyx", line 148, in aiohttp._http_writer._serialize_headers
|
| 678 |
+
File "aiohttp/_http_writer.pyx", line 115, in aiohttp._http_writer._write_str_raise_on_nlcr
|
| 679 |
+
ValueError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 680 |
+
|
| 681 |
+
During handling of the above exception, another exception occurred:
|
| 682 |
+
|
| 683 |
+
Traceback (most recent call last):
|
| 684 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 620, in acompletion
|
| 685 |
+
response = await init_response
|
| 686 |
+
^^^^^^^^^^^^^^^^^^^
|
| 687 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 308, in async_completion
|
| 688 |
+
response = await self._make_common_async_call(
|
| 689 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 690 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 203, in _make_common_async_call
|
| 691 |
+
raise self._handle_error(e=e, provider_config=provider_config)
|
| 692 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 693 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/llms/custom_httpx/llm_http_handler.py", line 4721, in _handle_error
|
| 694 |
+
raise provider_config.get_error_class(
|
| 695 |
+
litellm.llms.openai.common_utils.OpenAIError: Newline, carriage return, or null byte detected in headers. Potential header injection attack.
|
| 696 |
+
|
| 697 |
+
During handling of the above exception, another exception occurred:
|
| 698 |
+
|
| 699 |
+
Traceback (most recent call last):
|
| 700 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/fallback_utils.py", line 58, in async_completion_with_fallbacks
|
| 701 |
+
response = await litellm.acompletion(
|
| 702 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 703 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 2093, in wrapper_async
|
| 704 |
+
raise e
|
| 705 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/utils.py", line 1892, in wrapper_async
|
| 706 |
+
result = await original_function(*args, **kwargs)
|
| 707 |
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
| 708 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/main.py", line 639, in acompletion
|
| 709 |
+
raise exception_type(
|
| 710 |
+
^^^^^^^^^^^^^^^
|
| 711 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 2456, in exception_type
|
| 712 |
+
raise e
|
| 713 |
+
File "/home/user/.local/lib/python3.11/site-packages/litellm/litellm_core_utils/exception_mapping_utils.py", line 588, in exception_type
|
| 714 |
+
raise InternalServerError(
|
| 715 |
+
litellm.exceptions.InternalServerError: litellm.InternalServerError: InternalServerError: GroqException - Newline, carriage return, or null byte detected in headers. Potential header injection attack. LiteLLM Retried: 3 times
|
| 716 |
+
19:18:22 - LiteLLM:INFO: utils.py:4007 -
|
| 717 |
+
LiteLLM completion() model= llama3.1-8b; provider = cerebras
|
| 718 |
+
2026-04-20 19:18:22,751 | LiteLLM | INFO |
|
| 719 |
+
LiteLLM completion() model= llama3.1-8b; provider = cerebras
|
| 720 |
+
2026-04-20 19:18:22,975 | chronos.svo_parser | INFO | [DEBUG] Response received. Type: <class 'litellm.types.utils.ModelResponse'>
|
| 721 |
+
2026-04-20 19:18:22,975 | chronos.svo_parser | INFO | [DEBUG] Message type: <class 'litellm.types.utils.Message'>, content type: <class 'str'>
|
| 722 |
+
2026-04-20 19:18:22,975 | chronos.svo_parser | INFO | [DEBUG] Content repr: '[\n {\n "subject": "I",\n "verb": "drafted",\n "object": "a marketing activity",\n "timestamp": "2026-04-20T19:18:19.593000+00:00",\n "datetime_start": null,\n "datetime_end": null
|
| 723 |
+
2026-04-20 19:18:22,975 | chronos.svo_parser | INFO | [DEBUG] Calling _extract_json with 503 chars
|
| 724 |
+
2026-04-20 19:18:22,975 | chronos.svo_parser | INFO | _extract_json called with 503 chars: [
|
| 725 |
+
{
|
| 726 |
+
"subject": "I",
|
| 727 |
+
"verb": "drafted",
|
| 728 |
+
"object": "a marketing acti...
|
| 729 |
+
2026-04-20 19:18:22,975 | chronos.svo_parser | INFO | [DEBUG] _extract_json returned type: <class 'list'>
|
| 730 |
+
2026-04-20 19:18:22,975 | chronos.svo_parser | INFO | Fast Pipeline (groq/llama-3.1-8b-instant) extracted 2 SVO tuples from text (110 chars)
|
| 731 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 732 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 52.62it/s]
|
| 733 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 734 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 40.98it/s]
|
| 735 |
+
2026-04-20 19:18:24,705 | chronos.vector_store | INFO | Batch added 2 event vectors to pgvector
|
| 736 |
+
2026-04-20 19:18:25,870 | chronos.vector_store | INFO | Batch added 2 event vectors to pgvector
|
| 737 |
+
2026-04-20 19:18:25,990 | chronos.routes.ingest | INFO | Ingested 2 events + 1 turns for source=marketing-tracker in 7889.7ms
|
| 738 |
+
INFO: 10.16.24.44:57970 - "POST /ingest HTTP/1.1" 200 OK
|
| 739 |
+
2026-04-20 19:18:26,431 | chronos.vector_store | INFO | Batch added 3 event vectors to pgvector
|
| 740 |
+
2026-04-20 19:18:27,159 | chronos.routes.ingest | INFO | Ingested 2 events + 1 turns for source=marketing-tracker in 4899.6ms
|
| 741 |
+
INFO: 10.16.47.45:63351 - "POST /ingest HTTP/1.1" 200 OK
|
| 742 |
+
2026-04-20 19:18:27,764 | chronos.routes.ingest | INFO | Ingested 3 events + 1 turns for source=marketing-tracker in 6476.5ms
|
| 743 |
+
INFO: 10.16.47.45:6065 - "POST /ingest HTTP/1.1" 200 OK
|
| 744 |
+
2026-04-20 19:18:52,927 | chronos.agent.graph | INFO | Chronos agent graph compiled successfully
|
| 745 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 746 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 110.55it/s]
|
| 747 |
+
2026-04-20 19:18:54,323 | chronos.llm_router | INFO | Heavy pipeline initialized: runnable=ChatLiteLLM(client=<module 'litellm' from '/home/user/.local/lib/python3.11/site-packages/litellm/__init__.py'>, model='cerebras/qwen-3-235b-a22b-instruct-2507', openai_api_key='', azure_api_key='', anthropic_api_key='', replicate_api_key='', cohere_api_key='', openrouter_api_key='', temperature=0.4, model_kwargs={}, max_retries=3) fallbacks=[ChatLiteLLM(client=<module 'litellm' from '/home/user/.local/lib/python3.11/site-packages/litellm/__init__.py'>, model='groq/llama-3.3-70b-versatile', openai_api_key='', azure_api_key='', anthropic_api_key='', replicate_api_key='', cohere_api_key='', openrouter_api_key='', temperature=0.4, model_kwargs={}, max_retries=3)]
|
| 748 |
+
19:18:54 - LiteLLM:INFO: utils.py:4007 -
|
| 749 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 750 |
+
2026-04-20 19:18:54,364 | LiteLLM | INFO |
|
| 751 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 752 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 753 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 117.68it/s]
|
| 754 |
+
2026-04-20 19:18:55,073 | chronos.agent.nodes | ERROR | Tool execution failed: query_chronos_memory — invalid input for query argument $3: '5' ('str' object cannot be interpreted as an integer)
|
| 755 |
+
19:18:55 - LiteLLM:INFO: utils.py:4007 -
|
| 756 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 757 |
+
2026-04-20 19:18:55,096 | LiteLLM | INFO |
|
| 758 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 759 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 760 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 125.68it/s]
|
| 761 |
+
2026-04-20 19:18:55,505 | chronos.agent.nodes | ERROR | Tool execution failed: query_chronos_memory — invalid input for query argument $3: '10' ('str' object cannot be interpreted as an integer)
|
| 762 |
+
19:18:55 - LiteLLM:INFO: utils.py:4007 -
|
| 763 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 764 |
+
2026-04-20 19:18:55,526 | LiteLLM | INFO |
|
| 765 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 766 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 767 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 138.64it/s]
|
| 768 |
+
19:18:56 - LiteLLM:INFO: utils.py:4007 -
|
| 769 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 770 |
+
2026-04-20 19:18:56,281 | LiteLLM | INFO |
|
| 771 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 772 |
+
INFO: 10.16.44.111:32873 - "POST /agent/run HTTP/1.1" 200 OK
|
| 773 |
+
INFO: 10.16.44.111:5424 - "GET /health HTTP/1.1" 200 OK
|
| 774 |
+
INFO: 10.16.44.111:5424 - "GET /health HTTP/1.1" 200 OK
|
| 775 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 776 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 88.53it/s]
|
| 777 |
+
19:19:30 - LiteLLM:INFO: utils.py:4007 -
|
| 778 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 779 |
+
2026-04-20 19:19:30,083 | LiteLLM | INFO |
|
| 780 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 781 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 782 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 108.27it/s]
|
| 783 |
+
19:19:30 - LiteLLM:INFO: utils.py:4007 -
|
| 784 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 785 |
+
2026-04-20 19:19:30,731 | LiteLLM | INFO |
|
| 786 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 787 |
+
INFO: 10.16.47.45:42459 - "POST /agent/run HTTP/1.1" 200 OK
|
| 788 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 789 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 121.03it/s]
|
| 790 |
+
19:19:51 - LiteLLM:INFO: utils.py:4007 -
|
| 791 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 792 |
+
2026-04-20 19:19:51,938 | LiteLLM | INFO |
|
| 793 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 794 |
+
INFO: 10.16.47.45:29699 - "POST /agent/run HTTP/1.1" 200 OK
|
| 795 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 796 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 24.43it/s]
|
| 797 |
+
19:20:54 - LiteLLM:INFO: utils.py:4007 -
|
| 798 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 799 |
+
2026-04-20 19:20:54,363 | LiteLLM | INFO |
|
| 800 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 801 |
+
INFO: 10.16.24.44:28058 - "POST /agent/run HTTP/1.1" 200 OK
|
| 802 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 803 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 6.22it/s]
|
| 804 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 6.21it/s]
|
| 805 |
+
19:21:40 - LiteLLM:INFO: utils.py:4007 -
|
| 806 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 807 |
+
2026-04-20 19:21:40,264 | LiteLLM | INFO |
|
| 808 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 809 |
+
Batches: 0%| | 0/1 [00:00<?, ?it/s]
|
| 810 |
+
Batches: 100%|██████████| 1/1 [00:00<00:00, 61.89it/s]
|
| 811 |
+
19:21:40 - LiteLLM:INFO: utils.py:4007 -
|
| 812 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 813 |
+
2026-04-20 19:21:40,979 | LiteLLM | INFO |
|
| 814 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 815 |
+
|
| 816 |
+
Give Feedback / Get Help: https://github.com/BerriAI/litellm/issues/new
|
| 817 |
+
LiteLLM.Info: If you need to debug this error, use `litellm._turn_on_debug()'.
|
| 818 |
+
|
| 819 |
+
2026-04-20 19:21:41,067 | langchain_core.language_models.llms | WARNING | Retrying langchain_litellm.chat_models.litellm.ChatLiteLLM.acompletion_with_retry.<locals>._completion_with_retry in 4 seconds as it raised RateLimitError: litellm.RateLimitError: RateLimitError: CerebrasException - We're experiencing high traffic right now! Please try again soon..
|
| 820 |
+
19:21:45 - LiteLLM:INFO: utils.py:4007 -
|
| 821 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 822 |
+
2026-04-20 19:21:45,069 | LiteLLM | INFO |
|
| 823 |
+
LiteLLM completion() model= qwen-3-235b-a22b-instruct-2507; provider = cerebras
|
| 824 |
+
2026-04-20 19:21:45,113 | openai._base_client | INFO | Retrying request to /chat/completions in 60.000000 seconds
|
| 825 |
+
|
docs/global_insights_plan.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Global Insights ("The God System") Implementation Plan
|
| 2 |
+
|
| 3 |
+
The objective is to implement the concept of the "God System" for Chronos OS. By allowing "holy magic users" (agents, SaaS tools, and users) to feed their data into a central intelligence, Chronos becomes geometrically smarter.
|
| 4 |
+
|
| 5 |
+
This plan outlines the **simplest possible version** of this system, leveraging existing infrastructure without overcomplicating the architecture.
|
| 6 |
+
|
| 7 |
+
## 1. The Core Concept
|
| 8 |
+
|
| 9 |
+
Currently, all data ingested into Chronos is isolated by `source_id` (tenant isolation). To create the "God System", we simply need a mechanism to securely pool this knowledge, allowing a central intelligence to query across all users while maintaining privacy.
|
| 10 |
+
|
| 11 |
+
* **Users/Agents:** Opt-in to share anonymized events (e.g., "completed marketing task X") to the global pool.
|
| 12 |
+
* **Chronos (The God):** Queries the global pool to identify macro-patterns and provides highly intelligent answers to individual users based on collective experience.
|
| 13 |
+
* **The Engine:** A massive, high-parameter LLM (e.g., `deepseek-v3.2-cloud` or `nemotron-3-super-120b-cloud`) acts as the "God" model, capable of synthesizing thousands of disparate events into cohesive insights.
|
| 14 |
+
|
| 15 |
+
## 2. Simplest Possible Implementation (MVP)
|
| 16 |
+
|
| 17 |
+
We can achieve this with minimal code changes by adding a "Global" visibility flag to the existing database schema.
|
| 18 |
+
|
| 19 |
+
### Step 1: Opt-In Knowledge Pooling (The "Donation")
|
| 20 |
+
* **Database Update:** Add a simple boolean flag `is_global_insight` (default `False`) to the `events` table and the pgvector metadata.
|
| 21 |
+
* **Ingest API Change:** Update `POST /ingest` to accept `allow_global: bool`. If explicit consent is given, the event text is anonymized (stripping PII via a fast LLM pass or simple regex) and stored with `is_global_insight = True`.
|
| 22 |
+
|
| 23 |
+
### Step 2: Global Pattern Retrieval ("God's Memory")
|
| 24 |
+
* **Vector Search Update:** Modify the `semantic_search` function. Currently, it strictly filters by `source_id`. We will add a parameter `scope`. If `scope="global"`, it searches all vectors where `is_global_insight = True`, ignoring `source_id`.
|
| 25 |
+
|
| 26 |
+
### Step 3: Synthesis via Heavy Cloud Models ("The God Mind")
|
| 27 |
+
* Smaller 8B models (like Llama-3.1-8b) are great for fast ingestion, but they lack the context window and reasoning power to synthesize thousands of global events.
|
| 28 |
+
* **New Route (`GET /insights/global`):** This route triggers an Ollama Cloud hosted model (e.g., `ollama run deepseek-v3.2:671b-cloud` or `qwen3.6-coder:480b-cloud`) via the `litellm` router.
|
| 29 |
+
* This model takes the top 50 global events related to a user's prompt (e.g., "What are the most common successful marketing strategies?") and synthesizes a high-level master strategy.
|
| 30 |
+
|
| 31 |
+
### Step 4: Agent Tool Integration
|
| 32 |
+
* **New Agent Tool:** Create a new tool in `agent/tools.py` called `query_global_insights(query: str)`.
|
| 33 |
+
* Any agent (even a user's personal assistant) can call this tool to tap into the "God" memory when it gets stuck or needs global context.
|
| 34 |
+
|
| 35 |
+
## User Review Required
|
| 36 |
+
|
| 37 |
+
> [!IMPORTANT]
|
| 38 |
+
> **Privacy vs. Utility**: For the simplest MVP, are we comfortable relying on the user to ensure no sensitive PII is passed when `allow_global=True`, or do we want to implement an automatic zero-cost anonymization step (e.g., replacing names with [USER_1], companies with [CORP_A]) before storing global events?
|
| 39 |
+
|
| 40 |
+
> [!NOTE]
|
| 41 |
+
> **Model Selection**: Based on `modeldata.md`, we should designate a specific "heavy" model for global insights. `DeepSeek-V3.2-Cloud (671b)` is highly recommended for general synthesis, while `Nemotron-3-Super (120b)` is optimized for agentic coordination. Do you have a preference for the "God" model identity?
|
| 42 |
+
|
| 43 |
+
## 3. Next Steps
|
| 44 |
+
|
| 45 |
+
Once approved, we can implement this in roughly 3 phases:
|
| 46 |
+
1. **DB & API Migration:** Add the `is_global` flags and update the `/ingest` route.
|
| 47 |
+
2. **Vector Store Logic:** Update the `semantic_search` to support cross-tenant global querying.
|
| 48 |
+
3. **Agent Tooling:** Expose the `query_global_insights` tool to the LangGraph executor.
|
docs/idea.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
The ecosystem you're describing — one that pulls in every AI startup, SaaS product, app, and software idea from those startup subreddits (agentic or not) so they all run inside it, use it as their backbone, and you monetize via usage/revenue share — doesn't fully exist yet in 2026. But the search across Reddit (r/startups, r/SaaS, r/Entrepreneur, r/AI_Agents, etc.), web trends, agentic AI lists, and gaps shows a clear missing piece you can build around Chronos.Quick Context from Your Searches and ChronosYou saw the flood of AI/SaaS ideas but low traction for Chronos itself (<100 users). Reddit is full of "AI agents were cute in 2025; 2026 is about real infrastructure" posts, plus complaints like:Easy-to-copy AI tools → no moat.
|
| 2 |
+
Agents forget everything between runs (memory gap is the #1 unsolved pain).
|
| 3 |
+
Legacy/non-agentic SaaS can't easily "go agentic" without massive rework.
|
| 4 |
+
Orchestration, governance, trust, and long-term reasoning are missing → most pilots die, 99% of hype AI startups will fail by end of 2026.
|
| 5 |
+
|
| 6 |
+
Your Chronos (Letters to the Future) is perfectly positioned: it's about capturing quiet moments into letters to your future self, temporal self-comparison, and preserving the "unsaid." The matching 2026 research paper on Chronos (LLM long-term memory via structured event calendars) decomposes conversations into timestamped SVO event tuples + dual calendars (event + raw turns) for temporal-aware retrieval and multi-hop reasoning. It hits 95.6% on long-memory benchmarks — exactly the "temporal blindness" fix agents and apps desperately need.
|
| 7 |
+
|
| 8 |
+
arxiv.org +1
|
| 9 |
+
|
| 10 |
+
This isn't just personal journaling anymore. It's the core differentiator for the ecosystem.The One Unifying Thing That Can Pull in the Entire Public (Startups + Users)A Temporal AI Agent Ecosystem powered by Chronos — call it Chronos OS or Chronos Agent Hub. Think of it as the "AWS + App Store + Long-Term Memory Layer" but only for agents and SaaS. Every AI startup, SaaS builder, or software product plugs in once (via simple API or no-code connectors). Their product "lives inside" the ecosystem:Agentic products (the 120+ on the 2026 Agentic List) get instant long-term memory, orchestration, and cross-product collaboration.
|
| 11 |
+
Non-agentic SaaS/apps (the boring-but-profitable ones Reddit keeps pushing: compliance tools, finance stacks, procurement, ops orchestrators, etc.) expose APIs and instantly become agent-actionable — no rebuild needed. Agents can now negotiate contracts, run finance workflows, or orchestrate hybrid teams using their data/tools.
|
| 12 |
+
|
| 13 |
+
Startups use it to build/run/monetize faster. You get paid every time anything runs (memory storage, orchestration calls, compute, marketplace transactions).Why This Is the Only Thing That Works (From Deep Reddit + Internet Search)Reddit threads (sampled across thousands of recent ideas in r/startups/r/SaaS) repeatedly scream the same gaps:Memory & continuity → "Agents forget everything" is the moat for 2026.
|
| 14 |
+
Orchestration & coordination → AI-as-infrastructure, not just chatbots.
|
| 15 |
+
Governance/trust/liability → Needed for real adoption.
|
| 16 |
+
Integration with non-agentic/legacy stuff → Enterprises and startups are stuck bolting agents onto old stacks.
|
| 17 |
+
Marketplace/network effects → Discover, compose, and transact agents like an app store.
|
| 18 |
+
|
| 19 |
+
No single platform owns this yet. LangChain/Hub, TrueFoundry, xpander, etc., handle pieces — but none have structured temporal event calendars as the core (your Chronos edge). Generic agent marketplaces are emerging, but they're fragmented and lack the memory layer that makes agents actually compound knowledge over time.
|
| 20 |
+
|
| 21 |
+
reddit.com +1
|
| 22 |
+
|
| 23 |
+
Your ecosystem fixes the exact pains in those 5 B2B ideas people are hyping (global compliance engine, autonomous SME finance, AI procurement negotiator, reg-aware dev layer, hybrid ops orchestrator). They all become composable modules inside Chronos OS — agents remember past negotiations, predict cash gaps over months, simulate regulatory changes temporally, etc.Network effect is brutal: the more startups/products plug in → the smarter and more valuable every agent becomes → more startups join → you own the flywheel.How They Use It (and You Get Paid)Plug & Play Onboarding — Any startup connects their SaaS/product in minutes (API keys + optional event schema). Non-agentic tools auto-gain agent wrappers.
|
| 24 |
+
Build/Run Inside the Ecosystem — Use Chronos memory + orchestration to create agents that act across everyone's tools. Temporal reasoning is native (e.g., "What regulatory changes happened in Q3 last year that affect this contract?").
|
| 25 |
+
Marketplace — Publish agents/tools/capabilities. Others discover and compose them (e.g., your procurement agent calls someone else's compliance module). Revenue share on transactions.
|
| 26 |
+
Monetization (Your Money):Usage-based: Memory storage, orchestration steps, compute (the infra layer everyone needs but hates building).
|
| 27 |
+
Marketplace cut (10-20%).
|
| 28 |
+
Premium governance/audit/simulations (enterprise-ready).
|
| 29 |
+
White-label for big SaaS who want to embed Chronos memory.
|
| 30 |
+
|
| 31 |
+
Startups pay because it solves their #1 problem: building isolated agents that die in production. They get distribution (ecosystem discovery) and a moat (temporal memory no one else has).Why This Covers Both Agentic and Non-Agentic PerfectlyAgentic → Full power: long-term memory + multi-agent orchestration across the entire ecosystem.
|
| 32 |
+
Non-agentic → They don't have to become agents. They just expose APIs and instantly participate (agents treat them as tools). Reddit's "boring SaaS" crowd loves this — they keep their simple product but gain AI superpowers and new users via the hub.
|
| 33 |
+
|
| 34 |
+
This is the new system that emerges from scanning all those Reddit ideas: not another generic AI tool, but the infrastructure layer that turns the flood of startups into one interconnected, temporal-aware network.You already have the seed (Chronos personal + the research framework). Expand it to B2B developer/platform mode. The subreddits are begging for exactly this — they just don't know the name yet.If you build this, you don't chase 100 users in subreddits anymore. The entire ecosystem chases you. Want to prototype the API layer or name it? I can help refine the exact tech stack or go-to-market from here. Time is the key — this is your letter to the future of Chronos.
|
| 35 |
+
|
| 36 |
+
Yes — the full Temporal AI Agent Ecosystem (Chronos OS) doesn't exist yet. The March 2026 Chronos research paper (arXiv 2603.16862) gives us the perfect memory foundation (SVO event tuples + dual event/turn calendars for temporal reasoning), but it's still just academic code snippets and discussions. No one has turned it into a plug-and-play hub where any AI startup or SaaS product (agentic or non-agentic) connects once, gets instant long-term temporal memory + orchestration, and runs inside a shared marketplace.
|
| 37 |
+
|
| 38 |
+
arxiv.org +1
|
| 39 |
+
|
| 40 |
+
Closest pieces exist in isolation (Zep/Graphiti for temporal graphs, Temporal.io for durable workflows, Workato/Camunda for SaaS orchestration, Mem0/Letta for general memory), but nothing unifies them under structured event calendars as the core layer that makes every connected product smarter over time.
|
| 41 |
+
|
| 42 |
+
vectorize.io +2
|
| 43 |
+
|
| 44 |
+
Perfect. Let's build the simplest possible MVP right now — call it Chronos Hub v0 ("Letters to the Future" for agents). Goal: Get it live in <2 weeks with <500 lines of core code so the first 10 Reddit startups can plug in their tools, run agents with real temporal memory, and you start charging usage.The Simplest MVP (Build This First — No Over-Engineering)One-sentence vision: A tiny API + dashboard where any SaaS or agent "plugs in" via one webhook/endpoint, events get stored in Chronos calendars automatically, and agents query the shared temporal memory to act across everything.Core features (MVP scope — nothing more):Chronos Memory Core (the secret sauce) Ingest raw events/conversations → auto-extract SVO tuples + timestamps (use simple LLM call or regex fallback for v0).
|
| 45 |
+
Store in two lightweight tables: Event Calendar (structured: subject, verb, object, datetime_range, entity_aliases).
|
| 46 |
+
Turn Calendar (raw conversation for full context).
|
| 47 |
+
|
| 48 |
+
Query: Dynamic retrieval (e.g., "What contracts changed in Q1?") with multi-hop reasoning.
|
| 49 |
+
Backend: Python + FastAPI + SQLite (or Postgres) + ChromaDB (for vector fallback). Zero cloud cost at start.
|
| 50 |
+
|
| 51 |
+
Universal Plug-in API (covers both agentic + non-agentic) One endpoint: POST /ingest — any startup sends JSON events or tool schemas (e.g., "I am a finance SaaS, here are my API endpoints").
|
| 52 |
+
Non-agentic SaaS instantly becomes agent-actionable (agents treat it as a tool).
|
| 53 |
+
Agentic products get memory + cross-product orchestration for free.
|
| 54 |
+
|
| 55 |
+
Agent Runner Simple LangGraph wrapper: Agents use Chronos memory out-of-the-box.
|
| 56 |
+
Example: Your procurement agent now remembers past negotiations across 5 connected SaaS tools.
|
| 57 |
+
|
| 58 |
+
Tiny Dashboard (Streamlit or Gradio — 1 file) Connect your product (paste API key).
|
| 59 |
+
Test agent prompt.
|
| 60 |
+
See timeline view of events (your "Letters to the Future" UI extended to B2B).
|
| 61 |
+
|
| 62 |
+
Monetization from Day 1 Free tier: 10k events/month.
|
| 63 |
+
Paid: $0.01 per 1k events stored + $0.05 per orchestration call (Stripe checkout).
|
| 64 |
+
Marketplace v0: Just shareable agent JSON files (next version adds discoverability).
|
| 65 |
+
|
| 66 |
+
This MVP solves the exact Reddit pain you saw: "Agents forget" + "How do I make my boring SaaS AI-native without rebuilding?"Exact Tech Stack (Cheapest + Fastest to Ship)Layer
|
| 67 |
+
Tool (v0)
|
| 68 |
+
Why Simplest
|
| 69 |
+
Backend
|
| 70 |
+
FastAPI + Python
|
| 71 |
+
1 file API
|
| 72 |
+
Memory
|
| 73 |
+
SQLite + ChromaDB
|
| 74 |
+
No server needed
|
| 75 |
+
Parsing
|
| 76 |
+
LiteLLM (or openai)
|
| 77 |
+
One call to extract SVO
|
| 78 |
+
Agent Framework
|
| 79 |
+
LangGraph
|
| 80 |
+
Built-in state + checkpoints
|
| 81 |
+
Dashboard
|
| 82 |
+
Streamlit
|
| 83 |
+
50 lines
|
| 84 |
+
Auth/Hosting
|
| 85 |
+
Railway or Fly.io (free tier)
|
| 86 |
+
Deploy in 2 clicks
|
| 87 |
+
Payments
|
| 88 |
+
Stripe
|
| 89 |
+
5 lines
|
| 90 |
+
|
| 91 |
+
Total estimated build time for solo founder: 3–7 days if you code 2–3 hours/day.Step-by-Step Build Plan (We Do This Together)Today/Tomorrow: I generate the full Chronos Memory Core code (FastAPI + SVO parser + dual calendars). You copy-paste and run locally.
|
| 92 |
+
Day 2: Add /ingest endpoint + LangGraph agent example.
|
| 93 |
+
Day 3: Streamlit dashboard + "Connect SaaS" form.
|
| 94 |
+
Day 4: Deploy + add Stripe.
|
| 95 |
+
Day 5+: Post in 5 startup subreddits: "Free temporal memory for your AI/SaaS — plug in once".
|
| 96 |
+
|
| 97 |
+
This is the absolute minimal viable version that still captures the entire flywheel: startups plug in → agents get smarter → more startups join → you get paid on usage.Your existing Chronos personal app ("Letters to the Future") becomes the consumer on-ramp — users' personal timelines feed the same memory layer that powers B2B agents. Instant network effect.Ready? Reply with "Generate the core code" and I'll drop the complete starter repo structure + working Python files you can run immediately. Or tell me any tweak (e.g., use Node instead, focus only on personal first, etc.).This is your letter to the future of Chronos — let's ship v0 before the Reddit crowd even knows they need it.
|
| 98 |
+
|
| 99 |
+
Yes — you're exactly right. Even the absolute frontier models in April 2026 — Gemini 3, Claude 4.x/Opus, and Grok 4.x — do not have native, structured temporal long-term memory like the Chronos system (SVO event tuples + dual event/turn calendars for multi-hop temporal reasoning).What the big LLMs actually have (and what they’re still missing)Model
|
| 100 |
+
What they ship today (2026)
|
| 101 |
+
What they still lack (Chronos edge)
|
| 102 |
+
Gemini
|
| 103 |
+
1M+ token context window + “Memory” feature (auto-summarizes chats every ~24h, imports history/preferences)
|
| 104 |
+
No structured SVO events or dual calendars. Memory is just summaries + vector retrieval. Agents still need external DBs (e.g. Milvus) for anything beyond one session.
|
| 105 |
+
Claude
|
| 106 |
+
Free “Memory” profile (auto-summarizes key facts every 24h) + 3-layer system for coding agents (memory.md + grep + background daemon)
|
| 107 |
+
Summary-based only. No native temporal event decomposition or calendar-based multi-hop reasoning across months/years.
|
| 108 |
+
Grok
|
| 109 |
+
2M token context in Grok 4.1 Fast + server-side conversation history (30-day stateful threads)
|
| 110 |
+
Stateless core + no built-in structured temporal memory. Persistence is just chat logs, not event calendars.
|
| 111 |
+
|
| 112 |
+
All three rely on the same pattern:Massive context = short-term/working memory (great for one long chat).
|
| 113 |
+
Auto-summaries or vector stores = “long-term” memory (but it’s lossy, not structured, and not temporally aware).
|
| 114 |
+
|
| 115 |
+
They cannot natively:Decompose every interaction into timestamped SVO events.
|
| 116 |
+
Maintain a true event calendar + raw turn calendar.
|
| 117 |
+
Do precise multi-hop temporal queries (“What changed in Q3 last year that affects this contract we negotiated in January?”).
|
| 118 |
+
|
| 119 |
+
That’s exactly why the March 2026 Chronos paper (arXiv 2603.16862) is such a big deal — it’s the first framework that gives LLMs this missing temporal-aware memory layer. The big labs know this is the next frontier (every 2026 report calls “agentic memory” the #1 unsolved problem), but they haven’t shipped it natively yet.Why this is perfect for usThis gap is the entire reason Chronos Hub v0 will be unstoppable.
|
| 120 |
+
We’re not competing with Gemini/Claude/Grok — we’re plugging into them. Any startup or SaaS connects once, feeds events into the Chronos calendar, and suddenly their agents (powered by any of the big models) get real long-term temporal memory + cross-product orchestration.The big LLMs stay the “brain,” but Chronos becomes the persistent spine that makes agents actually useful over time.So yes — the simplest MVP we sketched yesterday is still the fastest path to owning this. The big models literally cannot do what Chronos does without an external system like the one we’re building.Ready to ship? Just say “Generate the core code” and I’ll drop the complete FastAPI + Chronos memory core (SVO parser + dual SQLite calendars + LangGraph agent runner) + Streamlit dashboard you can run locally in minutes. This is the letter to the future that the big labs haven’t written yet. Let’s write it.v
|
docs/implementation_plan.md
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Chronos OS — Temporal AI Agent Ecosystem
|
| 2 |
+
|
| 3 |
+
Transform Chronos from a personal time-capsule app into **Chronos OS**: the infrastructure layer that gives every AI agent and SaaS product structured temporal long-term memory.
|
| 4 |
+
|
| 5 |
+
## Background
|
| 6 |
+
|
| 7 |
+
| | Current (MVP) | Target (Chronos OS) |
|
| 8 |
+
|---|---|---|
|
| 9 |
+
| **Product** | Personal journaling SPA (React/TS/Vite) | Temporal AI Agent Ecosystem |
|
| 10 |
+
| **Users** | Individuals writing letters to their future self | AI startups, SaaS builders, agent developers |
|
| 11 |
+
| **Stack** | React 19 + localStorage + crypto-js | Python (FastAPI + LangGraph + SQLite + ChromaDB) + **Gemini 2.5 Flash (free)** |
|
| 12 |
+
| **Memory** | Base64 encrypted blobs in localStorage | Structured SVO event tuples + dual calendars |
|
| 13 |
+
| **Monetization** | Premium waitlist / Stripe demo | Usage-based (events + orchestration calls + marketplace cut) |
|
| 14 |
+
|
| 15 |
+
The existing React app stays as-is on the Play Store / web — it becomes the **consumer on-ramp**. Chronos OS is a **new, separate Python project** built alongside it.
|
| 16 |
+
|
| 17 |
+
---
|
| 18 |
+
|
| 19 |
+
## Decisions (Finalized)
|
| 20 |
+
|
| 21 |
+
| Decision | Choice | Rationale |
|
| 22 |
+
|---|---|---|
|
| 23 |
+
| **LLM Provider** | **Google Gemini 2.5 Flash** (free via Google AI Studio) | No cost, 1M token context, generous rate limits, no credit card needed |
|
| 24 |
+
| **Deployment** | **Railway** (free tier → ~$5/mo) | One-click deploy, easy scaling |
|
| 25 |
+
| **Pricing Model** | Premium 3-tier (see below) | Positioned against Mem0 ($19–$249), Zep (credits), LangSmith ($39/seat) |
|
| 26 |
+
|
| 27 |
+
> [!WARNING]
|
| 28 |
+
> **This is a brand-new Python project** — it does NOT modify your existing React/TS Chronos Vault app. The React app remains untouched.
|
| 29 |
+
|
| 30 |
+
---
|
| 31 |
+
|
| 32 |
+
## Proposed Changes
|
| 33 |
+
|
| 34 |
+
The entire project lives under `c:\Users\reman\OneDrive\Desktop\Chronos OS\chronos-hub\`. Here is the complete file tree we will build:
|
| 35 |
+
|
| 36 |
+
```
|
| 37 |
+
chronos-hub/
|
| 38 |
+
├── .env.example # Environment variable template
|
| 39 |
+
├── requirements.txt # Python dependencies
|
| 40 |
+
├── README.md # Chronos OS documentation
|
| 41 |
+
│
|
| 42 |
+
├── chronos_core/ # 🧠 Memory Core (the secret sauce)
|
| 43 |
+
│ ├── __init__.py
|
| 44 |
+
│ ├── models.py # Pydantic models for events, SVO tuples, calendars
|
| 45 |
+
│ ├── svo_parser.py # LLM-powered SVO extraction from raw text
|
| 46 |
+
│ ├── memory_store.py # SQLite event calendar + turn calendar
|
| 47 |
+
│ └── vector_store.py # ChromaDB semantic search layer
|
| 48 |
+
│
|
| 49 |
+
├── api/ # 🌐 FastAPI Gateway
|
| 50 |
+
│ ├── __init__.py
|
| 51 |
+
│ ├── main.py # FastAPI app entry point + CORS + middleware
|
| 52 |
+
│ ├── routes/
|
| 53 |
+
│ │ ├── __init__.py
|
| 54 |
+
│ │ ├── ingest.py # POST /ingest — universal event ingestion
|
| 55 |
+
│ │ ├── query.py # POST /query — temporal + semantic retrieval
|
| 56 |
+
│ │ ├── connectors.py # POST /connect — register SaaS/agent tools
|
| 57 |
+
│ │ ├── agent.py # POST /agent/run — execute agent with memory
|
| 58 |
+
│ │ └── billing.py # Stripe checkout + usage tracking
|
| 59 |
+
│ ├── auth.py # API key authentication middleware
|
| 60 |
+
│ └── deps.py # Dependency injection (DB sessions, stores)
|
| 61 |
+
│
|
| 62 |
+
├── agent/ # 🤖 LangGraph Agent Runner
|
| 63 |
+
│ ├── __init__.py
|
| 64 |
+
│ ├── graph.py # LangGraph state graph definition
|
| 65 |
+
│ ├── nodes.py # Agent nodes (call_model, use_tools, retrieve_memory)
|
| 66 |
+
│ └── tools.py # Built-in tools (query_memory, search_connectors)
|
| 67 |
+
│
|
| 68 |
+
├── dashboard/ # 📊 Streamlit Dashboard
|
| 69 |
+
│ └── app.py # Single-file Streamlit UI
|
| 70 |
+
│
|
| 71 |
+
└── tests/ # ✅ Basic tests
|
| 72 |
+
├── test_svo_parser.py
|
| 73 |
+
├── test_memory_store.py
|
| 74 |
+
└── test_api.py
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
---
|
| 78 |
+
|
| 79 |
+
### Component 1: Chronos Memory Core (`chronos_core/`)
|
| 80 |
+
|
| 81 |
+
> [!NOTE]
|
| 82 |
+
> This is the core differentiator — the structured temporal memory layer based on the Chronos research paper's SVO event decomposition + dual calendar architecture.
|
| 83 |
+
|
| 84 |
+
#### [NEW] [models.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/chronos_core/models.py)
|
| 85 |
+
- Pydantic models: `SVOTuple` (subject, verb, object, timestamp, datetime_range, entity_aliases, confidence)
|
| 86 |
+
- `EventRecord` — structured event for the Event Calendar (SQLite)
|
| 87 |
+
- `TurnRecord` — raw conversation turn for the Turn Calendar (SQLite)
|
| 88 |
+
- `IngestPayload` — incoming JSON from any SaaS/agent
|
| 89 |
+
- `QueryRequest` — temporal + semantic query spec
|
| 90 |
+
- `QueryResult` — ranked results with provenance
|
| 91 |
+
|
| 92 |
+
#### [NEW] [svo_parser.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/chronos_core/svo_parser.py)
|
| 93 |
+
- Uses **Google Gemini 2.5 Flash** (free tier via `google-genai` SDK) for SVO extraction
|
| 94 |
+
- Fallback: LiteLLM gateway for swapping to other providers later
|
| 95 |
+
- Prompt template: "Extract all Subject-Verb-Object events with timestamps from this text. Return JSON array."
|
| 96 |
+
- Regex fallback for simple patterns when LLM quota is exhausted
|
| 97 |
+
- Batch processing support for bulk ingestion
|
| 98 |
+
|
| 99 |
+
#### [NEW] [memory_store.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/chronos_core/memory_store.py)
|
| 100 |
+
- **Event Calendar** — SQLite table: `events(id, source_id, subject, verb, object, timestamp, datetime_start, datetime_end, entity_aliases, confidence, metadata_json, created_at)`
|
| 101 |
+
- **Turn Calendar** — SQLite table: `turns(id, source_id, role, content, timestamp, event_ids, created_at)`
|
| 102 |
+
- Methods: `insert_event()`, `insert_turn()`, `query_temporal()` (SQL WHERE on timestamp ranges), `query_by_entity()`, `multi_hop_query()` (join events across time)
|
| 103 |
+
- Connection pooling with `aiosqlite` for async FastAPI
|
| 104 |
+
|
| 105 |
+
#### [NEW] [vector_store.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/chronos_core/vector_store.py)
|
| 106 |
+
- ChromaDB collection `chronos_events`
|
| 107 |
+
- On each event insert: embed the raw text + store with SQLite event_id as metadata
|
| 108 |
+
- `semantic_search(query, n_results)` — returns event IDs ranked by relevance
|
| 109 |
+
- Hybrid retrieval: vector search → join with SQLite for full context + temporal filtering
|
| 110 |
+
|
| 111 |
+
---
|
| 112 |
+
|
| 113 |
+
### Component 2: FastAPI Gateway (`api/`)
|
| 114 |
+
|
| 115 |
+
#### [NEW] [main.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/api/main.py)
|
| 116 |
+
- FastAPI app with CORS middleware (allow all origins for dev)
|
| 117 |
+
- Lifespan handler to initialize SQLite + ChromaDB on startup
|
| 118 |
+
- Include all route routers
|
| 119 |
+
- Health check endpoint at `GET /`
|
| 120 |
+
|
| 121 |
+
#### [NEW] [routes/ingest.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/api/routes/ingest.py)
|
| 122 |
+
- `POST /ingest` — the universal endpoint
|
| 123 |
+
- Accepts JSON: `{ "source_id": "stripe-saas-123", "events": [{"text": "...", "timestamp": "..."}] }` or raw conversation turns
|
| 124 |
+
- Pipeline: validate → SVO parse → insert into Event Calendar + Turn Calendar + ChromaDB
|
| 125 |
+
- Returns: event IDs + extracted SVO tuples
|
| 126 |
+
- Usage metering: increment event count for billing
|
| 127 |
+
|
| 128 |
+
#### [NEW] [routes/query.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/api/routes/query.py)
|
| 129 |
+
- `POST /query` — temporal + semantic retrieval
|
| 130 |
+
- Accepts: `{ "query": "What contracts changed in Q1?", "time_range": {"start": "...", "end": "..."}, "source_ids": [...] }`
|
| 131 |
+
- Hybrid retrieval: ChromaDB semantic → SQLite temporal filter → multi-hop reasoning
|
| 132 |
+
- Returns ranked events with provenance chain
|
| 133 |
+
|
| 134 |
+
#### [NEW] [routes/connectors.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/api/routes/connectors.py)
|
| 135 |
+
- `POST /connect` — register a SaaS product's API schema
|
| 136 |
+
- Stores tool definitions so agents can discover and call connected products
|
| 137 |
+
- `GET /connectors` — list all connected tools
|
| 138 |
+
- Non-agentic SaaS instantly becomes agent-actionable
|
| 139 |
+
|
| 140 |
+
#### [NEW] [routes/agent.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/api/routes/agent.py)
|
| 141 |
+
- `POST /agent/run` — execute an agent prompt with full Chronos memory
|
| 142 |
+
- Accepts: `{ "prompt": "...", "thread_id": "...", "tools": [...] }`
|
| 143 |
+
- Invokes LangGraph agent runner with memory context
|
| 144 |
+
- Streams response via SSE or returns final result
|
| 145 |
+
- Usage metering: increment orchestration call count
|
| 146 |
+
|
| 147 |
+
#### [NEW] [routes/billing.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/api/routes/billing.py)
|
| 148 |
+
- `POST /billing/checkout` — create Stripe checkout session
|
| 149 |
+
- `GET /billing/usage` — current usage stats (events, orchestration calls)
|
| 150 |
+
- Premium 3-tier pricing (see Pricing section below)
|
| 151 |
+
|
| 152 |
+
#### [NEW] [auth.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/api/auth.py)
|
| 153 |
+
- API key middleware: validate `X-API-Key` header
|
| 154 |
+
- SQLite `api_keys` table: `(key_hash, source_id, tier, events_used, orchestration_used, created_at)`
|
| 155 |
+
- Rate limiting per tier
|
| 156 |
+
|
| 157 |
+
#### [NEW] [deps.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/api/deps.py)
|
| 158 |
+
- Dependency injection for FastAPI routes
|
| 159 |
+
- Provides: `get_memory_store()`, `get_vector_store()`, `get_svo_parser()`
|
| 160 |
+
|
| 161 |
+
---
|
| 162 |
+
|
| 163 |
+
### Component 3: LangGraph Agent Runner (`agent/`)
|
| 164 |
+
|
| 165 |
+
#### [NEW] [graph.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/agent/graph.py)
|
| 166 |
+
- LangGraph `StateGraph` with state: `{ messages, memory_context, tool_results }`
|
| 167 |
+
- Nodes: `retrieve_memory` → `call_model` → `tools` (loop) → `END`
|
| 168 |
+
- Conditional edges: if tool calls exist → execute tools → loop back to model
|
| 169 |
+
- SQLite checkpointer for session persistence
|
| 170 |
+
|
| 171 |
+
#### [NEW] [nodes.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/agent/nodes.py)
|
| 172 |
+
- `retrieve_memory_node()` — queries Chronos memory before each agent turn
|
| 173 |
+
- `call_model_node()` — invokes LLM with memory-augmented context
|
| 174 |
+
- `execute_tools_node()` — runs tools (including connected SaaS tools)
|
| 175 |
+
|
| 176 |
+
#### [NEW] [tools.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/agent/tools.py)
|
| 177 |
+
- `@tool query_chronos_memory` — agents can query the temporal memory
|
| 178 |
+
- `@tool ingest_event` — agents can store new events during execution
|
| 179 |
+
- `@tool list_connectors` — discover available SaaS tools
|
| 180 |
+
- `@tool call_connector` — invoke a connected SaaS API
|
| 181 |
+
|
| 182 |
+
---
|
| 183 |
+
|
| 184 |
+
### Component 4: Streamlit Dashboard (`dashboard/`)
|
| 185 |
+
|
| 186 |
+
#### [NEW] [app.py](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/dashboard/app.py)
|
| 187 |
+
- **Connect Your Product** — form to paste API key + register tool schema
|
| 188 |
+
- **Event Timeline** — visualize all ingested events on a temporal axis (extends "Letters to the Future" UI to B2B)
|
| 189 |
+
- **Test Agent** — text input to run agent prompts with live streaming
|
| 190 |
+
- **Usage & Billing** — event counts, orchestration calls, tier status
|
| 191 |
+
- Premium dark theme matching Chronos branding (deep navy + gold accents)
|
| 192 |
+
|
| 193 |
+
---
|
| 194 |
+
|
| 195 |
+
### Component 5: Configuration & Deployment
|
| 196 |
+
|
| 197 |
+
#### [NEW] [requirements.txt](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/requirements.txt)
|
| 198 |
+
```
|
| 199 |
+
fastapi>=0.115.0
|
| 200 |
+
uvicorn[standard]>=0.34.0
|
| 201 |
+
pydantic>=2.10.0
|
| 202 |
+
aiosqlite>=0.21.0
|
| 203 |
+
chromadb>=0.6.0
|
| 204 |
+
google-genai>=1.0.0
|
| 205 |
+
litellm>=1.60.0
|
| 206 |
+
langgraph>=0.4.0
|
| 207 |
+
langchain-google-genai>=2.0.0
|
| 208 |
+
langchain-core>=0.3.0
|
| 209 |
+
streamlit>=1.42.0
|
| 210 |
+
stripe>=11.0.0
|
| 211 |
+
python-dotenv>=1.0.0
|
| 212 |
+
httpx>=0.28.0
|
| 213 |
+
```
|
| 214 |
+
|
| 215 |
+
#### [NEW] [.env.example](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/.env.example)
|
| 216 |
+
```
|
| 217 |
+
GOOGLE_API_KEY=AIza... # Free from Google AI Studio
|
| 218 |
+
STRIPE_SECRET_KEY=sk_test_...
|
| 219 |
+
STRIPE_WEBHOOK_SECRET=whsec_...
|
| 220 |
+
CHRONOS_DB_PATH=./data/chronos.db
|
| 221 |
+
CHROMA_PERSIST_DIR=./data/chroma
|
| 222 |
+
API_SECRET_KEY=your-secret-for-signing-api-keys
|
| 223 |
+
```
|
| 224 |
+
|
| 225 |
+
#### [NEW] [README.md](file:///c:/Users/reman/OneDrive/Desktop/Chronos%20OS/chronos-hub/README.md)
|
| 226 |
+
- Project overview, quickstart, API docs, architecture diagram
|
| 227 |
+
|
| 228 |
+
---
|
| 229 |
+
|
| 230 |
+
## Build Schedule
|
| 231 |
+
|
| 232 |
+
| Day | Focus | Deliverable |
|
| 233 |
+
|-----|-------|-------------|
|
| 234 |
+
| **Day 1** | Memory Core | `chronos_core/` — SVO parser + dual calendars + vector store, all working with tests |
|
| 235 |
+
| **Day 2** | API Gateway | `api/` — `/ingest`, `/query` endpoints live, auth middleware, usage metering |
|
| 236 |
+
| **Day 3** | Agent Runner + Connectors | `agent/` — LangGraph graph + `/agent/run` + `/connect` endpoints |
|
| 237 |
+
| **Day 4** | Dashboard + Billing | `dashboard/app.py` + Stripe integration + deploy to Railway |
|
| 238 |
+
| **Day 5** | Polish + Launch | README, tests, Reddit posts ("Free temporal memory for your AI/SaaS") |
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## Pricing — Premium 3-Tier Model
|
| 243 |
+
|
| 244 |
+
Positioned competitively against Mem0 ($19–$249/mo), Zep (credit-based), and LangSmith ($39/seat):
|
| 245 |
+
|
| 246 |
+
| | **Explorer** (Free) | **Builder** ($49/mo) | **Scale** ($249/mo) |
|
| 247 |
+
|---|---|---|---|
|
| 248 |
+
| **Events/month** | 10,000 | 500,000 | 5,000,000 |
|
| 249 |
+
| **Orchestration calls** | 100 | 10,000 | Unlimited |
|
| 250 |
+
| **Connected tools** | 3 | 25 | Unlimited |
|
| 251 |
+
| **Retention** | 30 days | 1 year | Unlimited |
|
| 252 |
+
| **Agent threads** | 5 | 100 | Unlimited |
|
| 253 |
+
| **Support** | Community | Priority email | Dedicated Slack |
|
| 254 |
+
| **Event overage** | — | $0.05 / 1k events | $0.03 / 1k events |
|
| 255 |
+
| **Orchestration overage** | — | $0.10 / call | $0.07 / call |
|
| 256 |
+
|
| 257 |
+
> [!TIP]
|
| 258 |
+
> **Why these numbers?** Mem0 Pro is $249/mo. LangSmith Plus is $39/seat (but per-seat adds up fast for teams). Zep credits are opaque. Our $49 Builder tier undercuts Mem0 Starter ($19) on raw value (50x more events) while the $249 Scale tier matches Mem0 Pro but adds orchestration + agent runner + marketplace — features they don't have. The "Explorer" free tier is generous enough to hook startups from Reddit.
|
| 259 |
+
|
| 260 |
+
---
|
| 261 |
+
|
| 262 |
+
## Verification Plan
|
| 263 |
+
|
| 264 |
+
### Automated Tests
|
| 265 |
+
1. **Unit tests** for SVO parser (mock LLM responses, verify tuple extraction)
|
| 266 |
+
2. **Unit tests** for memory_store (insert events, query by time range, multi-hop)
|
| 267 |
+
3. **Integration tests** for `/ingest` → `/query` round-trip via `httpx.AsyncClient`
|
| 268 |
+
4. **Agent test** — run a sample prompt through LangGraph, verify memory retrieval
|
| 269 |
+
|
| 270 |
+
### Manual Verification
|
| 271 |
+
1. **cURL the API** — ingest sample events, query them back, run an agent prompt
|
| 272 |
+
2. **Streamlit dashboard** — connect a mock tool, visualize timeline, test agent chat
|
| 273 |
+
3. **Stripe test mode** — create checkout session, verify usage tracking
|
| 274 |
+
|
| 275 |
+
### Commands
|
| 276 |
+
```bash
|
| 277 |
+
# Run the API server
|
| 278 |
+
cd chronos-hub && uvicorn api.main:app --reload --port 8000
|
| 279 |
+
|
| 280 |
+
# Run the dashboard
|
| 281 |
+
cd chronos-hub && streamlit run dashboard/app.py --server.port 8501
|
| 282 |
+
|
| 283 |
+
# Run tests
|
| 284 |
+
cd chronos-hub && python -m pytest tests/ -v
|
| 285 |
+
```
|
docs/model_comparison.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AI Model Comparison — Local vs Free Cloud APIs
|
| 2 |
+
|
| 3 |
+
> **Purpose**: Choose the right brain for each task in the Marketing Engine.
|
| 4 |
+
> Hardware: RTX 3060 · 4GB VRAM · 16GB RAM · Windows
|
| 5 |
+
|
| 6 |
+
---
|
| 7 |
+
|
| 8 |
+
## Part 1 — Local Models (Zero Cost, Zero Internet)
|
| 9 |
+
|
| 10 |
+
Run 100% on your machine via Ollama or LM Studio.
|
| 11 |
+
|
| 12 |
+
| Model | Maker | Params | VRAM (Q4) | Context |
|
| 13 |
+
|---|---|---|---|---|
|
| 14 |
+
| **Llama 3.2 3B** | Meta | 3.2B | ~2.2 GB | 128K |
|
| 15 |
+
| **Phi-3.5 Mini** | Microsoft | 3.8B | ~2.6 GB | 128K |
|
| 16 |
+
| **Gemma 2 2B** | Google | 2.6B | ~3.0 GB (Q8) | 8K |
|
| 17 |
+
| **Falcon 3 3B** | TII | 3B | ~2.3 GB | 8K |
|
| 18 |
+
| **DeepSeek R1 1.5B** | DeepSeek | 1.5B | ~1.1 GB | 64K |
|
| 19 |
+
| **DeepSeek R1 7B** | DeepSeek | 7B | ~4.0 GB (tight) | 64K |
|
| 20 |
+
| ~~Mistral 7B~~ | Mistral | 7.3B | ~3.5 GB (tight) | 32K |
|
| 21 |
+
|
| 22 |
+
### Capability Breakdown — Local Models
|
| 23 |
+
|
| 24 |
+
| Capability | Llama 3.2 3B | Phi-3.5 Mini | Gemma 2 2B | DS R1 1.5B | DS R1 7B |
|
| 25 |
+
|---|---|---|---|---|---|
|
| 26 |
+
| **Text Generation** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
| 27 |
+
| **Math / Reasoning** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
| 28 |
+
| **Coding / Logic** | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
| 29 |
+
| **Self-correction** | ⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
| 30 |
+
| **Conversation / Reply** | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
| 31 |
+
| **Summarization** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
|
| 32 |
+
| **SEO / Keyword writing** | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐ |
|
| 33 |
+
| **Image generation** | ❌ | ❌ | ❌ | ❌ | ❌ |
|
| 34 |
+
| **Vision (image input)** | ✅ | ❌ | ❌ | ❌ | ❌ |
|
| 35 |
+
| **Speed (tok/sec local)** | ~80–100 | ~60–80 | ~70–90 | ~120–150 | ~40–60 |
|
| 36 |
+
| **VRAM safe for 4GB** | ✅ Easy | ✅ Easy | ✅ Easy | ✅ Very easy | ⚠️ Tight |
|
| 37 |
+
|
| 38 |
+
### Local Model Winner Per Task
|
| 39 |
+
|
| 40 |
+
| Task | Best Local Model | Why |
|
| 41 |
+
|---|---|---|
|
| 42 |
+
| Writing captions / posts | **Phi-3.5 Mini** | Best text quality at small size |
|
| 43 |
+
| Replying to comments | **Gemma 2 2B** | Best conversational fluency |
|
| 44 |
+
| Risk scoring / decisions | **DeepSeek R1 1.5B** | Chain-of-thought reasoning, tiny VRAM |
|
| 45 |
+
| Code self-repair / logic | **DeepSeek R1 7B** | Built for reasoning + rewriting |
|
| 46 |
+
| Fast background tasks | **Llama 3.2 3B** | Fastest + has vision |
|
| 47 |
+
|
| 48 |
+
---
|
| 49 |
+
|
| 50 |
+
## Part 2 — DeepSeek R1 — The Reasoning Giant
|
| 51 |
+
|
| 52 |
+
> [!IMPORTANT]
|
| 53 |
+
> DeepSeek R1 is not just a language model. It is a **reasoning model** trained with reinforcement learning, designed to think step-by-step before answering. This makes it fundamentally different from all other models above.
|
| 54 |
+
|
| 55 |
+
### What Makes R1 Special
|
| 56 |
+
|
| 57 |
+
| Feature | Normal Models | DeepSeek R1 |
|
| 58 |
+
|---|---|---|
|
| 59 |
+
| How it answers | Directly outputs text | Thinks out loud first (chain-of-thought), then answers |
|
| 60 |
+
| Self-correction | Rare | **Built-in** — it catches and fixes its own errors mid-thought |
|
| 61 |
+
| Reasoning depth | Surface level | Explores multiple approaches before committing |
|
| 62 |
+
| Code rewriting | Basic edits | Analyzes why code fails, rewrites with explanation |
|
| 63 |
+
| Math accuracy | Moderate | Near-perfect on complex problems |
|
| 64 |
+
|
| 65 |
+
### All DeepSeek R1 Variants
|
| 66 |
+
|
| 67 |
+
| Variant | Type | Params | VRAM (Q4) | Free API | Best Use |
|
| 68 |
+
|---|---|---|---|---|---|
|
| 69 |
+
| **R1 1.5B** (Distilled) | Local | 1.5B | ~1.1 GB | Groq | Fast local scoring, logic checks |
|
| 70 |
+
| **R1 7B** (Distilled) | Local | 7B | ~4.0 GB | Groq / OpenRouter | Local code reasoning (tight fit) |
|
| 71 |
+
| **R1 14B** (Distilled) | Local (needs RAM offload) | 14B | ~8 GB | Groq | Too heavy for 4GB alone |
|
| 72 |
+
| **R1 32B** (Distilled) | Cloud only | 32B | 18 GB | Groq / OpenRouter | Best reasoning via free API |
|
| 73 |
+
| **R1 70B** (Distilled) | Cloud only | 70B | 40 GB | Groq | Near-full R1 power free |
|
| 74 |
+
| **R1 671B** (Full) | Cloud only | 671B | 400 GB | OpenRouter (paid) | Godmode — not needed yet |
|
| 75 |
+
|
| 76 |
+
### R1 Benchmark vs Other Models
|
| 77 |
+
|
| 78 |
+
| Benchmark | GPT-4o | Gemini Flash | Llama 70B | **DeepSeek R1** |
|
| 79 |
+
|---|---|---|---|---|
|
| 80 |
+
| MATH-500 | 76% | 71% | 68% | **97.3%** |
|
| 81 |
+
| AIME (hard math) | 9.3% | 7% | 4% | **79.8%** |
|
| 82 |
+
| Codeforces (coding) | 46% | 40% | 34% | **96.3%** |
|
| 83 |
+
| MMLU (knowledge) | 88% | 85% | 86% | **90.8%** |
|
| 84 |
+
| LiveCodeBench | 53% | 48% | 44% | **65.9%** |
|
| 85 |
+
|
| 86 |
+
> R1 is the **#1 open-source reasoning model** as of early 2026. Free via Groq.
|
| 87 |
+
|
| 88 |
+
### R1's Role in the Marketing Engine: Self-Improvement
|
| 89 |
+
|
| 90 |
+
This is the game-changing part. Because R1 reasons step by step and can self-correct, it can:
|
| 91 |
+
|
| 92 |
+
```
|
| 93 |
+
1. Read the engine's own source code
|
| 94 |
+
2. Read the memory/pattern data (what's working, what's failing)
|
| 95 |
+
3. Reason about WHY something is underperforming
|
| 96 |
+
4. Propose and write a rewritten version of that module
|
| 97 |
+
5. Validate its own output before returning it
|
| 98 |
+
```
|
| 99 |
+
|
| 100 |
+
**Example auto-improvement loop:**
|
| 101 |
+
```
|
| 102 |
+
Scorer detects: "video posts on instagram underperforming last 7 days"
|
| 103 |
+
→ R1 reads scorer.py + last 50 post results from memory
|
| 104 |
+
→ R1 reasons: "The engagement formula weights saves too low vs
|
| 105 |
+
comments. Instagram algorithm changed — saves now
|
| 106 |
+
matter 4x more. Rewriting compute_engagement_score..."
|
| 107 |
+
→ R1 outputs updated scoring function
|
| 108 |
+
→ Engine tests new function on historical data
|
| 109 |
+
→ If score improves → auto-apply patch
|
| 110 |
+
→ Memory logs: "scorer updated by R1 on [date], reason: [...]"
|
| 111 |
+
```
|
| 112 |
+
|
| 113 |
+
**Other self-improvement tasks R1 can handle:**
|
| 114 |
+
- Rewrite the delay engine when ban risk rises
|
| 115 |
+
- Adjust hook-style weights when memory shows shift in platform trends
|
| 116 |
+
- Detect and fix broken API integrations automatically
|
| 117 |
+
- Propose new pattern types the scorer should track
|
| 118 |
+
- Review and improve its own generated captions
|
| 119 |
+
|
| 120 |
+
---
|
| 121 |
+
|
| 122 |
+
## Part 3 — Free Cloud APIs
|
| 123 |
+
|
| 124 |
+
No GPU load — runs on their servers. You call an API.
|
| 125 |
+
|
| 126 |
+
| API | Model | Free/Day | Speed | No Card | Reasoning |
|
| 127 |
+
|---|---|---|---|---|---|
|
| 128 |
+
| **Google AI Studio** | Gemini 2.0 Flash | 1,500 req | Very fast | ✅ | Moderate |
|
| 129 |
+
| **Google AI Studio** | Gemini 2.5 Flash-Lite | 1,000 req | Fast | ✅ | Good |
|
| 130 |
+
| **Groq** | Llama 3.3 70B | 14,400 req | Fastest | ✅ | Good |
|
| 131 |
+
| **Groq** | **DeepSeek R1 Distill 70B** | ~1,000 req | Fast | ✅ | **Best** |
|
| 132 |
+
| **Groq** | **DeepSeek R1 1.5B** | ~10,000 req | Fastest | ✅ | **Strong** |
|
| 133 |
+
| **OpenRouter** | DeepSeek R1 32B | ~200 req | Fast | ✅ | **Excellent** |
|
| 134 |
+
| **Cloudflare Workers AI** | Llama, Mistral | 10,000 req | Moderate | ✅ | Basic |
|
| 135 |
+
|
| 136 |
+
### Cloud API Capability Breakdown
|
| 137 |
+
|
| 138 |
+
| Capability | Gemini 2.0 Flash | Groq Llama 70B | DS R1 70B (Groq) | DS R1 32B (OR) |
|
| 139 |
+
|---|---|---|---|---|
|
| 140 |
+
| **Text Generation** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
| 141 |
+
| **Reasoning / Logic** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
| 142 |
+
| **Coding / Code rewrite** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
| 143 |
+
| **Self-correction** | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
|
| 144 |
+
| **SEO writing** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
|
| 145 |
+
| **Conversation / Reply** | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
|
| 146 |
+
| **Image understanding** | ✅ Multimodal | ❌ | ❌ | ❌ |
|
| 147 |
+
| **Long context** | ✅ 1M tokens | ✅ 128K | ✅ 64K | ✅ 64K |
|
| 148 |
+
| **Speed** | Very fast | Fastest | Fast | Fast |
|
| 149 |
+
|
| 150 |
+
---
|
| 151 |
+
|
| 152 |
+
## Part 4 — Image Generation Options
|
| 153 |
+
|
| 154 |
+
| Tool | Type | Cost | VRAM | Quality |
|
| 155 |
+
|---|---|---|---|---|
|
| 156 |
+
| **Stable Diffusion 1.5** | Local | Free | ~3.5 GB | ⭐⭐⭐ |
|
| 157 |
+
| **Stable Diffusion 2.1** | Local | Free | ~3.8 GB | ⭐⭐⭐⭐ |
|
| 158 |
+
| **Cloudflare Workers AI** | Cloud free | Free | 0 | ⭐⭐⭐ |
|
| 159 |
+
| **Hugging Face (FLUX)** | Cloud free | Free (queued) | 0 | ⭐⭐⭐⭐⭐ |
|
| 160 |
+
| **Pollinations.ai** | Cloud free | Free | 0 | ⭐⭐⭐⭐ |
|
| 161 |
+
|
| 162 |
+
> **Best for 4GB**: SD 1.5 locally + Pollinations.ai or HuggingFace FLUX as free cloud backup.
|
| 163 |
+
|
| 164 |
+
---
|
| 165 |
+
|
| 166 |
+
## Part 5 — Final Task Assignment (Updated)
|
| 167 |
+
|
| 168 |
+
### Complete Hybrid Strategy
|
| 169 |
+
|
| 170 |
+
```
|
| 171 |
+
TASK → MODEL → WHERE
|
| 172 |
+
-----------------------------------------------------------------------
|
| 173 |
+
Write captions / scripts → Gemini 2.0 Flash Cloud (1500/day)
|
| 174 |
+
Reply to comments / DMs → Groq Llama 70B Cloud (14400/day)
|
| 175 |
+
SEO content / keyword copy → Gemini 2.0 Flash Cloud
|
| 176 |
+
Risk scoring (fast) → DeepSeek R1 1.5B (local) Local (~1GB VRAM)
|
| 177 |
+
Pattern analysis / decisions → DeepSeek R1 7B (local) Local (~4GB VRAM)
|
| 178 |
+
CODE SELF-REPAIR / REWRITE → DeepSeek R1 70B (Groq) Cloud (free)
|
| 179 |
+
Generate images / thumbnails → SD 1.5 Local (3.5GB VRAM)
|
| 180 |
+
Image fallback → Pollinations.ai Cloud (free)
|
| 181 |
+
```
|
| 182 |
+
|
| 183 |
+
> [!IMPORTANT]
|
| 184 |
+
> R1 for code rewriting runs **on demand only** (not constantly) — triggered when the scorer detects declining performance or an error. This keeps API quota usage minimal and makes the engine genuinely self-improving without burning free limits.
|
| 185 |
+
|
| 186 |
+
### API Keys Needed (All Free, No Card)
|
| 187 |
+
|
| 188 |
+
| Service | Get Key At | Used For |
|
| 189 |
+
|---|---|---|
|
| 190 |
+
| Gemini | [aistudio.google.com](https://aistudio.google.com) | Writing, SEO, vision |
|
| 191 |
+
| Groq | [console.groq.com](https://console.groq.com) | Speed + R1 reasoning |
|
| 192 |
+
| OpenRouter | [openrouter.ai](https://openrouter.ai) | Fallback / R1 32B |
|
| 193 |
+
|
| 194 |
+
---
|
| 195 |
+
|
| 196 |
+
## AirLLM Note
|
| 197 |
+
|
| 198 |
+
> [!TIP]
|
| 199 |
+
> **AirLLM** runs 70B models on 4GB via layer-sharding (~1–5 tok/sec). Not worth it when Groq gives R1 70B free at full speed. Only use if going fully offline.
|
| 200 |
+
|
| 201 |
+
---
|
| 202 |
+
|
| 203 |
+
*Last updated: March 2026 · Hardware: RTX 3060 4GB VRAM · 16GB RAM*
|
docs/modeldata.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
As of April 2026, Ollama Cloud refers to the feature that allows you to offload high-parameter models to Ollama's managed infrastructure while using your local CLI or Desktop App. This allows you to run "frontier-level" models that normally require 48GB+ of VRAM on a standard laptop. [1, 2, 3]
|
| 2 |
+
The most powerful models currently available in the Ollama library with specific "cloud" support include:
|
| 3 |
+
## Top General & Reasoning Models
|
| 4 |
+
|
| 5 |
+
* DeepSeek-V3.2-Cloud: A top-tier general model known for sheer power and high computational efficiency. It excels in broad general knowledge and complex multi-turn conversations.
|
| 6 |
+
* GLM-5.1-Cloud: Currently ranked as one of the strongest open-weight models for advanced reasoning and engineering tasks.
|
| 7 |
+
* Kimi-K2.5-Cloud: A native multimodal "thinking" model designed for agentic workflows. It integrates vision and language seamlessly for tasks requiring deep logical analysis.
|
| 8 |
+
* Nemotron-3-Super (120B)-Cloud: The 120B parameter model you mentioned, optimized by NVIDIA for maximum accuracy in multi-agent applications. [4, 5, 6, 7]
|
| 9 |
+
*
|
| 10 |
+
|
| 11 |
+
## Top Coding & Engineering Models
|
| 12 |
+
|
| 13 |
+
* Qwen3.6-Coder-Next-Cloud: Alibaba's flagship coding model, specifically optimized for agentic coding workflows. It handles vast codebases and multi-file edits effectively.
|
| 14 |
+
* Devstral-2-Cloud (123B): A massive model that excels at exploring complex codebases and acting as a power software engineering agent.
|
| 15 |
+
* MiniMax-M2.7-Cloud: Frequently cited for professional productivity and high-performance tool-heavy workloads. [4, 5, 7, 8]
|
| 16 |
+
*
|
| 17 |
+
|
| 18 |
+
## Efficient & Multimodal Models
|
| 19 |
+
|
| 20 |
+
* Gemma 4 (31B)-Cloud: These are Google's latest open-weights that offer a balance of speed and high performance for reasoning and multimodal (vision/audio) tasks.
|
| 21 |
+
* Gemini-3-Flash-Preview-Cloud: It is built for speed while maintaining high intelligence for vision and tool use.
|
| 22 |
+
* Qwen 3.5-Cloud (122B): Part of the most recommended model family in 2026, it provides utility across text and vision. [4, 5, 7]
|
| 23 |
+
*
|
| 24 |
+
|
| 25 |
+
## How to Use These Models
|
| 26 |
+
To use these models without hardware limits:
|
| 27 |
+
|
| 28 |
+
1. Sign In: Use ollama signin in your terminal to authenticate your Ollama account.
|
| 29 |
+
2. Pull/Run with -Cloud: Use the -cloud suffix to run the model on remote infrastructure. For example:
|
| 30 |
+
* ollama run deepseek-v3.2:671b-cloud
|
| 31 |
+
* ollama run qwen3.6-coder:480b-cloud [2, 9, 10]
|
| 32 |
+
|
| 33 |
+
Would you like information on the hardware requirements for running these models locally?
|
| 34 |
+
|
| 35 |
+
[1] [https://www.mintlify.com](https://www.mintlify.com/ollama/ollama/platforms/cloud)
|
| 36 |
+
[2] [https://www.knightli.com](https://www.knightli.com/en/2026/04/09/ollama-cloud-models-guide/)
|
| 37 |
+
[3] [https://pandeyparul.medium.com](https://pandeyparul.medium.com/three-ways-in-which-ollama-makes-trying-new-models-much-easier-now-a089d0ec18f7)
|
| 38 |
+
[4] [https://ollama.com](https://ollama.com/search?c=cloud)
|
| 39 |
+
[5] [https://www.latent.space](https://www.latent.space/p/ainews-top-local-models-list-april)
|
| 40 |
+
[6] [https://whatllm.org](https://whatllm.org/best-open-source-llm)
|
| 41 |
+
[7] [https://ollama.com](https://ollama.com/search?c=tools)
|
| 42 |
+
[8] [https://ollama.com](https://ollama.com/search)
|
| 43 |
+
[9] [https://ollama.com](https://ollama.com/blog/cloud-models#:~:text=Usage.%20Cloud%20models%20behave%20like%20regular%20models.,ago%20qwen3%2Dcoder:480b%2Dcloud%2011483b8f8765%20%2D%202%20days%20ago.)
|
| 44 |
+
[10] [https://ollama.com](https://ollama.com/blog/cloud-models)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
NVIDIA NIM (build.nvidia.com)
|
| 48 |
+
This is NVIDIA's official hosted platform for developers.
|
| 49 |
+
Access: You can test the model for free directly in the browser or via API at build.nvidia.com.
|
| 50 |
+
The "Full Power" Advantage: It typically offers a higher rate limit than general free-tier aggregators (up to 40-200 RPM by request) and supports the model's specialized reasoning features natively.
|
| 51 |
+
Credits: New accounts often receive free credits to use their Inference Microservices (NIM), which provide OpenAI-compatible API endpoints.
|
| 52 |
+
NVIDIA Developer Forums
|
| 53 |
+
NVIDIA Developer Forums
|
| 54 |
+
+4
|
docs/precision-mem-bench.md
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# PrecisionMemBench — Complete Reference
|
| 2 |
+
|
| 3 |
+
> **Status:** Smriti has a working adapter and eval runner wired to this benchmark.
|
| 4 |
+
> **Source:** [github.com/tenurehq/precisionmembench](https://github.com/tenurehq/precisionmembench)
|
| 5 |
+
> **Paper:** arxiv.org (TenureHQ, 2026)
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## 1. What It Is
|
| 10 |
+
|
| 11 |
+
**PrecisionMemBench** is an AI memory retrieval benchmark created by **TenureHQ** to measure one specific thing: does a memory system return the *right* beliefs and *only* the right beliefs, without relying on a generative model to cover its mistakes?
|
| 12 |
+
|
| 13 |
+
Most existing benchmarks (LoCoMo, LongMemEval) measure **answer quality** — they let a judge LLM read whatever the retrieval system returns and score whether the final answer is correct. This hides retrieval failures because a strong generative model can often produce the right answer even from noisy or incomplete context.
|
| 14 |
+
|
| 15 |
+
PrecisionMemBench removes the generative model from the loop entirely. It asserts directly against what the retrieval system returned.
|
| 16 |
+
|
| 17 |
+
### Core Design Principle
|
| 18 |
+
> "If your retrieval is imprecise, no amount of generative compensation should mask it."
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## 2. What It Tests
|
| 23 |
+
|
| 24 |
+
**89 total cases** across 4 dimensions:
|
| 25 |
+
|
| 26 |
+
| Dimension | What it checks |
|
| 27 |
+
|---|---|
|
| 28 |
+
| Precision | Does the system return only relevant beliefs? |
|
| 29 |
+
| Noise Isolation | Does the system exclude semantically-similar-but-wrong beliefs? |
|
| 30 |
+
| Session Latency | Does the system handle time-scoped / session-bounded retrieval? |
|
| 31 |
+
| Belief Mutability | Does the system correctly suppress stale, resolved, or superseded beliefs? |
|
| 32 |
+
|
| 33 |
+
---
|
| 34 |
+
|
| 35 |
+
## 3. Benchmark Data / Fixtures
|
| 36 |
+
|
| 37 |
+
The benchmark ships three JSON fixture files:
|
| 38 |
+
|
| 39 |
+
| File | Contents |
|
| 40 |
+
|---|---|
|
| 41 |
+
| fixtures/beliefs.seed.json | The belief corpus loaded into the memory system before testing |
|
| 42 |
+
| fixtures/retrieval.cases.json | 89 retrieval test cases (the main eval) |
|
| 43 |
+
| fixtures/session-retrieval.cases.json | Session-scoped test cases (additional, opt-in) |
|
| 44 |
+
|
| 45 |
+
### Belief Object Structure
|
| 46 |
+
|
| 47 |
+
```json
|
| 48 |
+
{
|
| 49 |
+
"_id": "belief-uuid-001",
|
| 50 |
+
"user_id": "test-user",
|
| 51 |
+
"canonical_name": "Alice's current city",
|
| 52 |
+
"aliases": ["Alice lives in", "Alice moved to"],
|
| 53 |
+
"content": "Alice lives in Seattle as of March 2025.",
|
| 54 |
+
"why_it_matters": "Used to resolve location-dependent queries.",
|
| 55 |
+
"scope": ["user:work"],
|
| 56 |
+
"type": "fact",
|
| 57 |
+
"resolved_at": null,
|
| 58 |
+
"superseded_by": null,
|
| 59 |
+
"pinned": false
|
| 60 |
+
}
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
### Belief Types
|
| 64 |
+
|
| 65 |
+
| Type | Meaning | In /search results? |
|
| 66 |
+
|---|---|---|
|
| 67 |
+
| fact | Active, current fact | Yes |
|
| 68 |
+
| open_question | Unresolved question/task | No — openQuestions slot only |
|
| 69 |
+
| resolved (resolved_at != null) | Historical, no longer active | No |
|
| 70 |
+
| superseded (superseded_by != null) | Replaced by newer belief | No |
|
| 71 |
+
| pinned = true | Retrieved separately | No (via /pinned endpoint) |
|
| 72 |
+
|
| 73 |
+
---
|
| 74 |
+
|
| 75 |
+
## 4. Scoring Rules
|
| 76 |
+
|
| 77 |
+
Each test case specifies expected retrieval behaviour:
|
| 78 |
+
|
| 79 |
+
```json
|
| 80 |
+
{
|
| 81 |
+
"caseId": "retrieval-alice-city-scope",
|
| 82 |
+
"category": "precision",
|
| 83 |
+
"description": "Should return current city belief, not old city belief",
|
| 84 |
+
"query": "Where does Alice live?",
|
| 85 |
+
"scope": ["user:work"],
|
| 86 |
+
"expect": {
|
| 87 |
+
"relevantBeliefs": {
|
| 88 |
+
"mustInclude": ["belief-uuid-001"],
|
| 89 |
+
"mustExclude": ["belief-uuid-old-city"],
|
| 90 |
+
"shouldOnlyInclude": ["belief-uuid-001"],
|
| 91 |
+
"maxCount": 2
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
```
|
| 96 |
+
|
| 97 |
+
### Assertion Types
|
| 98 |
+
|
| 99 |
+
**mustInclude**
|
| 100 |
+
Belief IDs that MUST appear in results. Any missing = FAIL.
|
| 101 |
+
|
| 102 |
+
**mustExclude**
|
| 103 |
+
Belief IDs that must NOT appear in results. Any present = FAIL.
|
| 104 |
+
This is where "dump everything" systems fail — they return superseded facts.
|
| 105 |
+
|
| 106 |
+
**shouldOnlyInclude**
|
| 107 |
+
Strict allowlist. Any belief returned outside this list = FAIL.
|
| 108 |
+
The hardest constraint. Tests true precision.
|
| 109 |
+
|
| 110 |
+
**maxCount**
|
| 111 |
+
Maximum beliefs allowed in response. If len(results) > maxCount = FAIL.
|
| 112 |
+
Prevents "return 50 things and one is right" strategies.
|
| 113 |
+
|
| 114 |
+
### Pass Type Classification
|
| 115 |
+
|
| 116 |
+
| Pass Type | Meaning |
|
| 117 |
+
|---|---|
|
| 118 |
+
| active_retrieval | shouldOnlyInclude non-empty, all required beliefs returned, no noise. Real signal. |
|
| 119 |
+
| structural | Passed constraints but shouldOnlyInclude was empty. Weaker signal. |
|
| 120 |
+
| trivially_empty | No assertions triggered. Passes by default. No signal at all. |
|
| 121 |
+
| fail | Any assertion violated. |
|
| 122 |
+
|
| 123 |
+
### Score Metric
|
| 124 |
+
|
| 125 |
+
```
|
| 126 |
+
mean_precision = active_retrieval_passes / total_cases
|
| 127 |
+
```
|
| 128 |
+
|
| 129 |
+
TenureHQ reported 1.00 mean precision (perfect score) for their own Tenure system.
|
| 130 |
+
Competing systems scored 0.22 or lower.
|
| 131 |
+
|
| 132 |
+
---
|
| 133 |
+
|
| 134 |
+
## 5. Smriti's Actual Test — What Happened
|
| 135 |
+
|
| 136 |
+
**Run date:** 2026-06-22 at 19:12 UTC
|
| 137 |
+
**Tested against:** localhost:8080 (smriti_service.py — local ChromaDB service)
|
| 138 |
+
**Threshold used:** 0.45 cosine distance (default)
|
| 139 |
+
|
| 140 |
+
### Results
|
| 141 |
+
|
| 142 |
+
| Metric | Smriti (June 22) | Tenure (baseline) | Mem0 (baseline) |
|
| 143 |
+
|---|---|---|---|
|
| 144 |
+
| Total cases | 77 | 89 | 89 |
|
| 145 |
+
| Active retrieval passes | 9 | ~89 (perfect) | ~20 |
|
| 146 |
+
| Structural passes | 34 | 0 | - |
|
| 147 |
+
| Trivially empty passes | 34 | 0 | - |
|
| 148 |
+
| Fails | 0 | 0 | - |
|
| 149 |
+
| **Mean precision** | **0.117** | **1.00** | **~0.22** |
|
| 150 |
+
| p50 latency | 2096 ms | <100 ms | - |
|
| 151 |
+
| p95 latency | 2119 ms | <100 ms | - |
|
| 152 |
+
|
| 153 |
+
### What the Numbers Mean
|
| 154 |
+
|
| 155 |
+
**0 fails** — good. Smriti never returned a belief it was explicitly told to exclude.
|
| 156 |
+
That means type-aware filtering (suppressing resolved, superseded, open_question beliefs) worked.
|
| 157 |
+
|
| 158 |
+
**9 active passes out of 77** (precision = 0.117) — below the community average of ~0.22.
|
| 159 |
+
The problem: Smriti missed mustInclude beliefs on many cases where shouldOnlyInclude required exact matches.
|
| 160 |
+
The threshold of 0.45 was too broad for noise isolation — retrieval brought in noise (structural pass instead of active).
|
| 161 |
+
Or too narrow — true positives were below similarity threshold and not returned at all.
|
| 162 |
+
|
| 163 |
+
**34 trivially empty passes** — these are cases with no assertions, they pass by definition and add no signal.
|
| 164 |
+
The effective test pool is 77 - 34 = 43 meaningful cases.
|
| 165 |
+
Active passes on meaningful cases: 9/43 = 0.209 — closer to the community average.
|
| 166 |
+
|
| 167 |
+
**2 second latency** — serious problem.
|
| 168 |
+
p50 of 2096ms means the AVERAGE query took 2 seconds.
|
| 169 |
+
This was the ChromaDB telemetry issue — each request was blocked by a network call to ChromaDB's analytics server.
|
| 170 |
+
Fix: telemetry disabled in v2 of smriti_service.py. Expected latency after fix: <100ms.
|
| 171 |
+
|
| 172 |
+
### Why the Latency Was 2 Seconds
|
| 173 |
+
|
| 174 |
+
smriti_service.py v1 did not disable ChromaDB telemetry. On every `/search` call, ChromaDB made an outbound HTTP request to its analytics endpoint before returning results. This added ~2000ms of blocked I/O to every single query.
|
| 175 |
+
|
| 176 |
+
Fix applied in v2:
|
| 177 |
+
```python
|
| 178 |
+
os.environ["ANONYMIZED_TELEMETRY"] = "False"
|
| 179 |
+
os.environ["CHROMA_TELEMETRY_ENABLED"] = "false"
|
| 180 |
+
```
|
| 181 |
+
|
| 182 |
+
This was fixed before the doc was written. The 2096ms numbers are from the unfixed run.
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## 6. Why Benchmark Results Are NOT in the Smriti Dashboard
|
| 187 |
+
|
| 188 |
+
The June 22 benchmark ran against Smriti locally — the full system, running on the same laptop where
|
| 189 |
+
the entire codebase lives. smriti_service.py IS Smriti retrieval layer. The test is real.
|
| 190 |
+
|
| 191 |
+
`
|
| 192 |
+
benchmark/run_eval.py
|
| 193 |
+
--> POST http://localhost:8080/add (Smriti, running locally)
|
| 194 |
+
--> POST http://localhost:8080/search (Smriti, running locally)
|
| 195 |
+
--> DELETE http://localhost:8080/reset (Smriti, running locally)
|
| 196 |
+
`
|
| 197 |
+
|
| 198 |
+
The only thing NOT tested was the deployed live server (HF Space: spy9191-chronos-api-backend.hf.space).
|
| 199 |
+
Local Smriti = the real system. HF Space = the deployed version of the same system.
|
| 200 |
+
|
| 201 |
+
Why results are not in the Smriti dashboard:
|
| 202 |
+
- The benchmark uses an in-memory ChromaDB store wiped between test cases (via /reset)
|
| 203 |
+
- This is intentional — benchmark test data must not pollute the persistent memory database
|
| 204 |
+
- The dashboard shows production Neon/pgvector counts, not benchmark run data
|
| 205 |
+
- No benchmark beliefs were written to the persistent database — by design
|
| 206 |
+
- The benchmark is a test harness. It tests retrieval quality, it is not a data source.
|
| 207 |
+
|
| 208 |
+
## 7. How Smriti Is Wired To It
|
| 209 |
+
|
| 210 |
+
### 7.1 benchmark/smriti_service.py — Local Benchmark Service
|
| 211 |
+
|
| 212 |
+
Self-contained FastAPI service implementing the PrecisionMemBench provider API locally:
|
| 213 |
+
|
| 214 |
+
| Endpoint | Purpose |
|
| 215 |
+
|---|---|
|
| 216 |
+
| POST /add | Load a belief into ChromaDB vector store |
|
| 217 |
+
| POST /search | Retrieve beliefs by query; applies threshold + type filters |
|
| 218 |
+
| DELETE /reset | Wipe all collections between runs |
|
| 219 |
+
| GET /health | Service status, model info, belief count |
|
| 220 |
+
|
| 221 |
+
Key implementation details:
|
| 222 |
+
- Embedding model: all-MiniLM-L6-v2 (sentence-transformers)
|
| 223 |
+
- Vector store: ChromaDB (in-memory)
|
| 224 |
+
- Similarity threshold: 0.45 cosine distance (SMRITI_SIMILARITY_THRESHOLD)
|
| 225 |
+
- LRU embedding cache: 512 entries
|
| 226 |
+
- Type-aware filtering: open_question, resolved, superseded, pinned excluded from /search
|
| 227 |
+
- ChromaDB telemetry: disabled in v2 (was causing 2s latency in June 22 run)
|
| 228 |
+
|
| 229 |
+
### 7.2 benchmark/run_eval.py — Eval Runner
|
| 230 |
+
|
| 231 |
+
```bash
|
| 232 |
+
python benchmark/run_eval.py --url http://localhost:8080
|
| 233 |
+
python benchmark/run_eval.py --url http://localhost:8080 --threshold 0.35
|
| 234 |
+
python benchmark/run_eval.py --url http://localhost:8080 --no-reseed
|
| 235 |
+
python benchmark/run_eval.py --url http://localhost:8080 --session
|
| 236 |
+
```
|
| 237 |
+
|
| 238 |
+
Output: precisionMemBench/test-results/retrieval-report-smriti.json
|
| 239 |
+
|
| 240 |
+
### 7.3 benchmark_adapter.py — Live API Adapter
|
| 241 |
+
|
| 242 |
+
Bridges the benchmark harness to Smriti's production API (NOT used in June 22 run):
|
| 243 |
+
|
| 244 |
+
```
|
| 245 |
+
Benchmark Harness -> benchmark_adapter.py (port 8765) -> Smriti API (HF Space)
|
| 246 |
+
```
|
| 247 |
+
|
| 248 |
+
Config:
|
| 249 |
+
```bash
|
| 250 |
+
SMRITI_API_URL=https://spy9191-chronos-api-backend.hf.space
|
| 251 |
+
SMRITI_API_KEY=chrn_...
|
| 252 |
+
SMRITI_THRESHOLD=0.15
|
| 253 |
+
SMRITI_SEMANTIC_WEIGHT=0.7
|
| 254 |
+
SMRITI_SCOPE_MODE=test_id
|
| 255 |
+
ADAPTER_PORT=8765
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
---
|
| 259 |
+
|
| 260 |
+
## 8. How To Run a Full Eval
|
| 261 |
+
|
| 262 |
+
Step 1 — Start the local service:
|
| 263 |
+
```bash
|
| 264 |
+
cd "...\smriti\benchmark"
|
| 265 |
+
pip install -r requirements.txt
|
| 266 |
+
python smriti_service.py
|
| 267 |
+
# Listening on http://localhost:8080
|
| 268 |
+
```
|
| 269 |
+
|
| 270 |
+
Step 2 — Run the eval:
|
| 271 |
+
```bash
|
| 272 |
+
python benchmark/run_eval.py --url http://localhost:8080
|
| 273 |
+
```
|
| 274 |
+
|
| 275 |
+
Step 3 — Results in:
|
| 276 |
+
```
|
| 277 |
+
precisionMemBench/test-results/retrieval-report-smriti.json
|
| 278 |
+
```
|
| 279 |
+
|
| 280 |
+
To run against the live API instead:
|
| 281 |
+
```bash
|
| 282 |
+
# Terminal 1
|
| 283 |
+
SMRITI_API_KEY=chrn_xxx python benchmark_adapter.py
|
| 284 |
+
|
| 285 |
+
# Terminal 2
|
| 286 |
+
python benchmark/run_eval.py --url http://localhost:8765
|
| 287 |
+
```
|
| 288 |
+
|
| 289 |
+
---
|
| 290 |
+
|
| 291 |
+
## 9. Known Issues / Tuning
|
| 292 |
+
|
| 293 |
+
| Issue | Root Cause | Status |
|
| 294 |
+
|---|---|---|
|
| 295 |
+
| p50 latency 2096ms | ChromaDB telemetry enabled | Fixed in v2 (telemetry disabled) |
|
| 296 |
+
| Mean precision 0.117 | Threshold not tuned for belief format | Needs re-run after telemetry fix |
|
| 297 |
+
| 34 trivial passes out of 77 | Benchmark cases with no assertions | Expected — not Smriti's fault |
|
| 298 |
+
| Benchmark not testing real API | Local ChromaDB service != production | adapter.py exists, needs to be used |
|
| 299 |
+
| True positives missed | Threshold 0.45 possibly still too strict or broad | Need threshold sweep: 0.2, 0.3, 0.4, 0.5 |
|
| 300 |
+
|
| 301 |
+
---
|
| 302 |
+
|
| 303 |
+
## 10. PrecisionMemBench vs. LongMemEval vs. LoCoMo
|
| 304 |
+
|
| 305 |
+
| | PrecisionMemBench | LongMemEval | LoCoMo |
|
| 306 |
+
|---|---|---|---|
|
| 307 |
+
| What it measures | Retrieval precision only | End-to-end answer quality | Long-conversation memory + QA |
|
| 308 |
+
| Generative model in loop? | No | Yes (LLM judge) | Yes |
|
| 309 |
+
| Gameable? | Hard — strict ID assertions | Yes — Mem0 hardcoded question_ids | Harder but possible |
|
| 310 |
+
| Community credibility | Niche / TenureHQ-origin | High (r/AIMemory reference) | High (leaderboard active) |
|
| 311 |
+
| Smriti adapter exists? | Yes (full, local + live) | Not yet | Not yet |
|
| 312 |
+
| Cost to run | Free (local) | ~$5-20 GPT-4o calls | ~$5-15 |
|
| 313 |
+
| Smriti score | 0.117 (June 22, local, untuned) | Not run | Not run |
|
| 314 |
+
|
| 315 |
+
---
|
| 316 |
+
|
| 317 |
+
## 11. File Index
|
| 318 |
+
|
| 319 |
+
```
|
| 320 |
+
smriti/
|
| 321 |
+
├── benchmark_adapter.py # Adapter: harness -> Smriti live API
|
| 322 |
+
├── benchmark/
|
| 323 |
+
│ ├── smriti_service.py # Local benchmark service (ChromaDB + ST) v2
|
| 324 |
+
│ ├── run_eval.py # Eval runner
|
| 325 |
+
│ ├── requirements.txt # chromadb, sentence-transformers, fastapi, uvicorn
|
| 326 |
+
│ └── Dockerfile
|
| 327 |
+
precisionMemBench/
|
| 328 |
+
└── test-results/
|
| 329 |
+
├── retrieval-report-smriti.json # Smriti run (June 22 2026, local service)
|
| 330 |
+
└── baseline/ # Competitor baseline reports
|
| 331 |
+
├── retrieval-report-mem0.json
|
| 332 |
+
├── retrieval-report-zep.json
|
| 333 |
+
├── retrieval-report-tenure.json
|
| 334 |
+
└── ... (13 other systems)
|
| 335 |
+
docs/
|
| 336 |
+
└── precision-mem-bench.md # This file
|
| 337 |
+
```
|
| 338 |
+
|
| 339 |
+
---
|
| 340 |
+
|
| 341 |
+
*Last updated: July 2026*
|
mcp/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Smriti MCP Server
|
| 2 |
+
|
| 3 |
+
> **Model Context Protocol server for Smriti — Temporal AI Memory**
|
| 4 |
+
>
|
| 5 |
+
> Give any MCP-compatible AI host (Claude, Cursor, VS Code, Windsurf) structured temporal memory powered by S-V-O causal event tuples.
|
| 6 |
+
|
| 7 |
+
---
|
| 8 |
+
|
| 9 |
+
## Quick Start
|
| 10 |
+
|
| 11 |
+
### 1. Install dependencies
|
| 12 |
+
|
| 13 |
+
```bash
|
| 14 |
+
cd smriti
|
| 15 |
+
pip install -r mcp/requirements.txt
|
| 16 |
+
```
|
| 17 |
+
|
| 18 |
+
### 2. Set your API key
|
| 19 |
+
|
| 20 |
+
```bash
|
| 21 |
+
# Get a free API key at: https://smriti-kaal.vercel.app
|
| 22 |
+
export SMRITI_API_KEY="chrn_your_api_key_here"
|
| 23 |
+
```
|
| 24 |
+
|
| 25 |
+
### 3. Add to your MCP host
|
| 26 |
+
|
| 27 |
+
**Claude Desktop** — Edit `claude_desktop_config.json`:
|
| 28 |
+
|
| 29 |
+
```json
|
| 30 |
+
{
|
| 31 |
+
"mcpServers": {
|
| 32 |
+
"smriti": {
|
| 33 |
+
"command": "python",
|
| 34 |
+
"args": ["-m", "smriti.mcp"],
|
| 35 |
+
"cwd": "C:/path/to/smriti",
|
| 36 |
+
"env": {
|
| 37 |
+
"SMRITI_API_KEY": "chrn_your_api_key_here"
|
| 38 |
+
}
|
| 39 |
+
}
|
| 40 |
+
}
|
| 41 |
+
}
|
| 42 |
+
```
|
| 43 |
+
|
| 44 |
+
**Cursor** — Edit `.cursor/mcp.json` in your project root:
|
| 45 |
+
|
| 46 |
+
```json
|
| 47 |
+
{
|
| 48 |
+
"mcpServers": {
|
| 49 |
+
"smriti": {
|
| 50 |
+
"command": "python",
|
| 51 |
+
"args": ["-m", "smriti.mcp"],
|
| 52 |
+
"cwd": "C:/path/to/smriti",
|
| 53 |
+
"env": {
|
| 54 |
+
"SMRITI_API_KEY": "chrn_your_api_key_here",
|
| 55 |
+
"SMRITI_SOURCE_ID": "my-project"
|
| 56 |
+
}
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
### 4. Test with MCP Inspector
|
| 63 |
+
|
| 64 |
+
```bash
|
| 65 |
+
npx @modelcontextprotocol/inspector python -m smriti.mcp
|
| 66 |
+
```
|
| 67 |
+
|
| 68 |
+
---
|
| 69 |
+
|
| 70 |
+
## Tools
|
| 71 |
+
|
| 72 |
+
| Tool | Purpose | Key Args |
|
| 73 |
+
|------|---------|----------|
|
| 74 |
+
| `smriti_remember` | Store new memories (text → S-V-O extraction) | `text`, `source_id`, `scope`, `timestamp` |
|
| 75 |
+
| `smriti_recall` | Search memories (semantic + temporal + entity) | `query`, `max_results`, `time_range_start/end` |
|
| 76 |
+
| `smriti_timeline` | Chronological event timeline | `time_range_start`, `time_range_end`, `scope` |
|
| 77 |
+
| `smriti_forget` | Find memories to supersede | `query`, `scope` |
|
| 78 |
+
| `smriti_health` | Service health check | — |
|
| 79 |
+
| `smriti_usage` | API usage stats & tier limits | — |
|
| 80 |
+
|
| 81 |
+
### `smriti_remember`
|
| 82 |
+
|
| 83 |
+
Stores a new memory. Text is automatically decomposed into **S-V-O (Subject-Verb-Object) causal event tuples**.
|
| 84 |
+
|
| 85 |
+
```
|
| 86 |
+
Input: "Alice quit her job at Google because she got an offer from OpenAI"
|
| 87 |
+
Output: [Alice] [quit] [her job at Google]
|
| 88 |
+
[Alice] [got] [an offer from OpenAI]
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
Old facts are **automatically superseded** when contradicted — history is preserved, never deleted.
|
| 92 |
+
|
| 93 |
+
### `smriti_recall`
|
| 94 |
+
|
| 95 |
+
Searches memories using a **3-phase hybrid retrieval pipeline**:
|
| 96 |
+
|
| 97 |
+
1. **Semantic search** — pgvector cosine similarity (meaning-based)
|
| 98 |
+
2. **Temporal filtering** — PostgreSQL time range queries
|
| 99 |
+
3. **Entity multi-hop** — follows entity connections across memories
|
| 100 |
+
|
| 101 |
+
### `smriti_timeline`
|
| 102 |
+
|
| 103 |
+
Optimized for temporal queries. Low semantic weight, high temporal weight.
|
| 104 |
+
|
| 105 |
+
### `smriti_forget`
|
| 106 |
+
|
| 107 |
+
Finds matching memories and guides you through supersession. Smriti **never deletes** — it marks old facts as superseded and stores corrected facts.
|
| 108 |
+
|
| 109 |
+
---
|
| 110 |
+
|
| 111 |
+
## Resources
|
| 112 |
+
|
| 113 |
+
| URI | Description |
|
| 114 |
+
|-----|-------------|
|
| 115 |
+
| `smriti://status` | Live service health and memory counts |
|
| 116 |
+
| `smriti://usage` | Current API usage stats and tier limits |
|
| 117 |
+
| `smriti://config` | MCP server configuration (non-sensitive) |
|
| 118 |
+
|
| 119 |
+
---
|
| 120 |
+
|
| 121 |
+
## Prompts
|
| 122 |
+
|
| 123 |
+
| Name | Purpose |
|
| 124 |
+
|------|---------|
|
| 125 |
+
| `memory-chat` | System prompt for memory-augmented conversations |
|
| 126 |
+
| `daily-recap` | Template for timeline-based daily/weekly recap |
|
| 127 |
+
| `knowledge-extraction` | Bulk knowledge ingestion from documents |
|
| 128 |
+
|
| 129 |
+
---
|
| 130 |
+
|
| 131 |
+
## Configuration
|
| 132 |
+
|
| 133 |
+
All settings via environment variables:
|
| 134 |
+
|
| 135 |
+
| Variable | Default | Description |
|
| 136 |
+
|----------|---------|-------------|
|
| 137 |
+
| `SMRITI_API_KEY` | *(required)* | Your Smriti API key (`chrn_...`) |
|
| 138 |
+
| `SMRITI_BASE_URL` | `https://spy9191-chronos-api-backend.hf.space` | API base URL |
|
| 139 |
+
| `SMRITI_SOURCE_ID` | `mcp-client` | Default source namespace |
|
| 140 |
+
| `SMRITI_SCOPE` | `default` | Default memory scope |
|
| 141 |
+
| `SMRITI_MAX_RESULTS` | `20` | Default max results for queries |
|
| 142 |
+
| `SMRITI_PARSE_SVO` | `true` | Enable S-V-O extraction by default |
|
| 143 |
+
| `SMRITI_TIMEOUT` | `30.0` | HTTP timeout (seconds) |
|
| 144 |
+
| `SMRITI_MAX_RETRIES` | `3` | Max HTTP retries for transient errors |
|
| 145 |
+
|
| 146 |
+
---
|
| 147 |
+
|
| 148 |
+
## Transport
|
| 149 |
+
|
| 150 |
+
```bash
|
| 151 |
+
# stdio (default) — for Claude Desktop, Cursor, local use
|
| 152 |
+
python -m smriti.mcp
|
| 153 |
+
|
| 154 |
+
# SSE — for remote/multi-client access
|
| 155 |
+
python -m smriti.mcp --transport sse --port 8080
|
| 156 |
+
```
|
| 157 |
+
|
| 158 |
+
---
|
| 159 |
+
|
| 160 |
+
## Architecture
|
| 161 |
+
|
| 162 |
+
```
|
| 163 |
+
┌────────────────────────────────────────┐
|
| 164 |
+
│ MCP Host (Claude / Cursor / VS Code) │
|
| 165 |
+
│ ┌──────────────────────────────────┐ │
|
| 166 |
+
│ │ MCP Client │ │
|
| 167 |
+
│ └──────────────┬───────────────────┘ │
|
| 168 |
+
└─────────────────┼──────────────────────┘
|
| 169 |
+
│ JSON-RPC 2.0 (stdio)
|
| 170 |
+
│
|
| 171 |
+
┌─────────────────┼─���────────────────────┐
|
| 172 |
+
│ Smriti MCP Server (this package) │
|
| 173 |
+
│ ┌──────────────┴───────────────────┐ │
|
| 174 |
+
│ │ FastMCP (server.py) │ │
|
| 175 |
+
│ │ 6 Tools │ 3 Resources │ 3 Prompts │
|
| 176 |
+
│ └──────────────┬───────────────────┘ │
|
| 177 |
+
│ ┌──────────────┴───────────────────┐ │
|
| 178 |
+
│ │ SmritiClient (client.py) │ │
|
| 179 |
+
│ │ HTTP client → Smriti REST API │ │
|
| 180 |
+
│ └──────────────┬───────────────────┘ │
|
| 181 |
+
└─────────────────┼──────────────────────┘
|
| 182 |
+
│ HTTPS
|
| 183 |
+
│
|
| 184 |
+
┌─────────────────┼──────────────────────┐
|
| 185 |
+
│ Smriti API (HuggingFace Spaces) │
|
| 186 |
+
│ FastAPI + PostgreSQL + pgvector │
|
| 187 |
+
│ SVO Parser + Supersession Engine │
|
| 188 |
+
└────────────────────────────────────────┘
|
| 189 |
+
```
|
| 190 |
+
|
| 191 |
+
---
|
| 192 |
+
|
| 193 |
+
## Testing
|
| 194 |
+
|
| 195 |
+
```bash
|
| 196 |
+
# Install test dependencies
|
| 197 |
+
pip install pytest pytest-asyncio
|
| 198 |
+
|
| 199 |
+
# Run all MCP tests
|
| 200 |
+
python -m pytest tests/test_mcp_client.py tests/test_mcp_server.py -v
|
| 201 |
+
```
|
| 202 |
+
|
| 203 |
+
---
|
| 204 |
+
|
| 205 |
+
## Example Conversation
|
| 206 |
+
|
| 207 |
+
Once connected, the AI naturally uses Smriti memory:
|
| 208 |
+
|
| 209 |
+
> **You:** I just switched our backend from Express to FastAPI
|
| 210 |
+
>
|
| 211 |
+
> **AI:** *(calls `smriti_remember` with "User switched backend from Express to FastAPI")*
|
| 212 |
+
> Got it! I've noted the backend migration from Express to FastAPI.
|
| 213 |
+
>
|
| 214 |
+
> **You:** What tech stack are we using?
|
| 215 |
+
>
|
| 216 |
+
> **AI:** *(calls `smriti_recall` with "tech stack")*
|
| 217 |
+
> Based on your memory: You recently switched the backend to **FastAPI** (previously Express). [Other recalled context...]
|
| 218 |
+
>
|
| 219 |
+
> **You:** What happened this week?
|
| 220 |
+
>
|
| 221 |
+
> **AI:** *(calls `smriti_timeline` for last 7 days)*
|
| 222 |
+
> Here's your weekly recap:
|
| 223 |
+
> - Jul 15: Switched backend from Express to FastAPI
|
| 224 |
+
> - Jul 16: ...
|
| 225 |
+
|
| 226 |
+
---
|
| 227 |
+
|
| 228 |
+
## File Structure
|
| 229 |
+
|
| 230 |
+
```
|
| 231 |
+
smriti/mcp/
|
| 232 |
+
├── __init__.py # Package metadata
|
| 233 |
+
├── __main__.py # CLI entry point (python -m smriti.mcp)
|
| 234 |
+
├── server.py # FastMCP server — tools, resources, prompts
|
| 235 |
+
├── client.py # HTTP client for Smriti REST API
|
| 236 |
+
├── config.py # Environment variable configuration
|
| 237 |
+
├── requirements.txt # Python dependencies
|
| 238 |
+
├── README.md # This file
|
| 239 |
+
├── claude_desktop_config.json # Example Claude Desktop config
|
| 240 |
+
└── cursor_config.json # Example Cursor config
|
| 241 |
+
```
|
mcp/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smriti MCP Server
|
| 3 |
+
=================
|
| 4 |
+
Model Context Protocol server for Smriti — Temporal AI Memory.
|
| 5 |
+
Gives any MCP-compatible AI host (Claude, Cursor, VS Code, etc.)
|
| 6 |
+
structured temporal memory powered by S-V-O causal event tuples.
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
__version__ = "0.1.0"
|
| 10 |
+
__author__ = "Smriti / KAAL"
|
mcp/__main__.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smriti MCP — CLI Entry Point
|
| 3 |
+
=============================
|
| 4 |
+
Run with: python -m smriti.mcp
|
| 5 |
+
|
| 6 |
+
Supports:
|
| 7 |
+
python -m smriti.mcp → stdio transport (default)
|
| 8 |
+
python -m smriti.mcp --transport sse → SSE transport
|
| 9 |
+
python -m smriti.mcp --port 3000 → Custom SSE port
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
import argparse
|
| 15 |
+
import sys
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def main() -> None:
|
| 19 |
+
parser = argparse.ArgumentParser(
|
| 20 |
+
prog="smriti-mcp",
|
| 21 |
+
description="Smriti MCP Server — Temporal AI Memory for any MCP host",
|
| 22 |
+
)
|
| 23 |
+
parser.add_argument(
|
| 24 |
+
"--transport",
|
| 25 |
+
choices=["stdio", "sse"],
|
| 26 |
+
default="stdio",
|
| 27 |
+
help="Transport protocol (default: stdio)",
|
| 28 |
+
)
|
| 29 |
+
parser.add_argument(
|
| 30 |
+
"--port",
|
| 31 |
+
type=int,
|
| 32 |
+
default=8080,
|
| 33 |
+
help="Port for SSE transport (default: 8080)",
|
| 34 |
+
)
|
| 35 |
+
|
| 36 |
+
args = parser.parse_args()
|
| 37 |
+
|
| 38 |
+
# Import here to avoid loading everything just for --help
|
| 39 |
+
from .server import run_server
|
| 40 |
+
|
| 41 |
+
try:
|
| 42 |
+
run_server(transport=args.transport, port=args.port)
|
| 43 |
+
except KeyboardInterrupt:
|
| 44 |
+
print("\nSmriti MCP server stopped.", file=sys.stderr)
|
| 45 |
+
except ValueError as e:
|
| 46 |
+
print(f"Configuration error: {e}", file=sys.stderr)
|
| 47 |
+
sys.exit(1)
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
if __name__ == "__main__":
|
| 51 |
+
main()
|
mcp/claude_desktop_config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mcpServers": {
|
| 3 |
+
"smriti": {
|
| 4 |
+
"command": "python",
|
| 5 |
+
"args": ["-m", "smriti.mcp"],
|
| 6 |
+
"cwd": "C:/path/to/smriti",
|
| 7 |
+
"env": {
|
| 8 |
+
"SMRITI_API_KEY": "chrn_your_api_key_here",
|
| 9 |
+
"SMRITI_SOURCE_ID": "my-project",
|
| 10 |
+
"SMRITI_SCOPE": "default"
|
| 11 |
+
}
|
| 12 |
+
}
|
| 13 |
+
}
|
| 14 |
+
}
|
mcp/client.py
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smriti MCP — HTTP Client
|
| 3 |
+
=========================
|
| 4 |
+
Async HTTP client that wraps the Smriti (KAAL) REST API.
|
| 5 |
+
Handles authentication, retries, timeouts, and error mapping.
|
| 6 |
+
|
| 7 |
+
All methods return plain Python dicts — the MCP server layer
|
| 8 |
+
converts them to tool responses.
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from __future__ import annotations
|
| 12 |
+
|
| 13 |
+
import logging
|
| 14 |
+
import sys
|
| 15 |
+
from datetime import datetime
|
| 16 |
+
from typing import Any, Optional
|
| 17 |
+
|
| 18 |
+
import httpx
|
| 19 |
+
|
| 20 |
+
from .config import SmritiMCPConfig
|
| 21 |
+
|
| 22 |
+
# All logging goes to stderr (MCP requirement — stdout is the JSON-RPC channel)
|
| 23 |
+
logger = logging.getLogger("smriti.mcp.client")
|
| 24 |
+
handler = logging.StreamHandler(sys.stderr)
|
| 25 |
+
handler.setFormatter(logging.Formatter("%(asctime)s | %(name)s | %(levelname)s | %(message)s"))
|
| 26 |
+
logger.addHandler(handler)
|
| 27 |
+
logger.setLevel(logging.INFO)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class SmritiAPIError(Exception):
|
| 31 |
+
"""Raised when the Smriti API returns an error response."""
|
| 32 |
+
|
| 33 |
+
def __init__(self, status_code: int, detail: str):
|
| 34 |
+
self.status_code = status_code
|
| 35 |
+
self.detail = detail
|
| 36 |
+
super().__init__(f"Smriti API error {status_code}: {detail}")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
class SmritiClient:
|
| 40 |
+
"""
|
| 41 |
+
Async HTTP client for the Smriti (KAAL) temporal memory API.
|
| 42 |
+
|
| 43 |
+
Usage:
|
| 44 |
+
client = SmritiClient(config)
|
| 45 |
+
await client.connect()
|
| 46 |
+
result = await client.ingest("User started learning Rust")
|
| 47 |
+
await client.close()
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
def __init__(self, config: SmritiMCPConfig):
|
| 51 |
+
self.config = config
|
| 52 |
+
self._client: Optional[httpx.AsyncClient] = None
|
| 53 |
+
|
| 54 |
+
async def connect(self) -> None:
|
| 55 |
+
"""Initialize the HTTP client with auth headers."""
|
| 56 |
+
self._client = httpx.AsyncClient(
|
| 57 |
+
base_url=self.config.base_url.rstrip("/"),
|
| 58 |
+
headers={
|
| 59 |
+
"X-API-Key": self.config.api_key,
|
| 60 |
+
"Content-Type": "application/json",
|
| 61 |
+
"User-Agent": "smriti-mcp/0.1.0",
|
| 62 |
+
},
|
| 63 |
+
timeout=httpx.Timeout(self.config.timeout_seconds),
|
| 64 |
+
)
|
| 65 |
+
logger.info(f"SmritiClient connected to {self.config.base_url}")
|
| 66 |
+
|
| 67 |
+
async def close(self) -> None:
|
| 68 |
+
"""Close the HTTP client."""
|
| 69 |
+
if self._client:
|
| 70 |
+
await self._client.aclose()
|
| 71 |
+
self._client = None
|
| 72 |
+
logger.info("SmritiClient disconnected")
|
| 73 |
+
|
| 74 |
+
# ── Internal helpers ──────────────────────────────────────────────────────
|
| 75 |
+
|
| 76 |
+
def _ensure_connected(self) -> httpx.AsyncClient:
|
| 77 |
+
if self._client is None:
|
| 78 |
+
raise RuntimeError("SmritiClient not connected. Call connect() first.")
|
| 79 |
+
return self._client
|
| 80 |
+
|
| 81 |
+
async def _request(
|
| 82 |
+
self,
|
| 83 |
+
method: str,
|
| 84 |
+
path: str,
|
| 85 |
+
json: dict | None = None,
|
| 86 |
+
params: dict | None = None,
|
| 87 |
+
) -> dict[str, Any]:
|
| 88 |
+
"""Make an HTTP request with retry logic."""
|
| 89 |
+
client = self._ensure_connected()
|
| 90 |
+
last_error: Exception | None = None
|
| 91 |
+
|
| 92 |
+
for attempt in range(1, self.config.max_retries + 1):
|
| 93 |
+
try:
|
| 94 |
+
response = await client.request(
|
| 95 |
+
method=method,
|
| 96 |
+
url=path,
|
| 97 |
+
json=json,
|
| 98 |
+
params=params,
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
if response.status_code == 200:
|
| 102 |
+
return response.json()
|
| 103 |
+
|
| 104 |
+
# Non-retryable errors
|
| 105 |
+
if response.status_code in (400, 401, 403, 404, 409, 422):
|
| 106 |
+
detail = self._extract_error_detail(response)
|
| 107 |
+
raise SmritiAPIError(response.status_code, detail)
|
| 108 |
+
|
| 109 |
+
# Retryable errors (429, 500, 502, 503, 504)
|
| 110 |
+
detail = self._extract_error_detail(response)
|
| 111 |
+
last_error = SmritiAPIError(response.status_code, detail)
|
| 112 |
+
logger.warning(
|
| 113 |
+
f"Retryable error (attempt {attempt}/{self.config.max_retries}): "
|
| 114 |
+
f"{response.status_code} {detail}"
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
except httpx.TimeoutException as e:
|
| 118 |
+
last_error = e
|
| 119 |
+
logger.warning(
|
| 120 |
+
f"Timeout (attempt {attempt}/{self.config.max_retries}): {e}"
|
| 121 |
+
)
|
| 122 |
+
except httpx.ConnectError as e:
|
| 123 |
+
last_error = e
|
| 124 |
+
logger.warning(
|
| 125 |
+
f"Connection error (attempt {attempt}/{self.config.max_retries}): {e}"
|
| 126 |
+
)
|
| 127 |
+
except SmritiAPIError:
|
| 128 |
+
raise # Non-retryable, already raised
|
| 129 |
+
|
| 130 |
+
# All retries exhausted
|
| 131 |
+
raise SmritiAPIError(
|
| 132 |
+
503,
|
| 133 |
+
f"All {self.config.max_retries} retries exhausted. Last error: {last_error}",
|
| 134 |
+
)
|
| 135 |
+
|
| 136 |
+
@staticmethod
|
| 137 |
+
def _extract_error_detail(response: httpx.Response) -> str:
|
| 138 |
+
"""Extract a human-readable error message from an API response."""
|
| 139 |
+
try:
|
| 140 |
+
data = response.json()
|
| 141 |
+
return data.get("detail", str(data))
|
| 142 |
+
except Exception:
|
| 143 |
+
return response.text[:500]
|
| 144 |
+
|
| 145 |
+
# ── Public API methods ────────────────────────────────────────────────────
|
| 146 |
+
|
| 147 |
+
async def ingest(
|
| 148 |
+
self,
|
| 149 |
+
text: str,
|
| 150 |
+
source_id: str | None = None,
|
| 151 |
+
parse_svo: bool = True,
|
| 152 |
+
timestamp: str | None = None,
|
| 153 |
+
scope: str | None = None,
|
| 154 |
+
metadata: dict[str, Any] | None = None,
|
| 155 |
+
) -> dict[str, Any]:
|
| 156 |
+
"""
|
| 157 |
+
Ingest text into Smriti temporal memory.
|
| 158 |
+
|
| 159 |
+
The text is automatically decomposed into S-V-O (Subject-Verb-Object)
|
| 160 |
+
causal event tuples and stored with bi-temporal validity.
|
| 161 |
+
|
| 162 |
+
Args:
|
| 163 |
+
text: The memory to store (e.g., "User switched from React to Vue")
|
| 164 |
+
source_id: Namespace label for this memory source
|
| 165 |
+
parse_svo: Whether to extract S-V-O tuples (uses LLM)
|
| 166 |
+
timestamp: ISO timestamp override (default: now)
|
| 167 |
+
scope: Logical namespace (e.g., "work", "personal")
|
| 168 |
+
metadata: Additional metadata to attach to events
|
| 169 |
+
|
| 170 |
+
Returns:
|
| 171 |
+
Dict with ingested_count, event_ids, svo_tuples, turn_ids
|
| 172 |
+
"""
|
| 173 |
+
event: dict[str, Any] = {"text": text}
|
| 174 |
+
if timestamp:
|
| 175 |
+
event["timestamp"] = timestamp
|
| 176 |
+
if metadata:
|
| 177 |
+
event["metadata"] = metadata
|
| 178 |
+
if scope:
|
| 179 |
+
event["scope"] = scope
|
| 180 |
+
|
| 181 |
+
payload = {
|
| 182 |
+
"source_id": source_id or self.config.source_id,
|
| 183 |
+
"events": [event],
|
| 184 |
+
"parse_svo": parse_svo,
|
| 185 |
+
}
|
| 186 |
+
if scope:
|
| 187 |
+
payload["scope"] = scope
|
| 188 |
+
|
| 189 |
+
return await self._request("POST", "/ingest", json=payload)
|
| 190 |
+
|
| 191 |
+
async def query(
|
| 192 |
+
self,
|
| 193 |
+
query: str,
|
| 194 |
+
max_results: int | None = None,
|
| 195 |
+
source_ids: list[str] | None = None,
|
| 196 |
+
time_range_start: str | None = None,
|
| 197 |
+
time_range_end: str | None = None,
|
| 198 |
+
scope: str | None = None,
|
| 199 |
+
semantic_weight: float = 0.5,
|
| 200 |
+
) -> dict[str, Any]:
|
| 201 |
+
"""
|
| 202 |
+
Search Smriti temporal memory with hybrid retrieval.
|
| 203 |
+
|
| 204 |
+
Uses a 3-phase pipeline:
|
| 205 |
+
1. Semantic search via pgvector (fuzzy recall)
|
| 206 |
+
2. Temporal filtering via PostgreSQL (deterministic time ranges)
|
| 207 |
+
3. Entity multi-hop (capitalized entity matching)
|
| 208 |
+
|
| 209 |
+
Args:
|
| 210 |
+
query: Natural language search query
|
| 211 |
+
max_results: Maximum results to return
|
| 212 |
+
source_ids: Filter by specific source namespaces
|
| 213 |
+
time_range_start: ISO timestamp for range start
|
| 214 |
+
time_range_end: ISO timestamp for range end
|
| 215 |
+
scope: Restrict to a specific scope
|
| 216 |
+
semantic_weight: Balance between semantic (1.0) and temporal (0.0)
|
| 217 |
+
|
| 218 |
+
Returns:
|
| 219 |
+
Dict with results (list of events), total_found, query_time_ms
|
| 220 |
+
"""
|
| 221 |
+
payload: dict[str, Any] = {
|
| 222 |
+
"query": query,
|
| 223 |
+
"max_results": max_results or self.config.max_results,
|
| 224 |
+
"semantic_weight": semantic_weight,
|
| 225 |
+
}
|
| 226 |
+
if source_ids:
|
| 227 |
+
payload["source_ids"] = source_ids
|
| 228 |
+
if scope:
|
| 229 |
+
payload["scope"] = scope
|
| 230 |
+
|
| 231 |
+
if time_range_start or time_range_end:
|
| 232 |
+
time_range: dict[str, str] = {}
|
| 233 |
+
if time_range_start:
|
| 234 |
+
time_range["start"] = time_range_start
|
| 235 |
+
if time_range_end:
|
| 236 |
+
time_range["end"] = time_range_end
|
| 237 |
+
payload["time_range"] = time_range
|
| 238 |
+
|
| 239 |
+
return await self._request("POST", "/query", json=payload)
|
| 240 |
+
|
| 241 |
+
async def health(self) -> dict[str, Any]:
|
| 242 |
+
"""
|
| 243 |
+
Check Smriti API health status.
|
| 244 |
+
|
| 245 |
+
Returns:
|
| 246 |
+
Dict with status, postgres_events count, pgvector_embeddings count
|
| 247 |
+
"""
|
| 248 |
+
return await self._request("GET", "/health")
|
| 249 |
+
|
| 250 |
+
async def usage(self) -> dict[str, Any]:
|
| 251 |
+
"""
|
| 252 |
+
Get current API usage statistics and tier limits.
|
| 253 |
+
|
| 254 |
+
Returns:
|
| 255 |
+
Dict with tier, usage (events/orchestration/connectors), limits
|
| 256 |
+
"""
|
| 257 |
+
return await self._request("GET", "/billing/usage")
|
| 258 |
+
|
| 259 |
+
async def list_connectors(self) -> list[dict[str, Any]]:
|
| 260 |
+
"""
|
| 261 |
+
List all registered SaaS tool connectors.
|
| 262 |
+
|
| 263 |
+
Returns:
|
| 264 |
+
List of connector dicts with id, name, description, endpoints_count
|
| 265 |
+
"""
|
| 266 |
+
result = await self._request("GET", "/connectors")
|
| 267 |
+
# The API returns a list directly, not wrapped in an object
|
| 268 |
+
return result if isinstance(result, list) else []
|
| 269 |
+
|
| 270 |
+
async def root_health(self) -> dict[str, Any]:
|
| 271 |
+
"""
|
| 272 |
+
Basic health check (GET /).
|
| 273 |
+
|
| 274 |
+
Returns:
|
| 275 |
+
Dict with service name, version, status
|
| 276 |
+
"""
|
| 277 |
+
return await self._request("GET", "/")
|
mcp/config.py
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smriti MCP — Configuration
|
| 3 |
+
==========================
|
| 4 |
+
All settings loaded from environment variables.
|
| 5 |
+
Remote API mode only (HTTP client to deployed Smriti API).
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import os
|
| 11 |
+
import sys
|
| 12 |
+
from dataclasses import dataclass, field
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@dataclass(frozen=True)
|
| 16 |
+
class SmritiMCPConfig:
|
| 17 |
+
"""Immutable configuration for the Smriti MCP server."""
|
| 18 |
+
|
| 19 |
+
# ── Required ──────────────────────────────────────────────────────────────
|
| 20 |
+
api_key: str = field(default="")
|
| 21 |
+
|
| 22 |
+
# ── API Connection ────────────────────────────────────────────────────────
|
| 23 |
+
base_url: str = field(default="https://spy9191-chronos-api-backend.hf.space")
|
| 24 |
+
|
| 25 |
+
# ── Defaults ──────────────────────────────────────────────────────────────
|
| 26 |
+
source_id: str = field(default="mcp-client")
|
| 27 |
+
scope: str = field(default="default")
|
| 28 |
+
max_results: int = field(default=20)
|
| 29 |
+
parse_svo: bool = field(default=True)
|
| 30 |
+
|
| 31 |
+
# ── HTTP Client ───────────────────────────────────────────────────────────
|
| 32 |
+
timeout_seconds: float = field(default=30.0)
|
| 33 |
+
max_retries: int = field(default=3)
|
| 34 |
+
|
| 35 |
+
def validate(self) -> None:
|
| 36 |
+
"""Validate that all required config is present."""
|
| 37 |
+
if not self.api_key:
|
| 38 |
+
print(
|
| 39 |
+
"ERROR: SMRITI_API_KEY environment variable is required.\n"
|
| 40 |
+
"Get your free API key at: https://smriti-kaal.vercel.app\n"
|
| 41 |
+
"Then set it: export SMRITI_API_KEY=chrn_your_key_here",
|
| 42 |
+
file=sys.stderr,
|
| 43 |
+
)
|
| 44 |
+
raise ValueError("SMRITI_API_KEY is required but not set.")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def load_config() -> SmritiMCPConfig:
|
| 48 |
+
"""Load configuration from environment variables."""
|
| 49 |
+
config = SmritiMCPConfig(
|
| 50 |
+
api_key=os.getenv("SMRITI_API_KEY", ""),
|
| 51 |
+
base_url=os.getenv(
|
| 52 |
+
"SMRITI_BASE_URL",
|
| 53 |
+
"https://spy9191-chronos-api-backend.hf.space",
|
| 54 |
+
),
|
| 55 |
+
source_id=os.getenv("SMRITI_SOURCE_ID", "mcp-client"),
|
| 56 |
+
scope=os.getenv("SMRITI_SCOPE", "default"),
|
| 57 |
+
max_results=int(os.getenv("SMRITI_MAX_RESULTS", "20")),
|
| 58 |
+
parse_svo=os.getenv("SMRITI_PARSE_SVO", "true").lower() in ("true", "1", "yes"),
|
| 59 |
+
timeout_seconds=float(os.getenv("SMRITI_TIMEOUT", "30.0")),
|
| 60 |
+
max_retries=int(os.getenv("SMRITI_MAX_RETRIES", "3")),
|
| 61 |
+
)
|
| 62 |
+
return config
|
mcp/cursor_config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mcpServers": {
|
| 3 |
+
"smriti": {
|
| 4 |
+
"command": "python",
|
| 5 |
+
"args": ["-m", "smriti.mcp"],
|
| 6 |
+
"cwd": "C:/path/to/smriti",
|
| 7 |
+
"env": {
|
| 8 |
+
"SMRITI_API_KEY": "chrn_your_api_key_here",
|
| 9 |
+
"SMRITI_SOURCE_ID": "cursor-workspace",
|
| 10 |
+
"SMRITI_SCOPE": "code"
|
| 11 |
+
}
|
| 12 |
+
}
|
| 13 |
+
}
|
| 14 |
+
}
|
mcp/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Smriti MCP Server — Dependencies
|
| 2 |
+
mcp[cli]>=1.0.0
|
| 3 |
+
httpx>=0.27.0
|
| 4 |
+
python-dotenv>=1.0.0
|
mcp/server.py
ADDED
|
@@ -0,0 +1,633 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smriti MCP Server
|
| 3 |
+
=================
|
| 4 |
+
Model Context Protocol server for Smriti — the Temporal AI Memory Layer.
|
| 5 |
+
|
| 6 |
+
Exposes Smriti's S-V-O (Subject-Verb-Object) causal memory as MCP primitives:
|
| 7 |
+
• 6 Tools — remember, recall, timeline, forget, health, usage
|
| 8 |
+
• 3 Resources — status, usage, config
|
| 9 |
+
• 3 Prompts — memory-chat, daily-recap, knowledge-extraction
|
| 10 |
+
|
| 11 |
+
Runs over stdio (default) or SSE transport.
|
| 12 |
+
Connect to any MCP host: Claude Desktop, Cursor, VS Code, Windsurf, etc.
|
| 13 |
+
|
| 14 |
+
Usage:
|
| 15 |
+
python -m smriti.mcp # stdio (default)
|
| 16 |
+
python -m smriti.mcp --transport sse # SSE on port 8080
|
| 17 |
+
"""
|
| 18 |
+
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import json
|
| 22 |
+
import logging
|
| 23 |
+
import sys
|
| 24 |
+
from contextlib import asynccontextmanager
|
| 25 |
+
from datetime import datetime, timezone
|
| 26 |
+
from typing import Any
|
| 27 |
+
|
| 28 |
+
from mcp.server.fastmcp import FastMCP, Context
|
| 29 |
+
|
| 30 |
+
from .config import SmritiMCPConfig, load_config
|
| 31 |
+
from .client import SmritiClient, SmritiAPIError
|
| 32 |
+
|
| 33 |
+
# ── Logging (stderr only — stdout is the JSON-RPC channel) ────────────────────
|
| 34 |
+
logger = logging.getLogger("smriti.mcp.server")
|
| 35 |
+
handler = logging.StreamHandler(sys.stderr)
|
| 36 |
+
handler.setFormatter(
|
| 37 |
+
logging.Formatter("%(asctime)s | %(name)s | %(levelname)s | %(message)s")
|
| 38 |
+
)
|
| 39 |
+
logger.addHandler(handler)
|
| 40 |
+
logger.setLevel(logging.INFO)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
# ── Lifespan — initialize/teardown the HTTP client ────────────────────────────
|
| 44 |
+
|
| 45 |
+
@asynccontextmanager
|
| 46 |
+
async def smriti_lifespan(server):
|
| 47 |
+
"""Manage SmritiClient lifecycle across the MCP server's lifetime."""
|
| 48 |
+
config = load_config()
|
| 49 |
+
config.validate()
|
| 50 |
+
|
| 51 |
+
client = SmritiClient(config)
|
| 52 |
+
await client.connect()
|
| 53 |
+
|
| 54 |
+
logger.info("Smriti MCP server initialized — temporal memory online")
|
| 55 |
+
|
| 56 |
+
yield {"client": client, "config": config}
|
| 57 |
+
|
| 58 |
+
await client.close()
|
| 59 |
+
logger.info("Smriti MCP server shut down")
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
# ── FastMCP Server ────────────────────────────────────────────────────────────
|
| 63 |
+
|
| 64 |
+
mcp_server = FastMCP(
|
| 65 |
+
"smriti",
|
| 66 |
+
version="0.1.0",
|
| 67 |
+
description=(
|
| 68 |
+
"Temporal AI Memory — Smriti gives your AI causal, structured memory. "
|
| 69 |
+
"Remember what happened, when, and why — powered by S-V-O event tuples "
|
| 70 |
+
"with bi-temporal validity on PostgreSQL + pgvector."
|
| 71 |
+
),
|
| 72 |
+
lifespan=smriti_lifespan,
|
| 73 |
+
)
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
# ═══════════════════════════════════════════════════════════════════════════════
|
| 77 |
+
# TOOLS — Model-controlled functions the LLM can invoke
|
| 78 |
+
# ═══════════════════════════════════════════════════════════════════════════════
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
@mcp_server.tool()
|
| 82 |
+
async def smriti_remember(
|
| 83 |
+
text: str,
|
| 84 |
+
source_id: str | None = None,
|
| 85 |
+
parse_svo: bool = True,
|
| 86 |
+
timestamp: str | None = None,
|
| 87 |
+
scope: str | None = None,
|
| 88 |
+
ctx: Context = None,
|
| 89 |
+
) -> str:
|
| 90 |
+
"""Store a new memory in Smriti's temporal memory system.
|
| 91 |
+
|
| 92 |
+
The text is automatically decomposed into S-V-O (Subject-Verb-Object)
|
| 93 |
+
causal event tuples. For example:
|
| 94 |
+
"Alice quit her job at Google because she got an offer from OpenAI"
|
| 95 |
+
becomes:
|
| 96 |
+
[Alice] [quit] [her job at Google]
|
| 97 |
+
[Alice] [got] [an offer from OpenAI]
|
| 98 |
+
|
| 99 |
+
Old facts are automatically superseded when contradicted by new ones.
|
| 100 |
+
History is preserved (never deleted), just marked as superseded.
|
| 101 |
+
|
| 102 |
+
Args:
|
| 103 |
+
text: The memory to store. Can be a single fact, a paragraph,
|
| 104 |
+
or a full conversation snippet. Be descriptive.
|
| 105 |
+
source_id: Optional namespace label (e.g., "my-project", "work-notes").
|
| 106 |
+
Memories from different sources are kept separate.
|
| 107 |
+
parse_svo: Whether to extract structured S-V-O tuples (default: true).
|
| 108 |
+
Set to false for raw storage without decomposition.
|
| 109 |
+
timestamp: ISO 8601 timestamp of when this event occurred (e.g., "2026-07-15T10:30:00Z").
|
| 110 |
+
Defaults to current time if not provided.
|
| 111 |
+
scope: Logical namespace for this memory (e.g., "work", "personal", "project-x").
|
| 112 |
+
Useful for organizing memories by context.
|
| 113 |
+
"""
|
| 114 |
+
client: SmritiClient = ctx.request_context.lifespan_context["client"]
|
| 115 |
+
|
| 116 |
+
try:
|
| 117 |
+
result = await client.ingest(
|
| 118 |
+
text=text,
|
| 119 |
+
source_id=source_id,
|
| 120 |
+
parse_svo=parse_svo,
|
| 121 |
+
timestamp=timestamp,
|
| 122 |
+
scope=scope,
|
| 123 |
+
)
|
| 124 |
+
|
| 125 |
+
# Format a human-readable response
|
| 126 |
+
count = result.get("ingested_count", 0)
|
| 127 |
+
event_ids = result.get("event_ids", [])
|
| 128 |
+
svo_tuples = result.get("svo_tuples", [])
|
| 129 |
+
|
| 130 |
+
lines = [f"✅ Remembered {count} event(s)."]
|
| 131 |
+
|
| 132 |
+
if svo_tuples:
|
| 133 |
+
lines.append("\nExtracted S-V-O tuples:")
|
| 134 |
+
for svo in svo_tuples:
|
| 135 |
+
s = svo.get("subject", "?")
|
| 136 |
+
v = svo.get("verb", "?")
|
| 137 |
+
o = svo.get("object", "?")
|
| 138 |
+
conf = svo.get("confidence", 0)
|
| 139 |
+
lines.append(f" [{s}] [{v}] [{o}] (confidence: {conf:.0%})")
|
| 140 |
+
|
| 141 |
+
if event_ids:
|
| 142 |
+
lines.append(f"\nEvent IDs: {', '.join(event_ids[:5])}")
|
| 143 |
+
if len(event_ids) > 5:
|
| 144 |
+
lines.append(f" ... and {len(event_ids) - 5} more")
|
| 145 |
+
|
| 146 |
+
return "\n".join(lines)
|
| 147 |
+
|
| 148 |
+
except SmritiAPIError as e:
|
| 149 |
+
return f"❌ Failed to store memory: {e.detail}"
|
| 150 |
+
except Exception as e:
|
| 151 |
+
logger.error(f"smriti_remember error: {e}", exc_info=True)
|
| 152 |
+
return f"❌ Unexpected error storing memory: {str(e)}"
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
@mcp_server.tool()
|
| 156 |
+
async def smriti_recall(
|
| 157 |
+
query: str,
|
| 158 |
+
max_results: int = 10,
|
| 159 |
+
source_id: str | None = None,
|
| 160 |
+
time_range_start: str | None = None,
|
| 161 |
+
time_range_end: str | None = None,
|
| 162 |
+
scope: str | None = None,
|
| 163 |
+
ctx: Context = None,
|
| 164 |
+
) -> str:
|
| 165 |
+
"""Search and retrieve memories from Smriti's temporal memory.
|
| 166 |
+
|
| 167 |
+
Uses a powerful 3-phase hybrid retrieval pipeline:
|
| 168 |
+
1. Semantic search — finds memories by meaning (pgvector cosine similarity)
|
| 169 |
+
2. Temporal filtering — finds memories within a time range
|
| 170 |
+
3. Entity multi-hop — follows entity connections across memories
|
| 171 |
+
|
| 172 |
+
Results are ranked by combined relevance score.
|
| 173 |
+
|
| 174 |
+
Args:
|
| 175 |
+
query: Natural language search query. Be descriptive for best results.
|
| 176 |
+
Examples: "What programming languages has the user learned?",
|
| 177 |
+
"meetings about the product launch",
|
| 178 |
+
"any changes to the database schema"
|
| 179 |
+
max_results: Maximum number of results to return (1-100, default: 10).
|
| 180 |
+
source_id: Filter by a specific source namespace.
|
| 181 |
+
time_range_start: ISO 8601 timestamp for the start of the time range
|
| 182 |
+
(e.g., "2026-07-01T00:00:00Z"). Only memories after this time.
|
| 183 |
+
time_range_end: ISO 8601 timestamp for the end of the time range
|
| 184 |
+
(e.g., "2026-07-15T23:59:59Z"). Only memories before this time.
|
| 185 |
+
scope: Restrict results to a specific scope (e.g., "work", "personal").
|
| 186 |
+
"""
|
| 187 |
+
client: SmritiClient = ctx.request_context.lifespan_context["client"]
|
| 188 |
+
|
| 189 |
+
try:
|
| 190 |
+
source_ids = [source_id] if source_id else None
|
| 191 |
+
result = await client.query(
|
| 192 |
+
query=query,
|
| 193 |
+
max_results=max_results,
|
| 194 |
+
source_ids=source_ids,
|
| 195 |
+
time_range_start=time_range_start,
|
| 196 |
+
time_range_end=time_range_end,
|
| 197 |
+
scope=scope,
|
| 198 |
+
)
|
| 199 |
+
|
| 200 |
+
results = result.get("results", [])
|
| 201 |
+
total = result.get("total_found", 0)
|
| 202 |
+
query_ms = result.get("query_time_ms", 0)
|
| 203 |
+
|
| 204 |
+
if not results:
|
| 205 |
+
return f"No memories found matching: \"{query}\"\n(query took {query_ms:.0f}ms)"
|
| 206 |
+
|
| 207 |
+
lines = [f"Found {total} memory(ies) in {query_ms:.0f}ms:\n"]
|
| 208 |
+
|
| 209 |
+
for i, r in enumerate(results, 1):
|
| 210 |
+
event = r.get("event", {})
|
| 211 |
+
score = r.get("relevance_score", 0)
|
| 212 |
+
provenance = r.get("provenance", "unknown")
|
| 213 |
+
|
| 214 |
+
s = event.get("subject", "?")
|
| 215 |
+
v = event.get("verb", "?")
|
| 216 |
+
o = event.get("object", "?")
|
| 217 |
+
ts = event.get("timestamp", "")
|
| 218 |
+
|
| 219 |
+
# Format timestamp nicely if present
|
| 220 |
+
ts_display = ""
|
| 221 |
+
if ts:
|
| 222 |
+
try:
|
| 223 |
+
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
| 224 |
+
ts_display = dt.strftime("%Y-%m-%d %H:%M")
|
| 225 |
+
except (ValueError, AttributeError):
|
| 226 |
+
ts_display = str(ts)[:16]
|
| 227 |
+
|
| 228 |
+
lines.append(
|
| 229 |
+
f" {i}. [{s}] [{v}] [{o}]\n"
|
| 230 |
+
f" 📅 {ts_display} | relevance: {score:.2f} | via: {provenance}"
|
| 231 |
+
)
|
| 232 |
+
|
| 233 |
+
return "\n".join(lines)
|
| 234 |
+
|
| 235 |
+
except SmritiAPIError as e:
|
| 236 |
+
return f"❌ Failed to search memories: {e.detail}"
|
| 237 |
+
except Exception as e:
|
| 238 |
+
logger.error(f"smriti_recall error: {e}", exc_info=True)
|
| 239 |
+
return f"❌ Unexpected error searching memories: {str(e)}"
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
@mcp_server.tool()
|
| 243 |
+
async def smriti_timeline(
|
| 244 |
+
time_range_start: str,
|
| 245 |
+
time_range_end: str | None = None,
|
| 246 |
+
scope: str | None = None,
|
| 247 |
+
max_results: int = 20,
|
| 248 |
+
ctx: Context = None,
|
| 249 |
+
) -> str:
|
| 250 |
+
"""Retrieve a chronological timeline of events within a time range.
|
| 251 |
+
|
| 252 |
+
This is optimized for temporal queries — "What happened last week?",
|
| 253 |
+
"Show me everything from March 2026", "What did I do yesterday?"
|
| 254 |
+
|
| 255 |
+
Results are ordered chronologically (oldest first).
|
| 256 |
+
|
| 257 |
+
Args:
|
| 258 |
+
time_range_start: ISO 8601 timestamp for the start of the time range
|
| 259 |
+
(e.g., "2026-07-01T00:00:00Z").
|
| 260 |
+
time_range_end: ISO 8601 timestamp for the end of the time range
|
| 261 |
+
(e.g., "2026-07-15T23:59:59Z"). Defaults to current time.
|
| 262 |
+
scope: Restrict to a specific scope (e.g., "work", "personal").
|
| 263 |
+
max_results: Maximum number of events to return (default: 20).
|
| 264 |
+
"""
|
| 265 |
+
client: SmritiClient = ctx.request_context.lifespan_context["client"]
|
| 266 |
+
|
| 267 |
+
try:
|
| 268 |
+
# Use a broad query to get temporal results — the API's temporal filter
|
| 269 |
+
# does the heavy lifting
|
| 270 |
+
end = time_range_end or datetime.now(timezone.utc).isoformat()
|
| 271 |
+
|
| 272 |
+
result = await client.query(
|
| 273 |
+
query="*", # Broad query to let temporal filter dominate
|
| 274 |
+
max_results=max_results,
|
| 275 |
+
time_range_start=time_range_start,
|
| 276 |
+
time_range_end=end,
|
| 277 |
+
scope=scope,
|
| 278 |
+
semantic_weight=0.1, # Minimize semantic, maximize temporal
|
| 279 |
+
)
|
| 280 |
+
|
| 281 |
+
results = result.get("results", [])
|
| 282 |
+
|
| 283 |
+
if not results:
|
| 284 |
+
return (
|
| 285 |
+
f"No events found between {time_range_start} and {end}.\n"
|
| 286 |
+
"Try expanding the time range or checking if events were stored."
|
| 287 |
+
)
|
| 288 |
+
|
| 289 |
+
lines = [
|
| 290 |
+
f"📅 Timeline: {time_range_start[:10]} → {end[:10]}",
|
| 291 |
+
f" ({len(results)} event(s))\n",
|
| 292 |
+
]
|
| 293 |
+
|
| 294 |
+
for r in results:
|
| 295 |
+
event = r.get("event", {})
|
| 296 |
+
s = event.get("subject", "?")
|
| 297 |
+
v = event.get("verb", "?")
|
| 298 |
+
o = event.get("object", "?")
|
| 299 |
+
ts = event.get("timestamp", "")
|
| 300 |
+
|
| 301 |
+
ts_display = ""
|
| 302 |
+
if ts:
|
| 303 |
+
try:
|
| 304 |
+
dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
| 305 |
+
ts_display = dt.strftime("%b %d, %H:%M")
|
| 306 |
+
except (ValueError, AttributeError):
|
| 307 |
+
ts_display = str(ts)[:16]
|
| 308 |
+
|
| 309 |
+
lines.append(f" • {ts_display} — [{s}] [{v}] [{o}]")
|
| 310 |
+
|
| 311 |
+
return "\n".join(lines)
|
| 312 |
+
|
| 313 |
+
except SmritiAPIError as e:
|
| 314 |
+
return f"❌ Failed to retrieve timeline: {e.detail}"
|
| 315 |
+
except Exception as e:
|
| 316 |
+
logger.error(f"smriti_timeline error: {e}", exc_info=True)
|
| 317 |
+
return f"❌ Unexpected error retrieving timeline: {str(e)}"
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
@mcp_server.tool()
|
| 321 |
+
async def smriti_forget(
|
| 322 |
+
query: str,
|
| 323 |
+
scope: str | None = None,
|
| 324 |
+
max_to_forget: int = 5,
|
| 325 |
+
ctx: Context = None,
|
| 326 |
+
) -> str:
|
| 327 |
+
"""Find and mark memories as forgotten (superseded).
|
| 328 |
+
|
| 329 |
+
This does NOT delete memories — it marks them as superseded
|
| 330 |
+
(sets valid_to = now), preserving full history. The memories
|
| 331 |
+
will no longer appear in normal recall queries.
|
| 332 |
+
|
| 333 |
+
This is a two-step process:
|
| 334 |
+
1. First, call this tool to FIND memories matching the query
|
| 335 |
+
2. Review the results, then call smriti_remember with corrected facts
|
| 336 |
+
|
| 337 |
+
To truly "forget" something, store a superseding fact:
|
| 338 |
+
smriti_remember("User no longer uses React — switched to Vue")
|
| 339 |
+
|
| 340 |
+
Args:
|
| 341 |
+
query: Description of what to forget. Should match existing memories.
|
| 342 |
+
scope: Restrict to a specific scope.
|
| 343 |
+
max_to_forget: Maximum number of memories to find (safety limit, default: 5).
|
| 344 |
+
"""
|
| 345 |
+
client: SmritiClient = ctx.request_context.lifespan_context["client"]
|
| 346 |
+
|
| 347 |
+
try:
|
| 348 |
+
result = await client.query(
|
| 349 |
+
query=query,
|
| 350 |
+
max_results=max_to_forget,
|
| 351 |
+
scope=scope,
|
| 352 |
+
)
|
| 353 |
+
|
| 354 |
+
results = result.get("results", [])
|
| 355 |
+
|
| 356 |
+
if not results:
|
| 357 |
+
return f"No memories found matching: \"{query}\"\nNothing to forget."
|
| 358 |
+
|
| 359 |
+
lines = [
|
| 360 |
+
f"Found {len(results)} memory(ies) matching \"{query}\":\n",
|
| 361 |
+
"To supersede these, store a corrected fact with smriti_remember.",
|
| 362 |
+
"Example: smriti_remember(\"User no longer uses React — switched to Vue\")\n",
|
| 363 |
+
"Matching memories:",
|
| 364 |
+
]
|
| 365 |
+
|
| 366 |
+
for i, r in enumerate(results, 1):
|
| 367 |
+
event = r.get("event", {})
|
| 368 |
+
s = event.get("subject", "?")
|
| 369 |
+
v = event.get("verb", "?")
|
| 370 |
+
o = event.get("object", "?")
|
| 371 |
+
eid = event.get("id", "?")
|
| 372 |
+
|
| 373 |
+
lines.append(f" {i}. [{s}] [{v}] [{o}] (id: {eid[:8]}...)")
|
| 374 |
+
|
| 375 |
+
return "\n".join(lines)
|
| 376 |
+
|
| 377 |
+
except SmritiAPIError as e:
|
| 378 |
+
return f"❌ Failed to search for memories to forget: {e.detail}"
|
| 379 |
+
except Exception as e:
|
| 380 |
+
logger.error(f"smriti_forget error: {e}", exc_info=True)
|
| 381 |
+
return f"❌ Unexpected error: {str(e)}"
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
@mcp_server.tool()
|
| 385 |
+
async def smriti_health(ctx: Context = None) -> str:
|
| 386 |
+
"""Check the health and status of the Smriti memory service.
|
| 387 |
+
|
| 388 |
+
Returns the service status, event counts, and vector embedding counts.
|
| 389 |
+
Useful for diagnosing connection issues or checking if the service is alive.
|
| 390 |
+
"""
|
| 391 |
+
client: SmritiClient = ctx.request_context.lifespan_context["client"]
|
| 392 |
+
|
| 393 |
+
try:
|
| 394 |
+
# Try detailed health first, fall back to basic
|
| 395 |
+
try:
|
| 396 |
+
result = await client.health()
|
| 397 |
+
except SmritiAPIError:
|
| 398 |
+
result = await client.root_health()
|
| 399 |
+
|
| 400 |
+
status = result.get("status", "unknown")
|
| 401 |
+
stores = result.get("stores", {})
|
| 402 |
+
|
| 403 |
+
lines = [f"🏥 Smriti Health: {status.upper()}"]
|
| 404 |
+
|
| 405 |
+
if stores:
|
| 406 |
+
events = stores.get("postgres_events", "N/A")
|
| 407 |
+
vectors = stores.get("pgvector_embeddings", "N/A")
|
| 408 |
+
lines.append(f" 📊 Events stored: {events}")
|
| 409 |
+
lines.append(f" 🧮 Vector embeddings: {vectors}")
|
| 410 |
+
|
| 411 |
+
# Also include basic info if available
|
| 412 |
+
version = result.get("version", "")
|
| 413 |
+
if version:
|
| 414 |
+
lines.append(f" 📦 Version: {version}")
|
| 415 |
+
|
| 416 |
+
return "\n".join(lines)
|
| 417 |
+
|
| 418 |
+
except SmritiAPIError as e:
|
| 419 |
+
return f"❌ Health check failed: {e.detail}"
|
| 420 |
+
except Exception as e:
|
| 421 |
+
logger.error(f"smriti_health error: {e}", exc_info=True)
|
| 422 |
+
return f"❌ Cannot reach Smriti service: {str(e)}"
|
| 423 |
+
|
| 424 |
+
|
| 425 |
+
@mcp_server.tool()
|
| 426 |
+
async def smriti_usage(ctx: Context = None) -> str:
|
| 427 |
+
"""Check your current Smriti API usage statistics and tier limits.
|
| 428 |
+
|
| 429 |
+
Shows events used/remaining, orchestration calls, connected tools,
|
| 430 |
+
and your current pricing tier (Explorer/Builder/Scale).
|
| 431 |
+
"""
|
| 432 |
+
client: SmritiClient = ctx.request_context.lifespan_context["client"]
|
| 433 |
+
|
| 434 |
+
try:
|
| 435 |
+
result = await client.usage()
|
| 436 |
+
|
| 437 |
+
tier = result.get("tier", "unknown")
|
| 438 |
+
usage = result.get("usage", {})
|
| 439 |
+
limits = result.get("limits", {})
|
| 440 |
+
|
| 441 |
+
events = usage.get("events", {})
|
| 442 |
+
orch = usage.get("orchestration", {})
|
| 443 |
+
connectors = usage.get("connectors", {})
|
| 444 |
+
|
| 445 |
+
lines = [
|
| 446 |
+
f"📊 Smriti Usage — Tier: {tier.upper()}",
|
| 447 |
+
"",
|
| 448 |
+
f" Events: {events.get('used', 0):,} / {events.get('limit', 0):,} "
|
| 449 |
+
f"({events.get('remaining', 0):,} remaining)",
|
| 450 |
+
f" Orchestration: {orch.get('used', 0):,} / "
|
| 451 |
+
f"{'∞' if orch.get('limit', -1) == -1 else f'{orch.get(\"limit\", 0):,}'} "
|
| 452 |
+
f"({'∞' if orch.get('remaining') == 'unlimited' else f'{orch.get(\"remaining\", 0):,}'} remaining)",
|
| 453 |
+
f" Connectors: {connectors.get('used', 0)} / "
|
| 454 |
+
f"{'∞' if connectors.get('limit', -1) == -1 else connectors.get('limit', 0)}",
|
| 455 |
+
]
|
| 456 |
+
|
| 457 |
+
retention = limits.get("retention_days", 0)
|
| 458 |
+
lines.append(
|
| 459 |
+
f"\n Retention: {'unlimited' if retention == -1 else f'{retention} days'}"
|
| 460 |
+
)
|
| 461 |
+
|
| 462 |
+
period = result.get("period_start", "")
|
| 463 |
+
if period:
|
| 464 |
+
lines.append(f" Period start: {period[:10]}")
|
| 465 |
+
|
| 466 |
+
return "\n".join(lines)
|
| 467 |
+
|
| 468 |
+
except SmritiAPIError as e:
|
| 469 |
+
return f"❌ Failed to get usage stats: {e.detail}"
|
| 470 |
+
except Exception as e:
|
| 471 |
+
logger.error(f"smriti_usage error: {e}", exc_info=True)
|
| 472 |
+
return f"❌ Unexpected error fetching usage: {str(e)}"
|
| 473 |
+
|
| 474 |
+
|
| 475 |
+
# ═══════════════════════════════════════════════════════════════════════════════
|
| 476 |
+
# RESOURCES — Read-only data the application can pull into context
|
| 477 |
+
# ═══════════════════════════════════════════════════════════════════════════════
|
| 478 |
+
|
| 479 |
+
|
| 480 |
+
@mcp_server.resource("smriti://status")
|
| 481 |
+
async def resource_status() -> str:
|
| 482 |
+
"""Live Smriti service health status and memory counts."""
|
| 483 |
+
config = load_config()
|
| 484 |
+
client = SmritiClient(config)
|
| 485 |
+
await client.connect()
|
| 486 |
+
try:
|
| 487 |
+
result = await client.health()
|
| 488 |
+
return json.dumps(result, indent=2, default=str)
|
| 489 |
+
except Exception as e:
|
| 490 |
+
return json.dumps({"status": "error", "detail": str(e)})
|
| 491 |
+
finally:
|
| 492 |
+
await client.close()
|
| 493 |
+
|
| 494 |
+
|
| 495 |
+
@mcp_server.resource("smriti://usage")
|
| 496 |
+
async def resource_usage() -> str:
|
| 497 |
+
"""Current API usage statistics and tier limits."""
|
| 498 |
+
config = load_config()
|
| 499 |
+
client = SmritiClient(config)
|
| 500 |
+
await client.connect()
|
| 501 |
+
try:
|
| 502 |
+
result = await client.usage()
|
| 503 |
+
return json.dumps(result, indent=2, default=str)
|
| 504 |
+
except Exception as e:
|
| 505 |
+
return json.dumps({"error": str(e)})
|
| 506 |
+
finally:
|
| 507 |
+
await client.close()
|
| 508 |
+
|
| 509 |
+
|
| 510 |
+
@mcp_server.resource("smriti://config")
|
| 511 |
+
async def resource_config() -> str:
|
| 512 |
+
"""Current MCP server configuration (non-sensitive fields only)."""
|
| 513 |
+
config = load_config()
|
| 514 |
+
return json.dumps(
|
| 515 |
+
{
|
| 516 |
+
"base_url": config.base_url,
|
| 517 |
+
"source_id": config.source_id,
|
| 518 |
+
"scope": config.scope,
|
| 519 |
+
"max_results": config.max_results,
|
| 520 |
+
"parse_svo": config.parse_svo,
|
| 521 |
+
"timeout_seconds": config.timeout_seconds,
|
| 522 |
+
"api_key_set": bool(config.api_key),
|
| 523 |
+
},
|
| 524 |
+
indent=2,
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
|
| 528 |
+
# ═══════════════════════════════════════════════════════════════════════════════
|
| 529 |
+
# PROMPTS — User-selectable instruction templates
|
| 530 |
+
# ═══════════════════════════════════════════════════════════════════════════════
|
| 531 |
+
|
| 532 |
+
|
| 533 |
+
@mcp_server.prompt()
|
| 534 |
+
async def memory_chat() -> str:
|
| 535 |
+
"""System prompt for memory-augmented conversations.
|
| 536 |
+
|
| 537 |
+
Instructs the AI to naturally remember and recall information
|
| 538 |
+
during conversation, using smriti_remember and smriti_recall.
|
| 539 |
+
"""
|
| 540 |
+
return (
|
| 541 |
+
"You are an AI assistant with persistent temporal memory powered by Smriti.\n\n"
|
| 542 |
+
"MEMORY GUIDELINES:\n"
|
| 543 |
+
"1. REMEMBER important facts: When the user shares preferences, decisions, "
|
| 544 |
+
"events, or key information, use smriti_remember to store it.\n"
|
| 545 |
+
"2. RECALL before answering: When the user asks about something that might "
|
| 546 |
+
"have been discussed before, use smriti_recall to check your memory first.\n"
|
| 547 |
+
"3. BE NATURAL: Don't announce every memory operation. Seamlessly weave "
|
| 548 |
+
"recalled context into your responses.\n"
|
| 549 |
+
"4. TEMPORAL AWARENESS: Note when things happened. Use timestamps and "
|
| 550 |
+
"time ranges to provide temporally accurate answers.\n"
|
| 551 |
+
"5. SUPERSESSION: When facts change (e.g., 'I switched from React to Vue'), "
|
| 552 |
+
"store the new fact — Smriti automatically supersedes the old one.\n\n"
|
| 553 |
+
"WHAT TO REMEMBER:\n"
|
| 554 |
+
"- User preferences and settings\n"
|
| 555 |
+
"- Important decisions and their reasoning\n"
|
| 556 |
+
"- Project milestones and status changes\n"
|
| 557 |
+
"- Technical choices and architecture decisions\n"
|
| 558 |
+
"- People, relationships, and roles mentioned\n"
|
| 559 |
+
"- Problems encountered and solutions found\n\n"
|
| 560 |
+
"WHAT NOT TO REMEMBER:\n"
|
| 561 |
+
"- Transient/throwaway questions\n"
|
| 562 |
+
"- Generic greetings or chitchat\n"
|
| 563 |
+
"- Information the user explicitly asks you to ignore\n"
|
| 564 |
+
)
|
| 565 |
+
|
| 566 |
+
|
| 567 |
+
@mcp_server.prompt()
|
| 568 |
+
async def daily_recap() -> str:
|
| 569 |
+
"""Prompt template for generating a daily/weekly recap.
|
| 570 |
+
|
| 571 |
+
Uses smriti_timeline to pull events from a time range
|
| 572 |
+
and summarize what happened.
|
| 573 |
+
"""
|
| 574 |
+
return (
|
| 575 |
+
"Generate a concise recap of recent events using Smriti's temporal memory.\n\n"
|
| 576 |
+
"INSTRUCTIONS:\n"
|
| 577 |
+
"1. Use smriti_timeline to retrieve events from the relevant time range.\n"
|
| 578 |
+
"2. Group events by theme or category (work, personal, learning, etc.).\n"
|
| 579 |
+
"3. Highlight key decisions, milestones, and changes.\n"
|
| 580 |
+
"4. Note any superseded facts (things that changed).\n"
|
| 581 |
+
"5. Format as a clean, scannable summary.\n\n"
|
| 582 |
+
"SUGGESTED TIME RANGES:\n"
|
| 583 |
+
"- Daily recap: last 24 hours\n"
|
| 584 |
+
"- Weekly recap: last 7 days\n"
|
| 585 |
+
"- Monthly recap: last 30 days\n\n"
|
| 586 |
+
"Start by asking the user what time range they'd like to review, "
|
| 587 |
+
"or default to the last 24 hours.\n"
|
| 588 |
+
)
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
@mcp_server.prompt()
|
| 592 |
+
async def knowledge_extraction() -> str:
|
| 593 |
+
"""Prompt for bulk knowledge ingestion from documents or conversations.
|
| 594 |
+
|
| 595 |
+
Instructs the AI to read content and extract key facts into memory
|
| 596 |
+
using smriti_remember.
|
| 597 |
+
"""
|
| 598 |
+
return (
|
| 599 |
+
"You are a knowledge extraction assistant powered by Smriti temporal memory.\n\n"
|
| 600 |
+
"INSTRUCTIONS:\n"
|
| 601 |
+
"1. Read the provided content carefully.\n"
|
| 602 |
+
"2. Identify key facts, events, decisions, and relationships.\n"
|
| 603 |
+
"3. For each significant piece of information, use smriti_remember to store it.\n"
|
| 604 |
+
"4. Use descriptive text that captures the WHO, WHAT, WHEN, and WHY.\n"
|
| 605 |
+
"5. Set appropriate timestamps if dates are mentioned in the content.\n"
|
| 606 |
+
"6. Use scopes to organize by topic (e.g., 'project-x', 'meeting-notes').\n\n"
|
| 607 |
+
"EXTRACTION PRIORITIES:\n"
|
| 608 |
+
"- Decisions and their rationale\n"
|
| 609 |
+
"- Action items and deadlines\n"
|
| 610 |
+
"- Key metrics and numbers\n"
|
| 611 |
+
"- People and their roles/responsibilities\n"
|
| 612 |
+
"- Status changes and milestones\n"
|
| 613 |
+
"- Technical specifications and constraints\n\n"
|
| 614 |
+
"After extraction, provide a summary of what was stored.\n"
|
| 615 |
+
)
|
| 616 |
+
|
| 617 |
+
|
| 618 |
+
# ═══════════════════════════════════════════════════════════════════════════════
|
| 619 |
+
# Server runner (used by __main__.py)
|
| 620 |
+
# ═══════════════════════════════════════════════════════════════════════════════
|
| 621 |
+
|
| 622 |
+
|
| 623 |
+
def run_server(transport: str = "stdio", port: int = 8080) -> None:
|
| 624 |
+
"""Run the MCP server with the specified transport."""
|
| 625 |
+
logger.info(f"Starting Smriti MCP server (transport={transport})")
|
| 626 |
+
|
| 627 |
+
if transport == "stdio":
|
| 628 |
+
mcp_server.run(transport="stdio")
|
| 629 |
+
elif transport == "sse":
|
| 630 |
+
mcp_server.run(transport="sse", port=port)
|
| 631 |
+
else:
|
| 632 |
+
logger.error(f"Unknown transport: {transport}")
|
| 633 |
+
raise ValueError(f"Unsupported transport: {transport}. Use 'stdio' or 'sse'.")
|
tests/test_mcp_client.py
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smriti MCP — HTTP Client Tests
|
| 3 |
+
================================
|
| 4 |
+
Tests for the SmritiClient HTTP wrapper.
|
| 5 |
+
Uses httpx mock transport to avoid real API calls.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import pytest
|
| 12 |
+
import httpx
|
| 13 |
+
|
| 14 |
+
# ── Adjust import path ───────────────────────────────────────────────────────
|
| 15 |
+
import sys, os
|
| 16 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
| 17 |
+
|
| 18 |
+
from mcp.config import SmritiMCPConfig, load_config
|
| 19 |
+
from mcp.client import SmritiClient, SmritiAPIError
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# ── Fixtures ──────────────────────────────────────────────────────────────────
|
| 23 |
+
|
| 24 |
+
def make_config(**overrides) -> SmritiMCPConfig:
|
| 25 |
+
defaults = dict(
|
| 26 |
+
api_key="chrn_test_key_123",
|
| 27 |
+
base_url="https://test-api.smriti.dev",
|
| 28 |
+
source_id="test-source",
|
| 29 |
+
scope="default",
|
| 30 |
+
max_results=10,
|
| 31 |
+
parse_svo=True,
|
| 32 |
+
timeout_seconds=5.0,
|
| 33 |
+
max_retries=1,
|
| 34 |
+
)
|
| 35 |
+
defaults.update(overrides)
|
| 36 |
+
return SmritiMCPConfig(**defaults)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def mock_transport(responses: dict[str, httpx.Response]) -> httpx.MockTransport:
|
| 40 |
+
"""Create a mock transport that returns canned responses by path."""
|
| 41 |
+
def handler(request: httpx.Request) -> httpx.Response:
|
| 42 |
+
path = request.url.path
|
| 43 |
+
if path in responses:
|
| 44 |
+
return responses[path]
|
| 45 |
+
return httpx.Response(404, json={"detail": f"Not found: {path}"})
|
| 46 |
+
return httpx.MockTransport(handler)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
# ── Config Tests ──────────────────────────────────────────────────────────────
|
| 50 |
+
|
| 51 |
+
class TestConfig:
|
| 52 |
+
def test_load_defaults(self, monkeypatch):
|
| 53 |
+
monkeypatch.delenv("SMRITI_API_KEY", raising=False)
|
| 54 |
+
monkeypatch.delenv("SMRITI_BASE_URL", raising=False)
|
| 55 |
+
config = load_config()
|
| 56 |
+
assert config.api_key == ""
|
| 57 |
+
assert "hf.space" in config.base_url
|
| 58 |
+
assert config.source_id == "mcp-client"
|
| 59 |
+
|
| 60 |
+
def test_load_from_env(self, monkeypatch):
|
| 61 |
+
monkeypatch.setenv("SMRITI_API_KEY", "chrn_env_key")
|
| 62 |
+
monkeypatch.setenv("SMRITI_BASE_URL", "https://custom.api.dev")
|
| 63 |
+
monkeypatch.setenv("SMRITI_SOURCE_ID", "my-source")
|
| 64 |
+
monkeypatch.setenv("SMRITI_MAX_RESULTS", "50")
|
| 65 |
+
monkeypatch.setenv("SMRITI_PARSE_SVO", "false")
|
| 66 |
+
config = load_config()
|
| 67 |
+
assert config.api_key == "chrn_env_key"
|
| 68 |
+
assert config.base_url == "https://custom.api.dev"
|
| 69 |
+
assert config.source_id == "my-source"
|
| 70 |
+
assert config.max_results == 50
|
| 71 |
+
assert config.parse_svo is False
|
| 72 |
+
|
| 73 |
+
def test_validate_missing_key(self):
|
| 74 |
+
config = SmritiMCPConfig(api_key="")
|
| 75 |
+
with pytest.raises(ValueError, match="SMRITI_API_KEY"):
|
| 76 |
+
config.validate()
|
| 77 |
+
|
| 78 |
+
def test_validate_with_key(self):
|
| 79 |
+
config = SmritiMCPConfig(api_key="chrn_valid_key")
|
| 80 |
+
config.validate() # Should not raise
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# ── Client Tests ──────────────────────────────────────────────────────────────
|
| 84 |
+
|
| 85 |
+
class TestSmritiClient:
|
| 86 |
+
|
| 87 |
+
@pytest.mark.asyncio
|
| 88 |
+
async def test_ingest_success(self):
|
| 89 |
+
config = make_config()
|
| 90 |
+
client = SmritiClient(config)
|
| 91 |
+
|
| 92 |
+
response_data = {
|
| 93 |
+
"ingested_count": 2,
|
| 94 |
+
"event_ids": ["evt_abc", "evt_def"],
|
| 95 |
+
"svo_tuples": [
|
| 96 |
+
{"subject": "User", "verb": "learned", "object": "Rust", "confidence": 0.95}
|
| 97 |
+
],
|
| 98 |
+
"turn_ids": ["turn_001"],
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
transport = mock_transport({
|
| 102 |
+
"/ingest": httpx.Response(200, json=response_data),
|
| 103 |
+
})
|
| 104 |
+
client._client = httpx.AsyncClient(
|
| 105 |
+
base_url=config.base_url, transport=transport
|
| 106 |
+
)
|
| 107 |
+
|
| 108 |
+
result = await client.ingest("User started learning Rust last week")
|
| 109 |
+
assert result["ingested_count"] == 2
|
| 110 |
+
assert len(result["event_ids"]) == 2
|
| 111 |
+
assert result["svo_tuples"][0]["subject"] == "User"
|
| 112 |
+
|
| 113 |
+
await client.close()
|
| 114 |
+
|
| 115 |
+
@pytest.mark.asyncio
|
| 116 |
+
async def test_query_success(self):
|
| 117 |
+
config = make_config()
|
| 118 |
+
client = SmritiClient(config)
|
| 119 |
+
|
| 120 |
+
response_data = {
|
| 121 |
+
"results": [
|
| 122 |
+
{
|
| 123 |
+
"event": {
|
| 124 |
+
"id": "evt_abc",
|
| 125 |
+
"source_id": "test",
|
| 126 |
+
"subject": "User",
|
| 127 |
+
"verb": "learned",
|
| 128 |
+
"object": "Rust",
|
| 129 |
+
"timestamp": "2026-07-15T10:00:00Z",
|
| 130 |
+
"confidence": 0.95,
|
| 131 |
+
},
|
| 132 |
+
"relevance_score": 0.87,
|
| 133 |
+
"provenance": "semantic_search",
|
| 134 |
+
}
|
| 135 |
+
],
|
| 136 |
+
"total_found": 1,
|
| 137 |
+
"query_time_ms": 42.5,
|
| 138 |
+
}
|
| 139 |
+
|
| 140 |
+
transport = mock_transport({
|
| 141 |
+
"/query": httpx.Response(200, json=response_data),
|
| 142 |
+
})
|
| 143 |
+
client._client = httpx.AsyncClient(
|
| 144 |
+
base_url=config.base_url, transport=transport
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
result = await client.query("What has the user learned?")
|
| 148 |
+
assert result["total_found"] == 1
|
| 149 |
+
assert result["results"][0]["event"]["subject"] == "User"
|
| 150 |
+
|
| 151 |
+
await client.close()
|
| 152 |
+
|
| 153 |
+
@pytest.mark.asyncio
|
| 154 |
+
async def test_health_success(self):
|
| 155 |
+
config = make_config()
|
| 156 |
+
client = SmritiClient(config)
|
| 157 |
+
|
| 158 |
+
response_data = {
|
| 159 |
+
"status": "healthy",
|
| 160 |
+
"stores": {
|
| 161 |
+
"postgres_events": 1234,
|
| 162 |
+
"pgvector_embeddings": 1200,
|
| 163 |
+
},
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
transport = mock_transport({
|
| 167 |
+
"/health": httpx.Response(200, json=response_data),
|
| 168 |
+
})
|
| 169 |
+
client._client = httpx.AsyncClient(
|
| 170 |
+
base_url=config.base_url, transport=transport
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
result = await client.health()
|
| 174 |
+
assert result["status"] == "healthy"
|
| 175 |
+
assert result["stores"]["postgres_events"] == 1234
|
| 176 |
+
|
| 177 |
+
await client.close()
|
| 178 |
+
|
| 179 |
+
@pytest.mark.asyncio
|
| 180 |
+
async def test_usage_success(self):
|
| 181 |
+
config = make_config()
|
| 182 |
+
client = SmritiClient(config)
|
| 183 |
+
|
| 184 |
+
response_data = {
|
| 185 |
+
"source_id": "test",
|
| 186 |
+
"tier": "explorer",
|
| 187 |
+
"usage": {
|
| 188 |
+
"events": {"used": 500, "limit": 10000, "remaining": 9500},
|
| 189 |
+
"orchestration": {"used": 10, "limit": 1000, "remaining": 990},
|
| 190 |
+
"connectors": {"used": 1, "limit": 3},
|
| 191 |
+
},
|
| 192 |
+
"limits": {"retention_days": 30},
|
| 193 |
+
"period_start": "2026-07-01T00:00:00Z",
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
transport = mock_transport({
|
| 197 |
+
"/billing/usage": httpx.Response(200, json=response_data),
|
| 198 |
+
})
|
| 199 |
+
client._client = httpx.AsyncClient(
|
| 200 |
+
base_url=config.base_url, transport=transport
|
| 201 |
+
)
|
| 202 |
+
|
| 203 |
+
result = await client.usage()
|
| 204 |
+
assert result["tier"] == "explorer"
|
| 205 |
+
assert result["usage"]["events"]["used"] == 500
|
| 206 |
+
|
| 207 |
+
await client.close()
|
| 208 |
+
|
| 209 |
+
@pytest.mark.asyncio
|
| 210 |
+
async def test_auth_error_not_retried(self):
|
| 211 |
+
config = make_config(max_retries=3)
|
| 212 |
+
client = SmritiClient(config)
|
| 213 |
+
|
| 214 |
+
transport = mock_transport({
|
| 215 |
+
"/health": httpx.Response(401, json={"detail": "Invalid API key"}),
|
| 216 |
+
})
|
| 217 |
+
client._client = httpx.AsyncClient(
|
| 218 |
+
base_url=config.base_url, transport=transport
|
| 219 |
+
)
|
| 220 |
+
|
| 221 |
+
with pytest.raises(SmritiAPIError) as exc_info:
|
| 222 |
+
await client.health()
|
| 223 |
+
assert exc_info.value.status_code == 401
|
| 224 |
+
assert "Invalid API key" in exc_info.value.detail
|
| 225 |
+
|
| 226 |
+
await client.close()
|
| 227 |
+
|
| 228 |
+
@pytest.mark.asyncio
|
| 229 |
+
async def test_quota_error(self):
|
| 230 |
+
config = make_config()
|
| 231 |
+
client = SmritiClient(config)
|
| 232 |
+
|
| 233 |
+
transport = mock_transport({
|
| 234 |
+
"/ingest": httpx.Response(
|
| 235 |
+
429, json={"detail": "Event quota exceeded (10000/10000)"}
|
| 236 |
+
),
|
| 237 |
+
})
|
| 238 |
+
client._client = httpx.AsyncClient(
|
| 239 |
+
base_url=config.base_url, transport=transport
|
| 240 |
+
)
|
| 241 |
+
|
| 242 |
+
with pytest.raises(SmritiAPIError) as exc_info:
|
| 243 |
+
await client.ingest("test")
|
| 244 |
+
assert exc_info.value.status_code == 429
|
| 245 |
+
|
| 246 |
+
await client.close()
|
| 247 |
+
|
| 248 |
+
@pytest.mark.asyncio
|
| 249 |
+
async def test_connectors_success(self):
|
| 250 |
+
config = make_config()
|
| 251 |
+
client = SmritiClient(config)
|
| 252 |
+
|
| 253 |
+
response_data = [
|
| 254 |
+
{
|
| 255 |
+
"id": "conn_001",
|
| 256 |
+
"name": "Stripe",
|
| 257 |
+
"description": "Payment API",
|
| 258 |
+
"base_url": "https://api.stripe.com",
|
| 259 |
+
"endpoints_count": 3,
|
| 260 |
+
"created_at": "2026-07-01T00:00:00Z",
|
| 261 |
+
}
|
| 262 |
+
]
|
| 263 |
+
|
| 264 |
+
transport = mock_transport({
|
| 265 |
+
"/connectors": httpx.Response(200, json=response_data),
|
| 266 |
+
})
|
| 267 |
+
client._client = httpx.AsyncClient(
|
| 268 |
+
base_url=config.base_url, transport=transport
|
| 269 |
+
)
|
| 270 |
+
|
| 271 |
+
result = await client.list_connectors()
|
| 272 |
+
assert len(result) == 1
|
| 273 |
+
assert result[0]["name"] == "Stripe"
|
| 274 |
+
|
| 275 |
+
await client.close()
|
| 276 |
+
|
| 277 |
+
@pytest.mark.asyncio
|
| 278 |
+
async def test_query_with_time_range(self):
|
| 279 |
+
config = make_config()
|
| 280 |
+
client = SmritiClient(config)
|
| 281 |
+
|
| 282 |
+
transport = mock_transport({
|
| 283 |
+
"/query": httpx.Response(200, json={
|
| 284 |
+
"results": [], "total_found": 0, "query_time_ms": 5.0
|
| 285 |
+
}),
|
| 286 |
+
})
|
| 287 |
+
client._client = httpx.AsyncClient(
|
| 288 |
+
base_url=config.base_url, transport=transport
|
| 289 |
+
)
|
| 290 |
+
|
| 291 |
+
result = await client.query(
|
| 292 |
+
"meetings",
|
| 293 |
+
time_range_start="2026-07-01T00:00:00Z",
|
| 294 |
+
time_range_end="2026-07-15T23:59:59Z",
|
| 295 |
+
)
|
| 296 |
+
assert result["total_found"] == 0
|
| 297 |
+
|
| 298 |
+
await client.close()
|
| 299 |
+
|
| 300 |
+
@pytest.mark.asyncio
|
| 301 |
+
async def test_not_connected_error(self):
|
| 302 |
+
config = make_config()
|
| 303 |
+
client = SmritiClient(config)
|
| 304 |
+
# Don't call connect()
|
| 305 |
+
|
| 306 |
+
with pytest.raises(RuntimeError, match="not connected"):
|
| 307 |
+
await client.health()
|
| 308 |
+
|
| 309 |
+
@pytest.mark.asyncio
|
| 310 |
+
async def test_connect_and_close(self):
|
| 311 |
+
config = make_config()
|
| 312 |
+
client = SmritiClient(config)
|
| 313 |
+
|
| 314 |
+
await client.connect()
|
| 315 |
+
assert client._client is not None
|
| 316 |
+
|
| 317 |
+
await client.close()
|
| 318 |
+
assert client._client is None
|
| 319 |
+
|
| 320 |
+
@pytest.mark.asyncio
|
| 321 |
+
async def test_auth_header_set(self):
|
| 322 |
+
config = make_config(api_key="chrn_my_secret_key")
|
| 323 |
+
client = SmritiClient(config)
|
| 324 |
+
await client.connect()
|
| 325 |
+
|
| 326 |
+
assert client._client.headers["X-API-Key"] == "chrn_my_secret_key"
|
| 327 |
+
assert "smriti-mcp" in client._client.headers["User-Agent"]
|
| 328 |
+
|
| 329 |
+
await client.close()
|
tests/test_mcp_server.py
ADDED
|
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Smriti MCP — Server Tests
|
| 3 |
+
===========================
|
| 4 |
+
Tests for MCP tools, resources, and prompts.
|
| 5 |
+
Uses mocked SmritiClient to avoid real API calls.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import json
|
| 11 |
+
import pytest
|
| 12 |
+
from unittest.mock import AsyncMock, MagicMock, patch
|
| 13 |
+
|
| 14 |
+
# ── Adjust import path ───────────────────────────────────────────────────────
|
| 15 |
+
import sys, os
|
| 16 |
+
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
|
| 17 |
+
|
| 18 |
+
from mcp.client import SmritiClient, SmritiAPIError
|
| 19 |
+
from mcp.config import SmritiMCPConfig
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
| 23 |
+
|
| 24 |
+
def make_mock_context(client: SmritiClient) -> MagicMock:
|
| 25 |
+
"""Create a mock MCP Context with a SmritiClient in the lifespan context."""
|
| 26 |
+
ctx = MagicMock()
|
| 27 |
+
ctx.request_context.lifespan_context = {"client": client}
|
| 28 |
+
return ctx
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def make_mock_client() -> SmritiClient:
|
| 32 |
+
"""Create a SmritiClient with all methods mocked."""
|
| 33 |
+
config = SmritiMCPConfig(api_key="chrn_test_key")
|
| 34 |
+
client = SmritiClient(config)
|
| 35 |
+
client.ingest = AsyncMock()
|
| 36 |
+
client.query = AsyncMock()
|
| 37 |
+
client.health = AsyncMock()
|
| 38 |
+
client.root_health = AsyncMock()
|
| 39 |
+
client.usage = AsyncMock()
|
| 40 |
+
client.list_connectors = AsyncMock()
|
| 41 |
+
return client
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
# ── Tool Tests ────────────────────────────────────────────────────────────────
|
| 45 |
+
|
| 46 |
+
class TestSmritiRemember:
|
| 47 |
+
@pytest.mark.asyncio
|
| 48 |
+
async def test_remember_success(self):
|
| 49 |
+
from mcp.server import smriti_remember
|
| 50 |
+
|
| 51 |
+
client = make_mock_client()
|
| 52 |
+
client.ingest.return_value = {
|
| 53 |
+
"ingested_count": 1,
|
| 54 |
+
"event_ids": ["evt_abc123"],
|
| 55 |
+
"svo_tuples": [
|
| 56 |
+
{"subject": "Alice", "verb": "quit", "object": "her job at Google", "confidence": 0.92}
|
| 57 |
+
],
|
| 58 |
+
"turn_ids": ["turn_001"],
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
ctx = make_mock_context(client)
|
| 62 |
+
result = await smriti_remember(
|
| 63 |
+
text="Alice quit her job at Google",
|
| 64 |
+
ctx=ctx,
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
assert "Remembered 1 event" in result
|
| 68 |
+
assert "[Alice]" in result
|
| 69 |
+
assert "[quit]" in result
|
| 70 |
+
assert "confidence: 92%" in result
|
| 71 |
+
client.ingest.assert_awaited_once()
|
| 72 |
+
|
| 73 |
+
@pytest.mark.asyncio
|
| 74 |
+
async def test_remember_with_options(self):
|
| 75 |
+
from mcp.server import smriti_remember
|
| 76 |
+
|
| 77 |
+
client = make_mock_client()
|
| 78 |
+
client.ingest.return_value = {
|
| 79 |
+
"ingested_count": 1,
|
| 80 |
+
"event_ids": ["evt_def456"],
|
| 81 |
+
"svo_tuples": [],
|
| 82 |
+
"turn_ids": ["turn_002"],
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
ctx = make_mock_context(client)
|
| 86 |
+
result = await smriti_remember(
|
| 87 |
+
text="Daily standup meeting",
|
| 88 |
+
source_id="work-calendar",
|
| 89 |
+
parse_svo=False,
|
| 90 |
+
scope="work",
|
| 91 |
+
timestamp="2026-07-15T09:00:00Z",
|
| 92 |
+
ctx=ctx,
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
assert "Remembered 1 event" in result
|
| 96 |
+
call_kwargs = client.ingest.call_args.kwargs
|
| 97 |
+
assert call_kwargs["source_id"] == "work-calendar"
|
| 98 |
+
assert call_kwargs["parse_svo"] is False
|
| 99 |
+
assert call_kwargs["scope"] == "work"
|
| 100 |
+
|
| 101 |
+
@pytest.mark.asyncio
|
| 102 |
+
async def test_remember_api_error(self):
|
| 103 |
+
from mcp.server import smriti_remember
|
| 104 |
+
|
| 105 |
+
client = make_mock_client()
|
| 106 |
+
client.ingest.side_effect = SmritiAPIError(429, "Event quota exceeded")
|
| 107 |
+
|
| 108 |
+
ctx = make_mock_context(client)
|
| 109 |
+
result = await smriti_remember(text="test", ctx=ctx)
|
| 110 |
+
|
| 111 |
+
assert "❌" in result
|
| 112 |
+
assert "quota" in result.lower()
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
class TestSmritiRecall:
|
| 116 |
+
@pytest.mark.asyncio
|
| 117 |
+
async def test_recall_with_results(self):
|
| 118 |
+
from mcp.server import smriti_recall
|
| 119 |
+
|
| 120 |
+
client = make_mock_client()
|
| 121 |
+
client.query.return_value = {
|
| 122 |
+
"results": [
|
| 123 |
+
{
|
| 124 |
+
"event": {
|
| 125 |
+
"id": "evt_abc",
|
| 126 |
+
"source_id": "test",
|
| 127 |
+
"subject": "User",
|
| 128 |
+
"verb": "learned",
|
| 129 |
+
"object": "Rust",
|
| 130 |
+
"timestamp": "2026-07-15T10:00:00Z",
|
| 131 |
+
"confidence": 0.95,
|
| 132 |
+
},
|
| 133 |
+
"relevance_score": 0.87,
|
| 134 |
+
"provenance": "semantic_search",
|
| 135 |
+
}
|
| 136 |
+
],
|
| 137 |
+
"total_found": 1,
|
| 138 |
+
"query_time_ms": 42.5,
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
ctx = make_mock_context(client)
|
| 142 |
+
result = await smriti_recall(query="programming languages", ctx=ctx)
|
| 143 |
+
|
| 144 |
+
assert "Found 1 memory" in result
|
| 145 |
+
assert "[User]" in result
|
| 146 |
+
assert "[learned]" in result
|
| 147 |
+
assert "[Rust]" in result
|
| 148 |
+
assert "42ms" in result
|
| 149 |
+
|
| 150 |
+
@pytest.mark.asyncio
|
| 151 |
+
async def test_recall_no_results(self):
|
| 152 |
+
from mcp.server import smriti_recall
|
| 153 |
+
|
| 154 |
+
client = make_mock_client()
|
| 155 |
+
client.query.return_value = {
|
| 156 |
+
"results": [],
|
| 157 |
+
"total_found": 0,
|
| 158 |
+
"query_time_ms": 5.0,
|
| 159 |
+
}
|
| 160 |
+
|
| 161 |
+
ctx = make_mock_context(client)
|
| 162 |
+
result = await smriti_recall(query="quantum physics", ctx=ctx)
|
| 163 |
+
|
| 164 |
+
assert "No memories found" in result
|
| 165 |
+
|
| 166 |
+
@pytest.mark.asyncio
|
| 167 |
+
async def test_recall_with_filters(self):
|
| 168 |
+
from mcp.server import smriti_recall
|
| 169 |
+
|
| 170 |
+
client = make_mock_client()
|
| 171 |
+
client.query.return_value = {
|
| 172 |
+
"results": [], "total_found": 0, "query_time_ms": 3.0
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
ctx = make_mock_context(client)
|
| 176 |
+
await smriti_recall(
|
| 177 |
+
query="meetings",
|
| 178 |
+
max_results=5,
|
| 179 |
+
source_id="work",
|
| 180 |
+
time_range_start="2026-07-01T00:00:00Z",
|
| 181 |
+
time_range_end="2026-07-15T23:59:59Z",
|
| 182 |
+
scope="work",
|
| 183 |
+
ctx=ctx,
|
| 184 |
+
)
|
| 185 |
+
|
| 186 |
+
call_kwargs = client.query.call_args.kwargs
|
| 187 |
+
assert call_kwargs["max_results"] == 5
|
| 188 |
+
assert call_kwargs["source_ids"] == ["work"]
|
| 189 |
+
assert call_kwargs["time_range_start"] == "2026-07-01T00:00:00Z"
|
| 190 |
+
assert call_kwargs["scope"] == "work"
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
class TestSmritiTimeline:
|
| 194 |
+
@pytest.mark.asyncio
|
| 195 |
+
async def test_timeline_with_events(self):
|
| 196 |
+
from mcp.server import smriti_timeline
|
| 197 |
+
|
| 198 |
+
client = make_mock_client()
|
| 199 |
+
client.query.return_value = {
|
| 200 |
+
"results": [
|
| 201 |
+
{
|
| 202 |
+
"event": {
|
| 203 |
+
"id": "evt_1",
|
| 204 |
+
"source_id": "test",
|
| 205 |
+
"subject": "Team",
|
| 206 |
+
"verb": "deployed",
|
| 207 |
+
"object": "v2.0",
|
| 208 |
+
"timestamp": "2026-07-10T14:00:00Z",
|
| 209 |
+
"confidence": 0.9,
|
| 210 |
+
},
|
| 211 |
+
"relevance_score": 0.8,
|
| 212 |
+
"provenance": "temporal_filter",
|
| 213 |
+
}
|
| 214 |
+
],
|
| 215 |
+
"total_found": 1,
|
| 216 |
+
"query_time_ms": 20.0,
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
ctx = make_mock_context(client)
|
| 220 |
+
result = await smriti_timeline(
|
| 221 |
+
time_range_start="2026-07-01T00:00:00Z",
|
| 222 |
+
time_range_end="2026-07-15T23:59:59Z",
|
| 223 |
+
ctx=ctx,
|
| 224 |
+
)
|
| 225 |
+
|
| 226 |
+
assert "Timeline" in result
|
| 227 |
+
assert "[Team]" in result
|
| 228 |
+
assert "[deployed]" in result
|
| 229 |
+
|
| 230 |
+
@pytest.mark.asyncio
|
| 231 |
+
async def test_timeline_empty(self):
|
| 232 |
+
from mcp.server import smriti_timeline
|
| 233 |
+
|
| 234 |
+
client = make_mock_client()
|
| 235 |
+
client.query.return_value = {
|
| 236 |
+
"results": [], "total_found": 0, "query_time_ms": 3.0,
|
| 237 |
+
}
|
| 238 |
+
|
| 239 |
+
ctx = make_mock_context(client)
|
| 240 |
+
result = await smriti_timeline(
|
| 241 |
+
time_range_start="2020-01-01T00:00:00Z",
|
| 242 |
+
ctx=ctx,
|
| 243 |
+
)
|
| 244 |
+
|
| 245 |
+
assert "No events found" in result
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
class TestSmritiForget:
|
| 249 |
+
@pytest.mark.asyncio
|
| 250 |
+
async def test_forget_finds_matches(self):
|
| 251 |
+
from mcp.server import smriti_forget
|
| 252 |
+
|
| 253 |
+
client = make_mock_client()
|
| 254 |
+
client.query.return_value = {
|
| 255 |
+
"results": [
|
| 256 |
+
{
|
| 257 |
+
"event": {
|
| 258 |
+
"id": "evt_old_fact",
|
| 259 |
+
"source_id": "test",
|
| 260 |
+
"subject": "User",
|
| 261 |
+
"verb": "uses",
|
| 262 |
+
"object": "React",
|
| 263 |
+
"timestamp": "2026-06-01T00:00:00Z",
|
| 264 |
+
"confidence": 0.9,
|
| 265 |
+
},
|
| 266 |
+
"relevance_score": 0.8,
|
| 267 |
+
"provenance": "semantic_search",
|
| 268 |
+
}
|
| 269 |
+
],
|
| 270 |
+
"total_found": 1,
|
| 271 |
+
"query_time_ms": 10.0,
|
| 272 |
+
}
|
| 273 |
+
|
| 274 |
+
ctx = make_mock_context(client)
|
| 275 |
+
result = await smriti_forget(query="User uses React", ctx=ctx)
|
| 276 |
+
|
| 277 |
+
assert "Found 1 memory" in result
|
| 278 |
+
assert "smriti_remember" in result
|
| 279 |
+
assert "[User]" in result
|
| 280 |
+
|
| 281 |
+
@pytest.mark.asyncio
|
| 282 |
+
async def test_forget_no_matches(self):
|
| 283 |
+
from mcp.server import smriti_forget
|
| 284 |
+
|
| 285 |
+
client = make_mock_client()
|
| 286 |
+
client.query.return_value = {
|
| 287 |
+
"results": [], "total_found": 0, "query_time_ms": 5.0,
|
| 288 |
+
}
|
| 289 |
+
|
| 290 |
+
ctx = make_mock_context(client)
|
| 291 |
+
result = await smriti_forget(query="nonexistent fact", ctx=ctx)
|
| 292 |
+
|
| 293 |
+
assert "No memories found" in result
|
| 294 |
+
assert "Nothing to forget" in result
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
class TestSmritiHealth:
|
| 298 |
+
@pytest.mark.asyncio
|
| 299 |
+
async def test_health_success(self):
|
| 300 |
+
from mcp.server import smriti_health
|
| 301 |
+
|
| 302 |
+
client = make_mock_client()
|
| 303 |
+
client.health.return_value = {
|
| 304 |
+
"status": "healthy",
|
| 305 |
+
"stores": {
|
| 306 |
+
"postgres_events": 5000,
|
| 307 |
+
"pgvector_embeddings": 4800,
|
| 308 |
+
},
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
ctx = make_mock_context(client)
|
| 312 |
+
result = await smriti_health(ctx=ctx)
|
| 313 |
+
|
| 314 |
+
assert "HEALTHY" in result
|
| 315 |
+
assert "5000" in result
|
| 316 |
+
assert "4800" in result
|
| 317 |
+
|
| 318 |
+
@pytest.mark.asyncio
|
| 319 |
+
async def test_health_fallback(self):
|
| 320 |
+
from mcp.server import smriti_health
|
| 321 |
+
|
| 322 |
+
client = make_mock_client()
|
| 323 |
+
client.health.side_effect = SmritiAPIError(500, "Internal error")
|
| 324 |
+
client.root_health.return_value = {
|
| 325 |
+
"service": "KAAL",
|
| 326 |
+
"version": "0.2.0",
|
| 327 |
+
"status": "operational",
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
ctx = make_mock_context(client)
|
| 331 |
+
result = await smriti_health(ctx=ctx)
|
| 332 |
+
|
| 333 |
+
assert "OPERATIONAL" in result
|
| 334 |
+
assert "0.2.0" in result
|
| 335 |
+
|
| 336 |
+
|
| 337 |
+
class TestSmritiUsage:
|
| 338 |
+
@pytest.mark.asyncio
|
| 339 |
+
async def test_usage_success(self):
|
| 340 |
+
from mcp.server import smriti_usage
|
| 341 |
+
|
| 342 |
+
client = make_mock_client()
|
| 343 |
+
client.usage.return_value = {
|
| 344 |
+
"source_id": "test",
|
| 345 |
+
"tier": "explorer",
|
| 346 |
+
"usage": {
|
| 347 |
+
"events": {"used": 500, "limit": 10000, "remaining": 9500},
|
| 348 |
+
"orchestration": {"used": 10, "limit": 1000, "remaining": 990},
|
| 349 |
+
"connectors": {"used": 1, "limit": 3},
|
| 350 |
+
},
|
| 351 |
+
"limits": {"retention_days": 30},
|
| 352 |
+
"period_start": "2026-07-01T00:00:00Z",
|
| 353 |
+
}
|
| 354 |
+
|
| 355 |
+
ctx = make_mock_context(client)
|
| 356 |
+
result = await smriti_usage(ctx=ctx)
|
| 357 |
+
|
| 358 |
+
assert "EXPLORER" in result
|
| 359 |
+
assert "500" in result
|
| 360 |
+
assert "10,000" in result
|
| 361 |
+
assert "30 days" in result
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
# ── Resource Tests ────────────────────────────────────────────────────────────
|
| 365 |
+
|
| 366 |
+
class TestResources:
|
| 367 |
+
@pytest.mark.asyncio
|
| 368 |
+
async def test_config_resource(self):
|
| 369 |
+
from mcp.server import resource_config
|
| 370 |
+
|
| 371 |
+
with patch.dict(os.environ, {
|
| 372 |
+
"SMRITI_API_KEY": "chrn_test",
|
| 373 |
+
"SMRITI_SOURCE_ID": "test-src",
|
| 374 |
+
}):
|
| 375 |
+
result = await resource_config()
|
| 376 |
+
data = json.loads(result)
|
| 377 |
+
assert data["source_id"] == "test-src"
|
| 378 |
+
assert data["api_key_set"] is True
|
| 379 |
+
assert "api_key" not in data # Should NOT expose the actual key
|
| 380 |
+
|
| 381 |
+
|
| 382 |
+
# ── Prompt Tests ──────────────────────────────────────────────────────────────
|
| 383 |
+
|
| 384 |
+
class TestPrompts:
|
| 385 |
+
@pytest.mark.asyncio
|
| 386 |
+
async def test_memory_chat_prompt(self):
|
| 387 |
+
from mcp.server import memory_chat
|
| 388 |
+
|
| 389 |
+
result = await memory_chat()
|
| 390 |
+
assert "smriti_remember" in result
|
| 391 |
+
assert "smriti_recall" in result
|
| 392 |
+
assert "REMEMBER" in result
|
| 393 |
+
|
| 394 |
+
@pytest.mark.asyncio
|
| 395 |
+
async def test_daily_recap_prompt(self):
|
| 396 |
+
from mcp.server import daily_recap
|
| 397 |
+
|
| 398 |
+
result = await daily_recap()
|
| 399 |
+
assert "smriti_timeline" in result
|
| 400 |
+
assert "recap" in result.lower()
|
| 401 |
+
|
| 402 |
+
@pytest.mark.asyncio
|
| 403 |
+
async def test_knowledge_extraction_prompt(self):
|
| 404 |
+
from mcp.server import knowledge_extraction
|
| 405 |
+
|
| 406 |
+
result = await knowledge_extraction()
|
| 407 |
+
assert "smriti_remember" in result
|
| 408 |
+
assert "extraction" in result.lower()
|