# AgentChain 🔗

A minimal, zero-dependency Python library for building multi-agent workflows.

**Fluent API** • **Custom Agents** • **Per-Agent Models** • **Event Hooks** • **PyPI Ready**

---

## Installation

```bash
pip install agentchain
```

Or install from source:

```bash
git clone https://github.com/yourusername/agentchain
cd agentchain
pip install -e .
```

## Quick Start

```python
from agentchain import AgentChain

AgentChain() \
    .requirements("Build a calculator app with add, subtract, multiply, divide") \
    .model("deepseek-coder-v2:16b") \
    .output("./calculator") \
    .run()
```

That's it. AgentChain coordinates multiple AI agents to plan and implement your project.

---

## Core Concepts

### The Default Workflow

AgentChain ships with 3 agents that form a complete development pipeline:

| Agent | Role | Responsibility |
|-------|------|----------------|
| **Contractor** | Orchestrator | Coordinates the workflow, handles retries, verifies completion |
| **Planner** | Planning | Breaks requirements into concrete, ordered tasks |
| **Coder** | Implementation | Writes code to files based on task specifications |

### Fluent API

Configure everything with method chaining:

```python
AgentChain()
    .requirements("Your project description")  # What to build
    .model("llama3:8b")                         # Default model for all agents
    .configure("planner", model="llama3:70b")   # Planner gets a bigger model
    .configure("coder", model="deepseek-coder-v2:16b")  # Coder is specialized
    .output("./my_project")                     # Where to write files
    .run()
```

---

## Configuration

### Per-Agent Models

Use the right model for each task:

```python
AgentChain()
    .requirements("Build a REST API")
    .configure("planner", model="llama3:70b")           # Planning: bigger model
    .configure("coder", model="deepseek-coder-v2:16b")  # Coding: code-focused model
    .output("./api")
    .run()
```

### Agent Parameters

Pass custom parameters to specific agents:

```python
AgentChain()
    .requirements("Build a game")
    .configure("planner", 
        model="llama3:70b",
        max_tasks=20,           # Custom parameter
        include_tests=True      # Custom parameter
    )
    .configure("coder",
        model="deepseek-coder-v2:16b",
        style="verbose",        # Custom parameter
        language="python"       # Custom parameter
    )
    .output("./game")
    .run()
```

### LLM Configuration

Configure the LLM endpoint:

```python
from agentchain import ChainConfig

config = ChainConfig(
    llm_base_url="http://localhost:11434",  # Ollama default
    default_model="llama3:8b",
)

AgentChain(config)
    .requirements("Build something")
    .output("./output")
    .run()
```

---

## Custom Agents

### Function-Based Agents

The simplest way to add an agent:

```python
from agentchain import AgentChain, agent, AgentContext, AgentResult

@agent(name="reviewer", role="Code Reviewer")
def review_code(context: AgentContext, llm) -> AgentResult:
    code = context.get("code", "")
    
    response = llm.generate(
        "Review this code for issues:\n\n" + code,
        model="llama3:8b"
    )
    
    return AgentResult.ok({"review": response})


# Use it
AgentChain()
    .requirements("Build a CLI tool")
    .register("reviewer", review_code)  # Add to this chain
    .output("./cli")
    .run()
```

### Class-Based Agents

For more complex agents with state:

```python
from agentchain import Agent, agent, AgentContext, AgentResult

@agent(name="tester", role="Test Writer")
class TesterAgent(Agent):
    def execute(self, context: AgentContext) -> AgentResult:
        code_files = context.get("files", [])
        
        for file in code_files:
            test_code = self._llm.generate(
                f"Write tests for:\n{file['content']}",
                model=self._config.get("model", "llama3:8b")
            )
            # Write test file...
        
        return AgentResult.ok({"tests_created": len(code_files)})
```

### Custom Orchestrator

Replace the entire workflow:

