RemanenetSpy commited on
Commit
33ef0bb
·
1 Parent(s): d6b7d79

feat: add MCP server and update README for Smriti

Browse files
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Kaal API Backend
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/AI-GPT%20120B%20+%20Llama%203.1-C7AB6B?style=for-the-badge" alt="AI" />
13
- <img src="https://img.shields.io/badge/Free%20Tier-10K%20events%2Fmo-6B7194?style=for-the-badge" alt="Free" />
 
14
  </p>
15
 
16
- <h1 align="center">🕰️ KAAL</h1>
17
- <p align="center"><em>Temporal AI Agent Ecosystem Letters to the Future, for agents.</em></p>
18
  <p align="center">
19
- <strong>Give any AI agent structured, temporal long-term memory.</strong><br>
20
- Kaal decomposes text into Subject-Verb-Object events, stores them across dual calendars,<br>
21
- and lets agents query what happened, when, and why — across any connected SaaS tool.
 
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
- - [API Reference](#api-reference)
 
 
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
- ## Architecture & Deployments
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
- **Note for developers:** When you generate an API key on the Vercel site, you use that key to authenticate your backend requests directly to the Hugging Face API base URL. They are the same system working in tandem.
50
 
51
- ---
52
 
53
- ## What is Smriti?
 
 
 
 
54
 
55
- **The problem:** AI agents are goldfish. They process a request, forget everything, and start from zero next time. No memory of what happened yesterday, last week, or across your other tools.
56
 
57
- **The solution:** KAAL is a **temporal memory API** that any AI agent or SaaS product can plug into. It:
58
-
59
- 1. **Ingests** text from any source (CRM, chat, email, code commits...)
60
- 2. **Decomposes** it into structured Subject-Verb-Object events using AI (Llama 3.1 8B via Groq)
61
- 3. **Stores** events in dual calendars Postgres for temporal queries, pgvector for semantic search
62
- 4. **Retrieves** relevant memories via natural language "What happened with Acme Corp last quarter?"
63
- 5. **Powers** agents that actually remember an AI that knows *your* history
 
64
 
65
  ### Real Example
66
 
67
  ```
68
  INPUT: "Acme Corp signed a $50,000 contract for Q2 2026"
69
 
70
- CHRONOS EXTRACTS:
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 happened with contracts?"
78
- → [2026-04-12] Acme Corp signed a $50,000 contract for Q2 2026
79
  ```
80
 
81
  ---
82
 
83
- ## Architecture
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
- ### File Structure
116
 
117
- ```
118
- chronos-hub/
119
- ├── chronos_core/ 🧠 Memory Core
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
- ### Prerequisites
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
- cd chronos-hub
163
- pip install -r requirements.txt
 
 
164
  ```
165
 
166
- ### Step 2Configure
167
 
168
  ```bash
169
- # Copy the template
170
- cp .env.example .env
171
-
172
- # Edit .env and add your Groq key:
173
- # GROQ_API_KEY=gsk_your_key_here
174
  ```
175
 
176
- ### Step 3 — Start the API
177
 
178
  ```bash
 
 
 
 
179
  python -m uvicorn api.main:app --port 8000
180
  ```
181
 
182
- You should see:
183
- ```
184
- KAAL readyall systems online
185
- Uvicorn running on http://127.0.0.1:8000
186
- ```
187
 
188
- ### Step 4 Start the Dashboard
 
 
189
 
190
  ```bash
191
- # In a second terminal:
192
- cd chronos-ui
193
- npm install
194
- npm run dev
 
 
 
 
195
  ```
196
 
197
- Open **http://localhost:3000** in your browser.
198
 
199
- ### Step 5 — Test It
 
200
 
201
- ```bash
202
- python test_quick.py
 
 
 
 
 
 
 
 
 
 
 
 
203
  ```
204
 
205
- Expected output:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
206
  ```
207
- === GENERATING API KEY ===
208
- Key: chrn_abc123...
209
 
210
- === INGESTING EVENTS ===
211
- Ingested: 4 events
212
- SVO tuples found: 4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
 
214
- === QUERYING MEMORY ===
215
- Found: 4 results in ~80ms
 
 
 
216
 
217
- === ALL TESTS PASSED ===
 
 
 
218
  ```
219
 
 
 
220
  ---
221
 
222
- ## API Reference
223
 
224
  **Base URL:** `https://spy9191-chronos-api-backend.hf.space`
225
- **Auth:** Include your API key in the `Authorization: Bearer <key>` header.
226
- **Docs:** Open `https://spy9191-chronos-api-backend.hf.space/docs` for interactive Swagger UI.
227
 
228
  ---
229
 
230
  ### `POST /billing/keys` — Generate API Key
231
 
232
- No auth required. Creates a new API key.
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": "⚠️ Save this API key now — it cannot be retrieved later."
245
  }
