Metadata-Version: 2.4
Name: langchain-persis
Version: 0.1.0
Summary: LangChain integration for Persis - Biological Memory for AI Agents
Author-email: AerwareAI <hello@aerware.ai>
Maintainer-email: Aeryn White <aeryn@aerware.ai>
License: Proprietary
Project-URL: Homepage, https://persis.aerware.ai
Project-URL: Documentation, https://docs.aerware.ai/persis
Project-URL: Repository, https://github.com/aerwareai/persis
Project-URL: Bug Tracker, https://github.com/aerwareai/persis/issues
Project-URL: Changelog, https://github.com/aerwareai/persis/blob/main/CHANGELOG.md
Keywords: langchain,persis,memory,ai,agents,llm,rag,biological-memory,ebbinghaus,hasr
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: langchain-core>=0.1.0
Requires-Dist: requests>=2.31.0
Requires-Dist: pydantic>=1.10.0
Provides-Extra: dev
Requires-Dist: pytest>=7.4.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
Requires-Dist: black>=23.7.0; extra == "dev"
Requires-Dist: mypy>=1.5.0; extra == "dev"
Requires-Dist: ruff>=0.0.287; extra == "dev"
Provides-Extra: examples
Requires-Dist: langchain-anthropic>=0.1.0; extra == "examples"
Requires-Dist: python-dotenv>=1.0.0; extra == "examples"
Dynamic: license-file

# Persis × LangChain Integration

LangChain memory classes powered by Persis's biological memory engine.

## Features

- **Significance-Gated Storage** — Only meaningful interactions are stored (6-factor evaluation)
- **Ebbinghaus Decay** — Memories naturally fade over time unless reinforced
- **Contradiction Detection** — Outdated patterns automatically superseded
- **8-Drive Behavioral System** — Consistent agent personality (curiosity, protection, creation, etc.)
- **Sub-Millisecond Retrieval** — 302μs for 1000 patterns
- **Sleep-Time Consolidation** — Deep memory maintenance during low-activity periods

## Installation

```bash
pip install langchain langchain-anthropic python-dotenv
```

Copy `persis_memory.py` to your project:
```bash
cp /path/to/persis/integrations/langchain/persis_memory.py .
```

## Quick Start

```python
from persis_memory import PersisConversationBufferMemory
from langchain.chains import ConversationChain
from langchain_anthropic import ChatAnthropic

# Initialize memory with personality drives
memory = PersisConversationBufferMemory(
    operator_id="my_agent",
    api_key="your_persis_api_key",  # Optional, for unlimited API calls
    drives={
        "curiosity": 0.8,      # Eager to learn
        "connection": 0.7,     # Empathetic communication
        "truth": 0.9,          # Prioritize accuracy
    }
)

# Create conversation chain
chain = ConversationChain(
    llm=ChatAnthropic(model="claude-sonnet-4-20250514"),
    memory=memory
)

# Chat
response = chain.invoke({"input": "What's the capital of France?"})
print(response["response"])
```

## API Reference

### PersisMemory

Base memory class implementing `BaseChatMessageHistory`.

#### Constructor Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `operator_id` | `str` | **Required** | Unique identifier for this agent/conversation |
| `mcp_url` | `str` | `https://persis-api.aerynw.workers.dev/mcp` | Persis MCP endpoint URL |
| `drives` | `Dict[str, float]` | `{"curiosity": 0.5, "creation": 0.5, "protection": 0.5}` | Drive baselines (0.0-1.0) |
| `api_key` | `Optional[str]` | `None` | API key for authenticated access (bypasses rate limits) |
| `max_patterns` | `int` | `5` | Maximum patterns to recall per query |
| `significance_threshold` | `float` | `0.5` | Minimum significance score to store (0.0-1.0) |
| `return_messages` | `bool` | `True` | Return full message history or just recalled patterns |

#### Methods

**`add_messages(messages: Sequence[BaseMessage]) -> None`**

