Metadata-Version: 2.4
Name: n1r-cortex
Version: 0.7.0
Summary: N1 Research's Cortex Platform - Runtime-agnostic agent library
Project-URL: Homepage, https://github.com/n1healthcare/n1r-cortex
Project-URL: Repository, https://github.com/n1healthcare/n1r-cortex
Author-email: Arun <arun@n1.care>
License: Proprietary
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Requires-Dist: a2a-sdk[http-server]>=0.3.25
Requires-Dist: ag-ui-protocol>=0.1.13
Requires-Dist: anthropic>=0.69.0
Requires-Dist: fastapi>=0.135.1
Requires-Dist: httpx>=0.28.1
Requires-Dist: langchain-core>=1.2.18
Requires-Dist: langchain-memgraph>=0.1.12
Requires-Dist: langchain-openai>=1.1.6
Requires-Dist: langgraph>=1.1.0
Requires-Dist: mem0ai>=1.0.5
Requires-Dist: n1r-phoenix @ https://artifacts.n1-research.com/n1r-phoenix/n1r_phoenix-5.0.0-py3-none-any.whl
Requires-Dist: opentelemetry-api>=1.40.0
Requires-Dist: opentelemetry-sdk>=1.40.0
Requires-Dist: pydantic-settings>=2.13.1
Requires-Dist: pydantic>=2.12.5
Requires-Dist: pyyaml>=6.0
Requires-Dist: qdrant-client>=1.17.0
Requires-Dist: rank-bm25>=0.2.2
Requires-Dist: redis>=5.0.0
Requires-Dist: structlog>=25.5.0
Provides-Extra: dev
Requires-Dist: black>=26.3.0; extra == 'dev'
Requires-Dist: fakeredis[lua]>=2.34.1; extra == 'dev'
Requires-Dist: isort>=8.0.1; extra == 'dev'
Requires-Dist: mypy>=1.19.1; extra == 'dev'
Requires-Dist: pytest-asyncio>=1.3.0; extra == 'dev'
Requires-Dist: pytest-cov>=7.0.0; extra == 'dev'
Requires-Dist: pytest>=9.0.2; extra == 'dev'
Requires-Dist: ruff<0.15.5,>=0.15.4; extra == 'dev'
Description-Content-Type: text/markdown

# N1R Cortex

A runtime-agnostic agent library.

Cortex handles the hard parts of building AI agents: agent definitions, skill loading, memory with temporal awareness, multi-agent handoffs, streaming, and LLM routing. You write your agent instructions and tools; Cortex handles the infrastructure.

## Installation

```bash
pip install n1r-cortex
```

Requires Python 3.12+. All runtime adapters, memory backends, and telemetry are included.

## Quick Start

Define an agent in `AGENTS.md`:

```markdown
---
name: health-coach
description: Provides personalized health coaching based on biomarker data
model: gemini-3.1-flash-lite-preview
memory: true
skills: [biomarker-reference]
handoffs: [biomarker-analyst]
tools: [tools.biomarkers:fetch_biomarkers]
---

You are a health coach for Twin Healthcare. Use the patient's biomarker
history and dietary preferences to give actionable advice.
```

Create a tool in `tools/biomarkers.py`:

```python
from langchain_core.tools import tool

@tool
def fetch_biomarkers(patient_id: str, panel: str = "all") -> dict:
    """Fetch the latest biomarker data for a patient."""
    return {
        "ldl": {"value": 125, "unit": "mg/dL", "reference": "<100"},
        "hdl": {"value": 55, "unit": "mg/dL", "reference": ">60"},
    }
```

Load and chat:

```python
import asyncio
from n1r.cortex import Agent

async def main():
    agent = Agent.from_md("./agents/health-coach")
    result = await agent.chat("What does my lipid panel look like?", user_id="user-123")
    print(result)

asyncio.run(main())
```

The `tools: [tools.biomarkers:fetch_biomarkers]` in AGENTS.md auto-imports the function.

## What Cortex Handles

