# BioSimulagent Python Client

[![PyPI version](https://badge.fury.io/py/bio-simulagent-client.svg)](https://badge.fury.io/py/bio-simulagent-client)
[![Python versions](https://img.shields.io/pypi/pyversions/bio-simulagent-client.svg)](https://pypi.org/project/bio-simulagent-client/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![CI/CD](https://github.com/surplus96/bio-simulagent/workflows/Client%20Tests/badge.svg)](https://github.com/surplus96/bio-simulagent/actions)
[![Documentation Status](https://readthedocs.org/projects/bio-simulagent/badge/?version=latest)](https://bio-simulagent.readthedocs.io/en/latest/?badge=latest)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

Python MCP client library for reproducible research workflows in computational biology.

## Features

- **Async-first API** - Built on asyncio for high performance
- **Type-safe** - Comprehensive type hints and Pydantic validation
- **25 MCP Tools** - Literature, simulation, design, neural modeling
- **4 Workflows** - Ligand optimization, MD simulation, BBB prediction, drug discovery
- **Session Management** - Automatic subprocess lifecycle handling
- **Jupyter Integration** - Magic commands and visualization helpers for notebooks
- **BioSession + `$ref` DSL** *(v0.5.0)* — Reproducible cell-to-cell hand-off without re-typing PDB IDs or SMILES
- **Checkpoint Persistence** *(v0.5.0)* — `SessionStore` save/load + autosave with schema migrations
- **`%bio_pipe` Magic** *(v0.5.0)* — Status table, Mermaid graph, diff, and `$ref` resolver in one command
- **`%bio_suggest` Copilot** *(v0.5.0)* — Claude Opus 4.6-powered next-step suggestions that emit `$ref`s, not literal SMILES
- **Production Ready** - CI/CD, testing, benchmarking, and documentation

## Installation

### Basic Installation

```bash
pip install bio-simulagent-client
```

### With Optional Features

```bash
# Jupyter notebook support (magic commands, visualizations)
pip install bio-simulagent-client[jupyter]

# Visualization tools (NGLView, Plotly, MDTraj)
pip install bio-simulagent-client[viz]

# Advanced scientific computing (scikit-learn, scipy)
pip install bio-simulagent-client[advanced]

# Development tools (pytest, mypy, black, ruff)
pip install bio-simulagent-client[dev]

# Documentation tools (Sphinx, ReadTheDocs theme)
pip install bio-simulagent-client[docs]

# All optional dependencies
pip install bio-simulagent-client[all]
```

### Development Installation

```bash
git clone https://github.com/surplus96/bio-simulagent.git
cd bio-simulagent
pip install -e .[dev]
```

## Quick Start

### Python Script

```python
from bio_simulagent_client import BioSimulagent
import asyncio

async def main():
    # Connect to backend
    async with BioSimulagent.connect() as client:
        # Search literature
        results = await client.literature.search(
            query="EGFR inhibitor",
            max_results=10
        )

        for article in results.articles:
            print(f"{article.title} ({article.year})")

        # Run workflow
        result = await client.workflow.run(
            name="ligand_optimization",
            objective="Design high-affinity EGFR inhibitor",
            params={"target": "EGFR"}
        )
        print(f"Workflow completed: {result.status}")

# Run the async function
asyncio.run(main())
```

### Jupyter Notebook

```python
# Load magic commands
%load_ext bio_simulagent_client.jupyter.magic

# Connect to backend
%bio_connect

# Run workflow with cell magic
%%bio_workflow ligand_optimization
Design EGFR inhibitor with high selectivity

# Visualize molecular structure
from bio_simulagent_client.jupyter.viz import show_molecule
show_molecule("CCO", style="stick")
```

### BioSession + `$ref` DSL *(v0.5.0)*

`BioSession` records every tool call as an immutable step, so the *next*
cell can reference the *previous* cell's output without you copy-pasting
PDB IDs or SMILES strings.

```python
from bio_simulagent_client import BioSimulagent

async with BioSimulagent.connect() as client:
    # Step 1 — find a target structure
    await client.literature.search(query="EGFR kinase domain")

    # Step 2 — design ligands. No PDB ID typed by hand:
    await client.design.optimize(
        target_pdb="$ref:literature.search.top_pdb",
        n_candidates=20,
    )

    # Step 3 — simulate the lead. The lead SMILES comes from step 2's alias:
    await client.simulation.full_pipeline(
        ligand_smiles="$ref:latest.lead_smiles",
        protein_pdb="$ref:literature.search.top_pdb",
    )

    # Inspect the chain
    print(client.session.latest)        # {'lead_smiles': 'CC(=O)...', ...}
    print(client.session.resolve("$auto"))  # full report-ready step list
```

**`$ref` forms:**

| Form | Resolves to |
|---|---|
| `$ref:latest.lead_smiles` | Last successful tool's alias map entry |
| `$ref:step0.result.x` | First recorded step's `result["x"]` |
| `$ref:design.optimize.lead.smiles` | Latest call to `design.optimize`, walked into `lead.smiles` |
| `$ref:metadata.target` | `session.metadata["target"]` |
| `$ref:latest.candidates[0].smiles` | List indexing inside a result |
| `$auto` | Report-ready step list (handy as a single argument to a downstream tool) |
| `\$ref:fake` | Literal escape — passes the string `$ref:fake` through unchanged |

Failed steps **never** update the alias map, so `$ref:latest.X` always
points to the last *successful* result.

### `%bio_pipe` Magic — Notebook Pipeline Inspector *(v0.5.0)*

```python
%load_ext bio_simulagent_client.jupyter.magic
%bio_connect

# … run a few tool calls …

%bio_pipe show              # HTML status table (target, steps, success/error counts)
%bio_pipe graph             # Mermaid graph TD of the step chain
%bio_pipe value $ref:latest.lead_smiles
%bio_pipe diff design.optimize.0 design.optimize.1
%bio_pipe export ./run.json # Inlined JSON for sharing or pasting into a report

# Persist & restore
%bio_pipe save egfr-run-2026-04
%bio_sessions list
%bio_sessions load egfr-run-2026-04
%bio_sessions export egfr-run-2026-04 ./egfr.json
```

### `%bio_suggest` LLM Copilot *(v0.5.0)*

The copilot reads your current `BioSession` and asks Claude Opus 4.6
("adaptive thinking", `temperature=0`, JSON schema output) for the next
deterministic tool call. The system prompt **forbids literal SMILES** —
the copilot must express arguments as `$ref:` references.

```python
import os
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-…"

%bio_suggest next                       # ask for the next step
%bio_suggest next "focus on selectivity"  # with a hint
%bio_suggest apply                      # apply the most recent suggestion
%bio_suggest reject                     # discard it
%bio_suggest history                    # see all suggestions this session
%bio_suggest explain design.optimize.0  # narrate an existing step
```

The copilot is **fail-soft**: if `anthropic` isn't installed or
`ANTHROPIC_API_KEY` is missing, you get a clean `ProviderUnavailableError`
instead of a kernel crash. Set `copilot_enabled = false` in
`ClientConfig` to disable it entirely.

### Example Notebooks

Check out our comprehensive example notebooks:
- [01_getting_started.ipynb](bio_simulagent_client/examples/01_getting_started.ipynb) - Basic usage
- [02_literature_review.ipynb](bio_simulagent_client/examples/02_literature_review.ipynb) - Literature search
- [03_protein_structure.ipynb](bio_simulagent_client/examples/03_protein_structure.ipynb) - Structure prediction
- [04_ligand_design.ipynb](bio_simulagent_client/examples/04_ligand_design.ipynb) - Ligand optimization
- [05_md_simulation.ipynb](bio_simulagent_client/examples/05_md_simulation.ipynb) - MD simulations
- [06_advanced_workflows.ipynb](bio_simulagent_client/examples/06_advanced_workflows.ipynb) - Complete drug discovery pipeline
- [07_custom_analysis.ipynb](bio_simulagent_client/examples/07_custom_analysis.ipynb) - Custom trajectory analysis
- [08_production_tips.ipynb](bio_simulagent_client/examples/08_production_tips.ipynb) - Production best practices

## API Reference

### Client Connection

```python
# Default backend path (backend.main)
client = await BioSimulagent.connect()

# Custom backend
client = await BioSimulagent.connect(
    backend_path="custom.backend",
    python_executable="/path/to/python",
    timeout=30
)

# Context manager (recommended)
async with BioSimulagent.connect() as client:
    # Use client
    pass
```

### Literature Tools

```python
# Search PubMed
results = await client.literature.search(
    query="EGFR inhibitor",
    max_results=10,
    date_from="2020-01-01",
    date_to="2024-12-31"
)

# Get article details
article = await client.literature.get_article(pmid="12345678")

# Get full text
full_text = await client.literature.get_full_text(pmid="12345678")

# Extract biomedical entities
entities = await client.literature.extract_entities(
    text="EGFR mutations in lung cancer"
)
```

### Simulation Tools

```python
# Preprocess structure
config = await client.simulation.preprocess(
    pdb_file="protein.pdb",
    ligand_smiles="CCO"
)

# Run MD simulation
result = await client.simulation.run(
    config=config,
    duration_ns=100,
    temperature=300
)

# Analyze trajectory
analysis = await client.simulation.analyze(
    trajectory_file=result.trajectory_file,
    topology_file=result.topology_file
)
```

### Design Tools

```python
# Predict protein structure
structure = await client.design.predict_structure(
    sequence="MKTAYIAKQRQISFVKSHFSRQ"
)

# Design ligand
ligand = await client.design.design_ligand(
    target_pocket="EGFR_binding_site",
    constraints={"mw_max": 500}
)

# Optimize molecule
optimized = await client.design.optimize(
    smiles="CCO",
    objectives=["binding_affinity", "drug_likeness"]
)
```

### Neural Tools

```python
# Predict BBB permeability
bbb = await client.neural.predict_bbb(smiles="CCO")

# Simulate neural circuit
circuit = await client.neural.simulate_circuit(
    circuit_type="hippocampus",
    stimulation_pattern="theta"
)

# Predict drug effect
effect = await client.neural.drug_effect(
    smiles="CCO",
    target_receptors=["NMDA", "GABA"]
)
```

### Workflows

```python
# Run workflow with progress tracking
def on_progress(step):
    print(f"Step: {step.name} - {step.status}")

result = await client.workflow.run(
    name="ligand_optimization",
    objective="Design EGFR inhibitor",
    params={"target": "EGFR"},
    on_progress=on_progress,
    poll_interval=2.0
)

# Check workflow status
status = await client.workflow.status(workflow_id=result.workflow_id)

# Cancel workflow
await client.workflow.cancel(workflow_id=result.workflow_id)

# List available workflows
workflows = await client.workflow.list_available()
```

## Error Handling

```python
from bio_simulagent_client.exceptions import (
    BioSimulagentError,
    ConnectionError,
    ToolExecutionError,
    ValidationError,
    TimeoutError,
)

try:
    async with BioSimulagent.connect() as client:
        results = await client.literature.search(query="test")
except ConnectionError as e:
    print(f"Failed to connect: {e}")
except ToolExecutionError as e:
    print(f"Tool failed: {e.tool_name} - {e.message}")
except ValidationError as e:
    print(f"Invalid input: {e}")
except TimeoutError as e:
    print(f"Request timeout: {e}")
```

## Testing

```bash
# Run unit tests
pytest tests/

# Run with coverage
pytest --cov=bio_simulagent_client --cov-report=html

# Run integration tests (requires running backend)
RUN_INTEGRATION_TESTS=1 pytest tests/test_integration.py
```

## Development

### Project Structure

```
bio_simulagent_client/
├── __init__.py              # Public API exports
├── client.py                # Main BioSimulagent class
├── exceptions.py            # Custom exceptions
├── types.py                 # Pydantic models
├── workflows.py             # WorkflowRunner
├── _internal/               # Internal modules
│   ├── subprocess_manager.py
│   └── mcp_protocol.py
├── tools/                   # Tool wrappers
│   ├── __init__.py
│   ├── literature.py
│   ├── simulation.py
│   ├── design.py
│   └── neural.py
└── tests/                   # Test suite
    ├── conftest.py
    ├── test_client.py
    ├── test_literature.py
    ├── test_workflows.py
    └── test_integration.py
```

### Code Quality

```bash
# Format code
black bio_simulagent_client/
isort bio_simulagent_client/

# Type checking
mypy bio_simulagent_client/

# Linting
ruff check bio_simulagent_client/
```

## Requirements

- Python 3.9, 3.10, 3.11, or 3.12
- Core dependencies:
  - mcp >=0.9.0
  - pydantic >=2.0
  - aiohttp >=3.9.0

## Roadmap

### Week 1 (Completed)
- ✅ Core client library
- ✅ 25 MCP tool wrappers
- ✅ Workflow execution
- ✅ Unit tests (80% coverage)

### Week 2 (Completed)
- ✅ Jupyter magic commands
- ✅ Visualization helpers
- ✅ 5 basic example notebooks
- ✅ Documentation structure

### Week 3 (Completed) - v0.3.0
- ✅ 3 advanced example notebooks (drug discovery, trajectory analysis, production tips)
- ✅ Integration test suite (4 complete workflows)
- ✅ Performance benchmarking infrastructure
- ✅ Sphinx documentation with ReadTheDocs
- ✅ CI/CD pipeline (testing, docs, PyPI)
- ✅ Production-ready package structure

## License

MIT License - See LICENSE file for details

## Contributing

Contributions welcome! Please see CONTRIBUTING.md for guidelines.

## Links

- **PyPI**: https://pypi.org/project/bio-simulagent-client/
- **Documentation**: https://bio-simulagent.readthedocs.io
- **Source Code**: https://github.com/surplus96/bio-simulagent
- **Issue Tracker**: https://github.com/surplus96/bio-simulagent/issues
- **Changelog**: https://github.com/surplus96/bio-simulagent/blob/master/CHANGELOG.md