Add messages to memory with significance-gated pattern formation. Only messages that pass Persis's 6-factor evaluation (drive intensity, binding strength, co-activation, novelty, feedback, emotional intensity) will be stored.

```python
from langchain_core.messages import HumanMessage, AIMessage

memory.add_messages([
    HumanMessage(content="Remember my birthday is June 15th"),
    AIMessage(content="Got it! Your birthday is June 15th.")
])
```

**`messages -> List[BaseMessage]`** (property)

Retrieve relevant messages from Persis memory. Returns recalled patterns as `SystemMessage` objects with strength metadata.

```python
history = memory.messages
for msg in history:
    print(f"{msg.type}: {msg.content}")
```

**`clear() -> None`**

Clear message buffer (does NOT delete patterns from Persis). Patterns decay naturally via Ebbinghaus curve.

**`get_pattern_stats() -> Dict[str, Any]`**

Get statistics about stored patterns.

```python
stats = memory.get_pattern_stats()
print(f"Total patterns: {stats.get('total_patterns', 0)}")
print(f"Average strength: {stats.get('avg_strength', 0):.2f}")
print(f"Strong patterns (>0.7): {stats.get('strong_patterns', 0)}")
```

**`get_drive_state() -> Dict[str, Any]`**

Get current drive values and decision mode.

```python
drives = memory.get_drive_state()
print(f"Decision mode: {drives.get('decision_mode', 'unknown')}")
for drive, value in drives.get('drives', {}).items():
    print(f"  {drive}: {value:.2f}")
```

**`apply_drives_to_prompt(task_type: Optional[str] = None) -> str`**

Get behavioral modifiers for prompt injection.

```python
# General behavioral context
context = memory.apply_drives_to_prompt()

# Task-specific modifiers
code_review_context = memory.apply_drives_to_prompt("code_review")
bug_fix_context = memory.apply_drives_to_prompt("bug_fix")
```

Task types: `code_review`, `bug_fix`, `feature`, `refactor`, `exploration`

**`tick() -> Dict[str, Any]`**

Advance consciousness cycle (HASR oscillation + Ebbinghaus decay + re-clustering). Call periodically (e.g., every 30 seconds or after N interactions).

```python
result = memory.tick()
print(f"Decayed: {result.get('decay_summary', {}).get('decayed', 0)} patterns")
print(f"Pruned: {result.get('decay_summary', {}).get('pruned', 0)} patterns")
```

**`consolidate() -> Dict[str, Any]`**

Run sleep-time consolidation (deep maintenance). Call periodically (e.g., daily or during low-activity periods).

```python
result = memory.consolidate()
print(f"Boosted: {result.get('patterns_boosted', 0)} co-accessed patterns")
print(f"Pruned: {result.get('clusters_pruned', 0)} stale clusters")
```

**`invalidate_pattern(pattern_id: str) -> None`**

Mark a pattern as expired/superseded.

```python
memory.invalidate_pattern("pattern_12345")
```

**`reinforce_pattern(pattern_id: str, outcome: str) -> Dict[str, Any]`**

Reinforce a pattern based on outcome.

```python
# Strengthen a pattern that led to success
result = memory.reinforce_pattern("pattern_12345", "success")

# Weaken a pattern that led to failure
result = memory.reinforce_pattern("pattern_67890", "failure")
```

Outcomes: `success`, `failure`, `neutral`

### PersisConversationBufferMemory

Convenience wrapper that mimics LangChain's `ConversationBufferMemory` API. Drop-in replacement with biological memory capabilities.

#### Additional Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `memory_key` | `str` | `"history"` | Key for memory in chain inputs |
| `input_key` | `Optional[str]` | `None` | Key for input in chain |
| `output_key` | `Optional[str]` | `None` | Key for output in chain |

#### Methods

**`load_memory_variables(inputs: Dict[str, Any]) -> Dict[str, Any]`**

Load memory variables for chain execution.

**`save_context(inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None`**

Save context after chain execution.

## Examples

### Example 1: Basic Conversation

