Metadata-Version: 2.4
Name: prajnyavan
Version: 0.1.9
Summary: Persistent, emotionally-weighted memory for any LLM
Author: Prajnyavan Team
License: MIT
Project-URL: Homepage, https://github.com/prajnyavan/prajnyavan
Project-URL: Repository, https://github.com/prajnyavan/prajnyavan
Project-URL: Issues, https://github.com/prajnyavan/prajnyavan/issues
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.28
Provides-Extra: openai
Requires-Dist: openai>=1.0; extra == "openai"
Provides-Extra: cohere
Requires-Dist: cohere>=4.0; extra == "cohere"
Provides-Extra: local
Requires-Dist: sentence-transformers>=2.2; extra == "local"
Provides-Extra: uds
Requires-Dist: requests-unixsocket>=0.3; extra == "uds"
Provides-Extra: pydantic
Requires-Dist: pydantic>=2.0; extra == "pydantic"
Provides-Extra: langchain
Requires-Dist: langchain>=0.1; extra == "langchain"
Provides-Extra: llamaindex
Requires-Dist: llama-index>=0.10; extra == "llamaindex"
Provides-Extra: all
Requires-Dist: openai>=1.0; extra == "all"
Requires-Dist: cohere>=4.0; extra == "all"
Requires-Dist: sentence-transformers>=2.2; extra == "all"
Requires-Dist: requests-unixsocket>=0.3; extra == "all"
Requires-Dist: pydantic>=2.0; extra == "all"
Requires-Dist: langchain>=0.1; extra == "all"
Requires-Dist: llama-index>=0.10; extra == "all"

# Prajnyavan - The Memory Layer for AI

Persistent, emotionally-weighted memory for any LLM. Zero-config locally, production-ready when you need it.

---
## Zero-config quickstart (5 lines)
```bash
pip install prajnyavan
prajnyavan dev                   # auto-starts daemon in ~/.prajnyavan
```
```python
from prajnyavan import MemoryClient
mem = MemoryClient.dev(user_id="user_123")   # zero config, hash embeddings

@mem.wrap_llm(user_id="user_123")
def chat(prompt):
    return your_llm_call(prompt)
```
- No Docker or manual tokens locally. Data/log/cache live in ~/.prajnyavan.
- Set OPENAI_API_KEY or COHERE_API_KEY to auto-upgrade embeddings; otherwise hash embeddings keep deps at zero.
- For full features in one go: `pip install "prajnyavan[all]"`.

### One-minute PyPI smoke test
```bash
python - <<'PY'
from prajnyavan import MemoryClient
mem = MemoryClient.dev(user_id="smoke")
mid = mem.store("smoke", "Hello, memory!", importance=0.7)
print("stored", mid)
print("search", mem.search("smoke", "hello", exact_match=True))
print("emotion", mem.get_emotion())
PY
```

### Embedding options
| provider | quality | deps | cost | note |
| --- | --- | --- | --- | --- |
| hash (default) | low | none | $0 | deterministic, zero setup |
| local (sentence-transformers) | medium | torch + model download | $0 | set embedding_provider="local" |
| openai | high | openai>=1.0 | $ | set OPENAI_API_KEY |
| cohere | high | cohere>=4.0 | $ | set COHERE_API_KEY |

wrap_llm caveat: it enriches the prompt. If your function accepts `memory_context` or `enriched_prompt`, it will receive them; otherwise the call signature stays the same.

### CLI helpers
- `prajnyavan status --verbose`  # health, uptime, memory count, cache size
- `prajnyavan logs -n 50`        # tail daemon log
- `prajnyavan export --user alice` / `import --user alice --file backup.json`
- `prajnyavan stop` | `prajnyavan reset`

---
## Works with any LLM
```python
import anthropic
from prajnyavan import MemoryClient

mem = MemoryClient.dev(user_id="user_123")

@mem.wrap_llm(user_id="user_123")
def chat_with_claude(prompt):
    return anthropic.Anthropic().messages.create(
        model="claude-opus-4-6",
        max_tokens=1000,
        messages=[{"role": "user", "content": prompt}]
    ).content[0].text
```

---
## Production / Advanced
- Docker: copy platform binary to /usr/local/bin/prajnyavan_service, set BRAIN_SECRET_KEY, bind 127.0.0.1:9999 by default.
- Env entrypoint: `PRAJNYAVAN_URL`, `PRAJNYAVAN_TOKEN`, optional `PRAJNYAVAN_EMBEDDING_PROVIDER`.
- UDS (Linux/macOS): set PRAJNYAVAN_USE_UDS=1 before `prajnyavan dev` for unix socket transport.

---
## Troubleshooting (8 quick fixes)
1) `prajnyavan dev` hangs -> run `prajnyavan logs -n 50`; then `prajnyavan reset`.
2) 401/403 from client -> MemoryClient.dev() auto-refreshes; or `prajnyavan dev` to restart.
3) Binary missing -> reinstall or let `_daemon` auto-download from GitHub Releases.
4) Port in use -> daemon scans 9999-10008; set PRAJNYAVAN_USE_UDS=1 to avoid ports.
5) OpenAI/Cohere errors -> check API keys; fall back to hash by setting embedding_provider="hash".
6) Slow first call -> local model warms on first use; hash embedding is instant.
7) Log file missing -> start once with `prajnyavan dev`; logs at ~/.prajnyavan/daemon.log.
8) Corrupted dev.json -> delete ~/.prajnyavan/dev.json or run `prajnyavan reset`.