```python
from agentchain import Orchestrator, orchestrator, AgentContext, AgentResult

@orchestrator(name="my_workflow", role="Custom Pipeline")
class MyOrchestrator(Orchestrator):
    def execute(self, context: AgentContext) -> AgentResult:
        # Phase 1: Your planning logic
        plan_result = self.invoke_agent("planner", context)
        
        # Phase 2: Your implementation logic
        for task in plan_result.data.get("tasks", []):
            self.invoke_agent("coder", AgentContext(
                task=task["description"],
                inputs=task
            ))
        
        # Phase 3: Your custom phase
        if self.get_agent("tester"):
            self.invoke_agent("tester", context)
        
        return AgentResult.ok({"status": "complete"})


# Use it
AgentChain()
    .requirements("Build something")
    .orchestrator(MyOrchestrator)
    .output("./output")
    .run()
```

---

## Events & Callbacks

Monitor progress with event hooks:

```python
from agentchain import AgentChain, Event

def on_progress(event_type, data):
    print(f"[{event_type}] {data.get('message', '')}")

def on_task_complete(event_type, data):
    task = data.get("task", {})
    print(f"✓ Completed: {task.get('name')}")

AgentChain()
    .requirements("Build an app")
    .on(Event.PROGRESS, on_progress)
    .on(Event.TASK_COMPLETE, on_task_complete)
    .on(Event.AGENT_ERROR, lambda e, d: print(f"Error: {d}"))
    .output("./app")
    .run()
```

### Available Events

| Event | Trigger |
|-------|---------|
| `Event.CHAIN_START` | Chain execution begins |
| `Event.CHAIN_COMPLETE` | Chain execution finishes |
| `Event.AGENT_START` | An agent starts executing |
| `Event.AGENT_COMPLETE` | An agent finishes |
| `Event.AGENT_ERROR` | An agent encounters an error |
| `Event.TASK_START` | A task begins |
| `Event.TASK_COMPLETE` | A task completes |
| `Event.TASK_FAILED` | A task fails |
| `Event.PROGRESS` | General progress update |

---

## CLI Usage

AgentChain includes a command-line interface:

```bash
# Basic usage
agentchain "Build a todo app with Flask" --output ./todo-app

# With specific model
agentchain "Build a calculator" --model deepseek-coder-v2:16b --output ./calc

# Verbose output
agentchain "Build a game" --output ./game --verbose
```

---

## API Reference

### AgentChain

The main entry point for building chains.

```python
class AgentChain:
    def requirements(self, text: str) -> AgentChain
    def model(self, name: str) -> AgentChain
    def configure(self, agent_name: str, **kwargs) -> AgentChain
    def output(self, directory: str) -> AgentChain
    def orchestrator(self, cls: Type[Orchestrator]) -> AgentChain
    def register(self, name: str, agent: Agent | Callable) -> AgentChain
    def on(self, event: Event, handler: Callable) -> AgentChain
    def run(self) -> AgentResult
```

### AgentContext

Data passed to agents:

```python
class AgentContext:
    task: str          # Current task description
    inputs: dict       # Input data from previous agents
    workspace: str     # Output directory path
    
    def get(self, key: str, default=None) -> Any
```

### AgentResult

Return value from agents:

```python
class AgentResult:
    success: bool      # Whether execution succeeded
    data: dict         # Output data
    error: str | None  # Error message if failed
    
    @classmethod
    def ok(cls, data: dict) -> AgentResult
    
    @classmethod
    def fail(cls, error: str) -> AgentResult
```

---

## Requirements

- **Python**: 3.9+
- **LLM Backend**: Ollama running at `localhost:11434` (configurable)
- **Dependencies**: None (stdlib only!)

---

## Examples

See the [examples/](./examples) directory:

- [minimal.py](./examples/minimal.py) - Basic usage
- [multi_model.py](./examples/multi_model.py) - Different models per agent
- [custom_agent.py](./examples/custom_agent.py) - Creating custom agents
- [custom_orchestrator.py](./examples/custom_orchestrator.py) - Custom workflow

---

## License

MIT License - see [LICENSE](./LICENSE)

---

## Contributing

Contributions welcome! Please read our contributing guidelines first.

```bash
# Development install
pip install -e ".[dev]"

# Run tests
pytest

# Format code
black src/
```