246
  ```
247
 
248
  ---
249
 
250
- ### `POST /ingest` — Feed Events
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 | ✅ | Identifies the data source |
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 Agent
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
- ### `POST /connect` — Register a SaaS Tool
369
 
370
  ```bash
371
- curl -X POST https://spy9191-chronos-api-backend.hf.space/connect \
372
- -H "X-API-Key: chrn_your_key" \
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
- ### `GET /billing/usage` — Check Usage
388
 
389
- ```bash
390
- curl https://spy9191-chronos-api-backend.hf.space/billing/usage \
391
- -H "X-API-Key: chrn_your_key"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  ```
393
 
394
- ---
395
 
396
- ### `GET /health` Health Check
 
 
 
397
 
398
- ```bash
399
- curl https://spy9191-chronos-api-backend.hf.space/health
400
- ```
401
 
402
- ```json
403
- {
404
- "status": "healthy",
405
- "stores": {"postgres_events": 4, "pgvector_embeddings": 4}
406
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
407
  ```
408
 
409
  ---
410
 
411
  ## Dashboard Guide
412
 
413
- The dashboard runs at **http://localhost:3000** and provides a visual interface for all Chronos features.
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 your temporal memory |
421
  | 🔗 **Connect Tool** | Register any SaaS API for agent discovery |
422
  | 📊 **Usage & Billing** | Tier badge, usage meters, pricing table |
423
- | 🔑 **API Keys** | Generate new keys with one click |
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 API key from [console.groq.com](https://console.groq.com) |
 
504
  | `CHRONOS_DB_URL` | No | Postgres DB URL |
505
- | `PGVECTOR_DB_URL` | No | Postgres DB URL for pgvector |
506
  | `API_SECRET_KEY` | No | Secret for signing API keys |
507
- | `RAZORPAY_KEY_ID` | No | Razorpay key ID for billing |
508
- | `RAZORPAY_KEY_SECRET` | No | Razorpay secret key |
509
- | `RAZORPAY_PLAN_BUILDER` | No | Razorpay plan ID for Builder tier |
510
- | `RAZORPAY_PLAN_SCALE` | No | Razorpay plan ID for Scale tier |
511
- | `CHRONOS_API_URL` | No | API URL for dashboard (default: `http://localhost:8000`) |
 
 
 
 
 
 
 
512
 
513
  ### Tech Stack
514
 
515
  | Component | Technology |
516
  |---|---|
517
  | **API Framework** | FastAPI + Uvicorn |
 
518
  | **Structured Storage** | Neon PostgreSQL |
519
- | **Vector Search** | PostgreSQL pgvector + sentence-transformers |
520
  | **Embedding Model** | all-MiniLM-L6-v2 (HuggingFace) |
521
- | **LLM** | GPT OSS 120B & Llama 3.1 8B (Cerebras) |
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 SDK Usage
562
 
563
  ```python
564
  import httpx
565
 
566
- API = "http://localhost:8000"
567
  KEY = "chrn_your_key_here"
568
  HEADERS = {"X-API-Key": KEY}
569
 
570
- # Ingest an event
571
  httpx.post(f"{API}/ingest", headers=HEADERS, json={
572
  "source_id": "my-app",
573
- "events": [{"text": "User completed onboarding on April 12"}]
574
  })
575
 
576
- # Query memory
577
  result = httpx.post(f"{API}/query", headers=HEADERS, json={
578
  "query": "What did the user do?"
579
  })
580
- print(result.json()["results"]) # → onboarding event
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>🕰️ Curated with continuity in mind.</em><br>
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 3Recall 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()