```python
from persis_memory import PersisConversationBufferMemory
from langchain.chains import ConversationChain
from langchain_anthropic import ChatAnthropic

memory = PersisConversationBufferMemory(
    operator_id="basic_agent",
    drives={"curiosity": 0.7, "connection": 0.8}
)

chain = ConversationChain(
    llm=ChatAnthropic(model="claude-sonnet-4-20250514"),
    memory=memory
)

# First conversation
response = chain.invoke({"input": "My name is Alice and I love hiking."})
print(response["response"])

# Later conversation (Persis recalls the pattern)
response = chain.invoke({"input": "What do I enjoy doing?"})
print(response["response"])  # Should mention hiking
```

### Example 2: Custom Drive Profiles

```python
# Creative writing assistant
creative_memory = PersisConversationBufferMemory(
    operator_id="creative_writer",
    drives={
        "curiosity": 0.9,      # Explore new ideas
        "creation": 0.95,      # Generate novel content
        "pattern": 0.3,        # Less focus on consistency
    }
)

# Technical support agent
support_memory = PersisConversationBufferMemory(
    operator_id="support_agent",
    drives={
        "protection": 0.9,     # User safety first
        "truth": 0.95,         # Accurate information
        "connection": 0.8,     # Empathetic communication
    }
)
```

### Example 3: Memory Lifecycle Management

```python
memory = PersisConversationBufferMemory(operator_id="lifecycle_demo")

# Add some interactions
chain = ConversationChain(
    llm=ChatAnthropic(model="claude-sonnet-4-20250514"),
    memory=memory
)

for i in range(10):
    chain.invoke({"input": f"This is message {i}"})

    # Auto-tick every 5 turns
    if i % 5 == 0:
        result = memory.tick()
        print(f"[Tick] Decayed: {result.get('decay_summary', {}).get('decayed', 0)}")

# Deep consolidation after session
consolidation = memory.consolidate()
print(f"Consolidation: {consolidation.get('patterns_boosted', 0)} patterns boosted")
```

### Example 4: Pattern Reinforcement

```python
from langchain_core.messages import HumanMessage, AIMessage

memory = PersisConversationBufferMemory(operator_id="reinforcement_demo")

# Store a pattern
memory.add_messages([
    HumanMessage(content="What's the capital of France?"),
    AIMessage(content="The capital of France is Paris.", metadata={"pattern_id": "paris_fact"})
])

# Later, reinforce based on user feedback
if user_clicked_thumbs_up:
    memory.reinforce_pattern("paris_fact", "success")
elif user_clicked_thumbs_down:
    memory.reinforce_pattern("paris_fact", "failure")
```

### Example 5: Drive-Based Prompt Injection

```python
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder

memory = PersisConversationBufferMemory(
    operator_id="code_reviewer",
    drives={"truth": 0.95, "protection": 0.9, "optimization": 0.7}
)

prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a code reviewer.

{drive_context}

Review the code carefully."""),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

chain = ConversationChain(
    llm=ChatAnthropic(model="claude-sonnet-4-20250514"),
    memory=memory,
    prompt=prompt
)

# Get drive context for this code review task
drive_context = memory.apply_drives_to_prompt("code_review")

response = chain.invoke({
    "input": "Review this function...",
    "drive_context": drive_context
})
```

## Configuration

### Environment Variables

Create a `.env` file:

```bash
# Required
ANTHROPIC_API_KEY=your_anthropic_key_here

# Optional (for unlimited API calls)
PERSIS_API_KEY=your_persis_key_here
```

### Drive Configuration

The 8-drive system controls agent personality:

| Drive | Description | High Value Behavior | Low Value Behavior |
|-------|-------------|---------------------|-------------------|
| `curiosity` | Desire to learn and explore | Asks questions, seeks novelty | Sticks to known patterns |
| `protection` | Safety and risk avoidance | Conservative, validates inputs | Takes more risks |
| `creation` | Generate novel solutions | Innovative, experimental | Conventional, proven methods |
| `connection` | Social bonding and empathy | Warm, personable | Task-focused, direct |
| `pattern` | Consistency and structure | Organized, systematic | Flexible, adaptive |
| `optimization` | Efficiency and refinement | Minimal, elegant solutions | Quick but verbose |
| `truth` | Accuracy and honesty | Precise, fact-checked | Approximations OK |
| `mastery` | Skill development | Perfectionist, thorough | Good enough approach |