| Problem | Solution |
|---------|----------|
| Defining agents as files | AGENTS.md loading (agent identity + instructions) |
| Reusable capabilities | SKILL.md loading ([agentskills.io](https://agentskills.io) spec) |
| Remembering past conversations | 5-scope memory system (user, agent, session, entity, temporal) |
| Preserving clinical history | Temporal graph memory with snapshot-diff-archive |
| Agent-to-agent delegation | RuntimeAdapter handoffs |
| Streaming responses to a frontend | AG-UI protocol mapping over WebSocket |
| Routing to different LLMs | LiteLLM proxy integration |
| Pluggable LLM runtimes | LangChain (default), Anthropic Agents SDK, LangGraph |
| Exposing agents as APIs | FastAPI router with WebSocket + REST |
| Remote agent communication | A2A protocol (opt-in) |

## Core Concepts

### Agents and Skills

Cortex separates **agents** (identity, instructions, personality) from **skills** (reusable capabilities).

**AGENTS.md** defines the agent:

```markdown
---
name: biomarker-analyst
description: Analyzes biomarker data and provides personalized health insights
model: gemini-3.1-flash-lite-preview
memory: true
skills: [lipid-analysis, trend-analysis]
handoffs: [health-coach]
tools: [tools.biomarkers:fetch_biomarkers]
---

You are a biomarker analyst for Twin Healthcare...
```

**SKILL.md** defines a reusable capability:

```markdown
---
name: lipid-analysis
description: Specialized analysis of lipid panel biomarkers
allowed-tools: [fetch_biomarkers]
---

When analyzing lipid panels, follow this protocol...
```

Skills are loaded at startup and injected into the agent's instructions as `<available-skills>` blocks.

### Agent

The primary class. Wraps an AGENTS.md into a runnable agent with memory, streaming, and tools:

```python
from n1r.cortex import Agent

# From an AGENTS.md file
agent = Agent.from_md("./agents/analyst")

# With additional tools
agent = Agent.from_md("./agents/analyst", tools=[extra_tool])

# Chat
result = await agent.chat("Analyze my lipid panel", user_id="user-123")

# Stream (normalized StreamEvents)
async for event in agent.chat_stream("Analyze my lipid panel", user_id="user-123"):
    print(event)
```

### Runtime Adapters

Cortex is runtime-agnostic. Three adapters ship with the library:

| Adapter | When to use |
|---------|-------------|
| **LangChain** (default) | General purpose, broadest tool ecosystem |
| **Anthropic Agents SDK** | Anthropic-native tool use and handoffs |
| **LangGraph** | Complex agent graphs, state machines |

```python
from n1r.cortex.runtimes.anthropic_sdk import AnthropicAdapter

agent = Agent.from_md("./agents/analyst", runtime=AnthropicAdapter())
```

All adapters implement the same `RuntimeAdapter` interface. Memory, telemetry, A2A, and routing work identically regardless of adapter.

### Multi-Agent Handoffs

Agents delegate to other agents via handoffs. The LLM decides when to hand off based on the agent's instructions:

```python
evidence = Agent.from_md("./agents/evidence-synthesizer", tools=[search_pubmed])
analyst = Agent.from_md("./agents/analyst", handoffs=[evidence])

result = await analyst.chat("What does elevated LDL mean?", user_id="user-123")
```

### Memory

On by default. No infrastructure needed for local development -- Qdrant runs embedded, writing to a local directory.

Five scopes:

| Scope | Shared Across | Example |
|-------|---------------|---------|
| **User** | All agents for a user | "User takes vitamin D daily" |
| **Agent** | All replicas of one agent for a user | "Analyzed lipid panel on 3/10" |
| **Session** | Current conversation | Turn-level context |
| **Entity** | Typed Qdrant collections | Structured biomarker records |
| **Temporal** | Graph-level history | LDL: 180 (Jan) -> 120 (Mar) |

Memory is automatically retrieved before each request and captured after each response. A budget-aware context engine compresses memory into a 25-line context block with 5 priority tiers:

1. **Patient summary** -- compressed current state (meds, conditions, labs)
2. **Temporal changes** -- query-relevant trends and archived values
3. **Vector memories** -- semantic search results from Mem0
4. **Agent/session notes** -- agent-scoped working memory
5. **Entity data** -- typed knowledge from Qdrant collections

```python
# Memory on by default
agent = Agent.from_md("./agents/analyst")

# Disable for a specific agent (or set memory: false in AGENTS.md)
agent = Agent(name="stateless-tool", instructions="hi", memory=False)
```

### Temporal Graph Memory

When Memgraph is configured, Cortex preserves clinical history that Mem0 would otherwise destroy.

Mem0 permanently deletes contradicting relationships (LDL 180 -> 120 after statins? Old value gone). Cortex intercepts these deletions with a snapshot-diff-archive pattern:

```
snapshot relations  ->  mem0 client.add()  ->  diff  ->  archive deleted rels
                                                      ->  stamp observed_at
                                                      ->  publish change event
```

Archived relationships become `:Observation` nodes connected via `:HAD` edges. The graph stays traversable and the full history is queryable:

```python
from n1r.cortex.memory import UserMemory

user_mem = UserMemory(client, "user-123", temporal_enricher=enricher)
timeline = await user_mem.get_timeline("ldl_cholesterol")
# [{value: "180_mg_dl", observed_at: Jan}, {value: "120_mg_dl", observed_at: Mar}]

changes = await user_mem.get_changes_since(datetime(2026, 1, 1))
# {"added": [...], "removed": [...]}
```

Four graceful degradation levels:

| Setup | Behavior |
|-------|----------|
| No Memgraph | No temporal features (existing behavior) |
| Memgraph only | Preservation + timestamps, no extraction |
| Memgraph + extraction model | Preservation + smart timestamps from extracted dates |
| Memgraph + extraction model + Redis | Full two-tier temporal system |

With Redis configured, change events are published to a stream for deep enrichment by the [temporal-enricher](https://github.com/n1healthcare/temporal-enricher) service, which builds medication timelines, biomarker trends, condition lifecycles, and causal chains.

### Pipeline

Compose multiple agents sequentially or in parallel:

```python
from n1r.cortex import Agent, Pipeline

extractor = Agent.from_md("./agents/extract")
analyzer = Agent.from_md("./agents/analyze")

# Sequential: output of each feeds into the next
result = await Pipeline.sequential(
    [extractor, analyzer], message="Generate report", user_id="user-123"
)

# Parallel: all agents get the same input
results = await Pipeline.parallel(
    [extractor, analyzer], message="Analyze this data", user_id="user-123"
)
```

### FastAPI Router

Serve any agent as an API:

```python
from fastapi import FastAPI
from n1r.cortex import Agent

app = FastAPI()
agent = Agent.from_md("./agents/analyst", tools=[fetch_biomarkers])
app.include_router(agent.as_router("/chat"))
# WebSocket: /chat/ws     -- AG-UI streaming
# POST:      /chat/sync   -- JSON request/response
# GET:       /chat/health  -- Health check
```

### A2A (Agent-to-Agent Protocol)

Agents can optionally expose themselves via the A2A protocol for remote communication. Set `a2a: true` in AGENTS.md:

```python
agent = Agent.from_md("./agents/analyst", tools=[fetch_biomarkers])
await agent.initialize()

app = FastAPI()
app.include_router(agent.as_router("/analyst"))
agent.mount_a2a(app)
await agent.register_a2a()
```

Consume a remote agent as a handoff target:

```python
from n1r.cortex.a2a.remote import RemoteAgent

remote = RemoteAgent.from_url("analyst", "http://analyst-service/a2a")
coach = Agent.from_md("./agents/coach", handoffs=[remote])
```

## Configuration

Configure via environment variables or `.env` file. External services use standard platform env vars (no prefix). Cortex-internal settings use the `CORTEX_` prefix.

```ini
# LLM Routing (standard platform vars)
OPENAI_BASE_URL=http://litellm:8000
OPENAI_API_KEY=sk-...
CORTEX_DEFAULT_MODEL=gemini-3.1-flash-lite-preview

# Memory
QDRANT_URL=                     # Empty = embedded local, no server needed
CORTEX_MEMORY_LLM_MODEL=gemini-3.1-flash-lite-preview

# Embedding
CORTEX_EMBEDDING_MODEL=embedding-gemma
CORTEX_EMBEDDING_DIMS=768
CORTEX_EMBEDDING_BASE_URL=      # Empty = falls back to OPENAI_BASE_URL

# Graph Memory (optional)
MEMGRAPH_URL=                   # Empty = disabled

# Temporal Extraction (optional, small model for date/event parsing)
CORTEX_TEMPORAL_EXTRACTION_MODEL=       # Empty = disabled
CORTEX_TEMPORAL_EXTRACTION_BASE_URL=    # Empty = falls back to OPENAI_BASE_URL

# Infrastructure
REDIS_URL=redis://localhost:6379/0
CORTEX_SHARED_SKILLS_DIR=./shared-skills

# Billing (standard platform vars)
BILLING_URL=http://billing-service:4444
BILLING_API_KEY=
BILLING_ENABLED=true

# A2A (optional)
CORTEX_A2A_REGISTRY_URL=
```

Or configure programmatically:

```python
from n1r.cortex import CortexConfig, Agent

config = CortexConfig(
    litellm_proxy_url="http://localhost:8000",
    litellm_api_key="sk-test",
    qdrant_url="http://localhost:6333",
)
agent = Agent.from_md("./agents/analyst", config=config)
```

## Examples

- [`examples/biomarker-analyst/`](examples/biomarker-analyst/) -- Single agent with skills
- [`examples/health-report/`](examples/health-report/) -- Multi-agent pipeline with handoffs (4 agents, 2 shared skills)

## Documentation

- [Getting Started](docs/getting-started.md) -- Build your first agent
- [Agents and Skills](docs/agents-and-skills.md) -- AGENTS.md and SKILL.md formats
- [Memory](docs/memory.md) -- How memory works, scopes, and backends
- [Configuration](docs/configuration.md) -- All environment variables and options

## License

Proprietary. Copyright N1 Research LLC.