## Security
- BRAIN_SECRET_KEY required in prod; auto-generated per run in dev (tokens rotate on restart).
- dev.json is chmod 600 when possible; contains local bearer token and base_url.
- Use HTTPS/reverse proxy in prod; keep daemon bound to 127.0.0.1 by default.

## Performance baseline
Measured on this machine with release build, perf_targets dataset (hash embeddings):

| Operation       | p50  | p95  | p99  |
|-----------------|------|------|------|
| store()         | 12ms | 13ms | 14ms |
| search() k=5    | 0.3ms| 0.4ms| 0.5ms|
| list_memories() | 12ms | 13ms | 14ms |

Notes: timings are per-op averages derived from the `perf_targets` release tests on this machine (500 episodic writes + 50 reads, 200 semantic writes + 40 reads). Adjust for your hardware; rerun `scripts/bench_and_report.sh` to refresh.

## Contributing
- git clone && pip install -e '.[all]'
- cargo build -p prajnyavan_service
- pytest tests/ -v
- RUN_PRAJNYAVAN_INTEGRATION=1 pytest tests/integration/ -vv
- Good first issues: add Ollama adapter to wrap_llm; export/import CLI enhancements; make PRAJNYAVAN_TOKEN_TTL configurable.

## Python API Reference

### MemoryClient construction

| Method | Description |
|--------|-------------|
| MemoryClient.dev(user_id="developer") | Zero-config local daemon. Auto-starts. |
| MemoryClient.from_env()               | Production: reads PRAJNYAVAN_URL + TOKEN. |

### Storing memories

```python
# Basic store — returns memory id string
id = mem.store("alice", "Loves hiking in Nepal", importance=0.8, category="interests")

# Store with namespace scoping
id = mem.store("alice", "Q3 report done", namespace="work", ttl_days=90)

# Store a structured profile fact (upserts — same key replaces previous value)
mem.store_profile("alice", "role", "Lead Engineer", importance=0.9)

# Batch store
ids = mem.store_batch("alice", [
    {"content": "Likes coffee", "importance": 0.5},
    {"content": "Allergic to cats", "importance": 0.95, "category": "health"},
])
```

### Searching memories

```python
# Basic semantic search
results = mem.search("alice", "outdoor activities", k=5)

# Filters — all optional, combinable
results = mem.search("alice", "work tasks",
    namespace="work",         # scope to namespace
    category="tasks",         # scope to category
    min_importance=0.6,       # only important memories
    valence_bias="positive",  # only positive-emotion memories
    exact_match=True,         # must contain query text (server-side)
)

# result shape: {id, content_text, relevance_score, primary_emotion, importance, tags}
```

### Retrieving and managing memories

```python
mem.get_memory(id)                      # fetch a single memory by UUID
mem.get_related(id, depth=2)            # graph-connected memories (spread activation)
mem.list_memories("alice")              # all memories for a user
mem.list_memories("alice", namespace="work")  # scoped list
mem.export_user("alice")                # full export as list of dicts
mem.update(id, {"importance": 0.9})     # partial update
mem.forget(id, "outdated",              # fine-grained delete
    tombstone_only=False,
    purge_archive=True)
mem.delete_memory(id)                   # simple delete
mem.delete_user("alice")                # delete all memories for user
```

### Analytics and state

```python
mem.highlights("alice", limit=10)       # highest-importance memories
mem.summary("alice", days=7)            # text summary of recent memories
mem.explain("outdoor activities")       # debug: scored source breakdown
mem.stats()                             # daemon performance metrics
mem.get_emotion()                       # global emotional state
mem.brain_status()                      # developmental stage
mem.last_strong_event("alice",          # most emotionally intense memory
    min_arousal=0.7)
```

### LLM context enrichment (decorator)

```python
@mem.wrap_llm(user_id="alice", k=5, valence_bias="positive", debug=False)
def chat(prompt):
    return your_llm_call(prompt)
# Automatically injects relevant memory context into prompt,
# stores the exchange after response.
```

### Knowledge graph

```python
mem.associate(id_a, id_b, edge_type=1, strength=0.9)  # create graph edge
# edge_type: 0=Temporal 1=Semantic 2=Causal 3=Episodic
#            4=Analogical 5=Cooccurrence 6=Contrary 7=PartOf
```

### Maintenance

```python
mem.compact("semantic")   # compact semantic vector store
mem.compact("episodic")   # compact ANN index
mem.compact("graph")      # compact knowledge graph
mem.recover_from_wal()    # replay WAL after crash
```

## Importance and emotion
- importance: float in [0,1]; higher = more salient; currently user-supplied or default from service.
- primary_emotion may be None for neutral text; surface your own emotion scores via tags if needed.

## Emotional context
The brain maintains a continuous emotional state that influences memory retrieval.

```python
state = mem.get_emotion()
# {'primary_emotion': 'joy', 'valence': 0.6, 'arousal': 0.4, 'mood': 'positive'}
```

To store a memory with explicit emotional tone:
```python
mem.store(user_id, "Great meeting today", valence=0.8, arousal=0.5)
```

valence: -1.0 (negative) to 1.0 (positive)  
arousal: 0.0 (calm) to 1.0 (excited)  
primary_emotion: joy, sadness, anger, fear, surprise, disgust, neutral