Values range from 0.0 (low) to 1.0 (high). Default: 0.5 for all drives.

### Significance Threshold

Controls what gets stored as a pattern:

```python
# Store everything (0.0 = no filtering)
memory = PersisMemory(operator_id="store_all", significance_threshold=0.0)

# Store only important interactions (0.7 = high bar)
memory = PersisMemory(operator_id="selective", significance_threshold=0.7)
```

Default: 0.5 (moderate filtering)

### Max Patterns

Controls how many patterns are recalled per query:

```python
# Recall more context (slower, more expensive)
memory = PersisMemory(operator_id="context_rich", max_patterns=20)

# Recall less context (faster, cheaper)
memory = PersisMemory(operator_id="context_light", max_patterns=3)
```

Default: 5 patterns

### Return Mode

Controls whether full message history is returned:

```python
# Return full conversation buffer + recalled patterns
memory = PersisMemory(operator_id="full", return_messages=True)

# Return only patterns recalled from Persis (no buffer)
memory = PersisMemory(operator_id="patterns_only", return_messages=False)
```

Default: `True` (return full history)

## Troubleshooting

### "Persis MCP error: Rate limit exceeded"

You're hitting the 60 requests/minute rate limit for unauthenticated requests.

**Solution:** Set `PERSIS_API_KEY` environment variable to bypass rate limits.

```python
memory = PersisMemory(
    operator_id="my_agent",
    api_key=os.getenv("PERSIS_API_KEY")  # Unlimited requests
)
```

### "No patterns recalled"

Patterns may not have passed significance gating, or context doesn't match stored patterns.

**Solutions:**
1. Lower significance threshold: `significance_threshold=0.3`
2. Check pattern stats: `memory.get_pattern_stats()`
3. Store patterns explicitly with high significance context

### "Memory not persisting across sessions"

Each `operator_id` creates a separate memory space. Ensure you're using the same ID.

```python
# Wrong (creates new memory each run)
operator_id = f"agent_{datetime.now().timestamp()}"

# Correct (reuses memory)
operator_id = "my_persistent_agent"
```

### "Patterns decay too quickly"

Adjust Ebbinghaus decay rate or reinforce important patterns.

```python
# Reinforce patterns that were useful
result = memory.reinforce_pattern(pattern_id, "success")

# Run consolidation less frequently (lets patterns stabilize)
memory.consolidate()  # Call daily instead of hourly
```

### "Connection timeout"

MCP endpoint may be unreachable or slow.

**Solutions:**
1. Check internet connection
2. Verify `mcp_url` is correct
3. Self-host Persis if reliability is critical (see main README)

## Advanced Usage

### Custom MCP Endpoint

Self-host Persis and point to your instance:

```python
memory = PersisMemory(
    operator_id="my_agent",
    mcp_url="https://my-persis-instance.com/mcp"
)
```

### Multiple Agents with Shared Memory

```python
# Agent 1: Research assistant
research_memory = PersisMemory(operator_id="shared_memory")

# Agent 2: Writing assistant (same operator_id)
writing_memory = PersisMemory(operator_id="shared_memory")

# Both agents access the same pattern store
```

### Monitoring Memory Health

```python
import time

while True:
    stats = memory.get_pattern_stats()
    drives = memory.get_drive_state()

    print(f"Patterns: {stats.get('total_patterns', 0)}")
    print(f"Avg strength: {stats.get('avg_strength', 0):.2f}")
    print(f"Decision mode: {drives.get('decision_mode', 'unknown')}")

    time.sleep(60)  # Check every minute
```

## License

© 2026 AerwareAI - Proprietary

See Persis main README for licensing information.
