# mcal-ai-langgraph

LangGraph integration for [MCAL](https://github.com/Shivakoreddi/mcal-ai) — Goal-aware memory for AI agents.

## Installation

```bash
pip install mcal-ai-langgraph
```

This will automatically install `mcal-ai` and `langgraph` as dependencies.

## Quick Start

```python
from mcal import MCAL
from mcal_langgraph import MCALStore

# Initialize MCAL
mcal = MCAL(llm_provider="anthropic")

# Create LangGraph-compatible store
store = MCALStore(mcal)

# Use with LangGraph
from langgraph.prebuilt import create_react_agent

agent = create_react_agent(
    model=your_model,
    tools=your_tools,
    store=store  # Goal-aware memory!
)
```

## What's New in 0.4.1

- **First-Class FACT Nodes** — 3 new typed edges (`measures`, `evidence_for`, `quantifies`) improve fact retrieval; quantitative queries automatically boost fact content
- **Importance Scoring Boost** — FACT nodes with numeric values score higher in retrieval
- **`search_facts()` API** — Filter facts by category and value range on `UnifiedGraph`
- **Version Metadata Fix** — `__version__` now correctly reports 0.4.1 (was stuck at 0.2.9)

## What's New in 0.4.0

- **Graph Compaction Fixes** — Improved retrieval quality with facts-in-context, expanded edge types, chunk boost scoring
- **CTO-1020 Benchmark** — 85.3% decision retention over 1020 turns, 95.6% cross-era recall, 88% token reduction
- **Statistical Rigor** — Multi-run validation with Fisher's exact test, Wilson score confidence intervals

## What's New in 0.3.0

- **Expanded Relationship Edge Types** — 10 new edge types (`family`, `friend`, `colleague`, `likes`, `prefers`, `lives_in`, `works_at`, etc.) for richer relationship graphs
- **Key Facts & Entities in Search Context** — `search()` now surfaces extracted facts and background entities directly in `result.context`
- **Improved Chunk Retrieval** — More results returned with equal weighting; conversation excerpts prioritized in context

<details>
<summary>Older releases</summary>

## What's New in 0.2.9

- **Configurable Extraction Profiles** — Choose `decision`, `conversational`, or `comprehensive` via `MCALMemoryConfig`
- **Hybrid Retrieval with ChunkStore** — Graph traversal + embedding search for maximum recall
- **FACT/PERSON Node Protection** — Graph compaction preserves factual and identity nodes

</details>

```python
# Configuration options
memory = MCALMemory(
    llm_provider="anthropic",
    extraction_profile="decision",      # "decision" | "conversational" | "comprehensive"
    enable_chunk_store=True,             # hybrid retrieval
)
```

## Features

### MCALStore (BaseStore)

Drop-in replacement for LangGraph's built-in stores with **goal-aware** memory:

```python
from mcal_langgraph import MCALStore

store = MCALStore(mcal)

# Store memories
await store.aput(
    namespace=("user_123", "memories"),
    key="decision_1",
    value={"text": "Decided to use PostgreSQL for ACID compliance"}
)

# Goal-aware search — returns memories relevant to current goals
results = await store.asearch(
    namespace_prefix=("user_123",),
    query="database choice"
)

# Results include goal context and decisions
for item in results:
    print(item.value)
```

### MCALMemory

Memory nodes for custom LangGraph workflows:

```python
from mcal_langgraph import MCALMemory

# Initialize with provider (uses get_mcal() factory internally)
memory = MCALMemory(llm_provider="anthropic")

# Or pass an existing MCAL instance
memory = MCALMemory(mcal=mcal, user_id="user_123")

# Add as nodes in your graph
graph.add_node("update_memory", memory.update_node())
graph.add_node("get_context", memory.context_node())
```

### MCALCheckpointer

State persistence for LangGraph graphs:

```python
from mcal_langgraph import MCALCheckpointer

checkpointer = MCALCheckpointer(storage_path="~/.mcal")
graph = builder.compile(checkpointer=checkpointer)
```

## Why mcal-ai-langgraph?

| Feature | LangGraph InMemoryStore | **MCALStore** |
|---------|------------------------|---------------|
| BaseStore interface | ✅ | ✅ |
| Namespace organization | ✅ | ✅ |
| TTL support | ❌ | ✅ |
| Filter operators ($eq, $gt, etc.) | ❌ | ✅ |
| **Goal-aware search** | ❌ | ✅ |
| **Decision tracking** | ❌ | ✅ |
| **Intent preservation** | ❌ | ✅ |

## API Reference

### MCALStore

```python
class MCALStore(BaseStore):
    def __init__(self, mcal: MCAL): ...
    
    # Async API
    async def aput(self, namespace, key, value, index=None): ...
    async def aget(self, namespace, key) -> Optional[Item]: ...
    async def adelete(self, namespace, key): ...
    async def asearch(self, namespace_prefix, /, *, query=None, filter=None, limit=10, offset=0) -> list[Item]: ...
    async def alist_namespaces(self, *, prefix=None, suffix=None, max_depth=None, limit=100, offset=0) -> list[tuple[str, ...]]: ...
    
    # Sync API (also available)
    def put(self, namespace, key, value, index=None): ...
    def get(self, namespace, key) -> Optional[Item]: ...
    def delete(self, namespace, key): ...
    def search(self, namespace_prefix, /, *, query=None, filter=None, limit=10, offset=0) -> list[Item]: ...
```

### MCALMemory

```python
class MCALMemory:
    def __init__(
        self,
        mcal: Optional[MCAL] = None,
        llm_provider: str = "anthropic",
        embedding_provider: str = "openai",
        storage_path: Optional[str] = None,
        user_id: str = "default",
        **mcal_kwargs,
    ): ...
    
    def update_node(self) -> Callable: ...
    def context_node(self) -> Callable: ...
    async def add(self, messages, user_id=None): ...
    async def get_context(self, query, user_id=None): ...
    async def search(self, query, user_id=None, limit=5): ...
```

### MCALCheckpointer

```python
class MCALCheckpointer(BaseCheckpointSaver):
    def __init__(self, storage_path: Optional[str] = None): ...
    
    def get(self, config) -> Optional[dict]: ...
    def put(self, config, checkpoint): ...
    def list(self, config) -> list[dict]: ...
```

## Migrating from mcal[langgraph]

If you were using the old extras-based installation:

```python
# Old way (deprecated)
from mcal.integrations.langgraph import MCALStore

# New way (recommended)
from mcal_langgraph import MCALStore
```

The old import path still works but will show a deprecation warning.

## Requirements

- Python >= 3.11
- mcal-ai >= 0.2.0
- langgraph >= 0.2.0
- langchain-core >= 0.3.0

## License

MIT
