streamlit-langgraph 0.2.1
A Streamlit package for building multiagent web interfaces with LangGraph
License: MIT
Requires Python: >=3.10
Keywords: streamlit langgraph multiagent ai chatbot llm

# streamlit-langgraph

[![PyPI version](https://badge.fury.io/py/streamlit-langgraph.svg)](https://badge.fury.io/py/streamlit-langgraph)

A Python package that integrates Streamlit's intuitive web interface with LangGraph's advanced multi-agent orchestration. Build interactive AI applications featuring multiple specialized agents collaborating in customizable workflows.

If you're using Streamlit with a single agent, consider [streamlit-openai](https://github.com/sbslee/streamlit-openai/tree/main) instead. This project is inspired by that work, especially its integration with the OpenAI Response API.

**streamlit-langgraph** is designed for multi-agent systems where multiple specialized agents collaborate to solve complex tasks.

## Table of Contents

- [Main Goal](#main-goal)
- [Status](#status)
  - [Supported LLM Providers](#supported-llm-providers)
- [Installation](#installation)
- [API Key Configuration](#api-key-configuration)
- [Quick Start](#quick-start)
- [Examples](#examples)
  - [Simple Single Agent](#simple-single-agent)
  - [File Preprocessing Callback](#file-preprocessing-callback)
  - [Supervisor Sequential](#supervisor-sequential)
  - [Supervisor Parallel](#supervisor-parallel)
  - [Hierarchical Workflow](#hierarchical-workflow)
  - [Network Workflow](#network-workflow)
  - [Human-in-the-Loop](#human-in-the-loop)
  - [MCP Tools](#mcp-tools)
- [Package Structure](#package-structure)
- [Core Logic](#core-logic)
  - [Section and Block System](#section-and-block-system)
  - [Workflow State as Single Source of Truth](#workflow-state-as-single-source-of-truth)
  - [Streamlit Session State Usage](#streamlit-session-state-usage)
- [Core Concepts](#core-concepts)
  - [Agent Configuration](#agent-configuration)
  - [UI Configuration](#ui-configuration)
  - [Workflow Patterns](#workflow-patterns)
  - [Executor Architecture](#executor-architecture)
  - [Conversation History Modes](#conversation-history-modes)
  - [Context Modes](#context-modes)
  - [Human-in-the-Loop](#human-in-the-loop-hitl)
  - [Custom Tools](#custom-tools)
  - [MCP (Model Context Protocol)](#mcp-model-context-protocol)
- [API Reference](#api-reference)
  - [Agent](#agent)
  - [UIConfig](#uiconfig)
  - [LangGraphChat](#langgraphchat)
  - [WorkflowBuilder](#workflowbuilder)
  - [WorkflowBuilder.SupervisorTeam](#workflowbuildersupervisorteam)
  - [CustomTool](#customtool)
- [License](#license)

## Main Goal

To build successful multi-agent systems, defining agent instructions, tasks, and context is more important than the actual orchestration logic. As illustrated by:

**[LangChain - Customizing agent context](https://docs.langchain.com/oss/python/langchain/multi-agent#customizing-agent-context)**:
> At the heart of multi-agent design is **context engineering** - deciding what information each agent sees... The quality of your system **heavily depends** on **context engineering**.

**[CrewAI - The 80/20 Rule](https://docs.crewai.com/en/guides/agents/crafting-effective-agents#the-80%2F20-rule%3A-focus-on-tasks-over-agents)**:
> 80% of your effort should go into designing tasks, and only 20% into defining agents... well-designed tasks can elevate even a simple agent.

With that in mind, this package is designed so users can focus on defining agents and tasks, rather than worrying about agent orchestration or UI implementation details.

**Key Features:**

1. **Seamless Integration of Streamlit and LangGraph:** Combine Streamlit's rapid UI development, which turns simple Python scripts into interactive web applications, with LangGraph's flexible agent orchestration for real-time interaction.

2. **Lowering the Barrier to Multi-Agent Orchestration:** Simplify multi-agent development with easy-to-use interfaces that abstract away LangGraph's complexity.

3. **Ready-to-Use Multi-Agent Architectures:** Include standard patterns (supervisor, hierarchical, network) out of the box.

4. **Fully support OpenAI Responses API unlike the partial support of LangChain:** Automatically configures OpenAI's Responses API when native tools are enabled. LangChain's ChatOpenAI supports only basic native tool features and lacks support for partial image generation, real-time code interpreter output, and several other advanced functionalities. To provide a true live experience, I separately integrated the Responses API while maintaining compatibility with other LangChain features.

5. **Extensibility to Other LLMs:** Not limited to OpenAI, the framework is designed to support additional LLM providers such as Gemini, Claude, and others by utilizing LangChain and manual adaptations as needed, similar to the approach used for OpenAI's Response API.

## Status

This project is in **alpha**. Features and APIs are subject to change.

**Note:** Uses `langchain`/`langgraph` version `1.0.1`.

### Supported LLM Providers

| Provider | Support | Notes |
|----------|---------|-------|
| **OpenAI** | ✅ | Uses **ResponseAPIExecutor** (Responses API) when native tools enabled and HITL disabled. Uses **CreateAgentExecutor** (ChatCompletion API) for HITL or when native tools disabled. |
| **Anthropic (Claude)** | ❓ | May work but not explicitly tested. |
| **Google (Gemini)** | ✅ | Via LangChain's `init_chat_model` and `langchain-google-genai`. Web search and code interpreter only; see notes below. |
| **Other LangChain Providers** | ❓ | May work but not explicitly tested.|

**Legend:**
- ✅ **O** = Fully supported and tested
- ❌ **X** = Not supported
- ❓ **?** = May work but not explicitly tested

**Notes:**
- **OpenAI**: Automatically selects ResponseAPIExecutor (Responses API) or CreateAgentExecutor (ChatCompletion API) based on native tool configuration and HITL settings
  - ResponseAPIExecutor: Used when native tools enabled and HITL disabled
  - CreateAgentExecutor: Used for HITL scenarios or when native tools are disabled
- **Google (Gemini)**: Supported via LangChain (`langchain-google-genai`). Currently supported: **web search** (grounding/citations) and **code interpreter**. OpenAI equivalents for file search and image generation are not yet supported and may be added in a later release.
- Support depends on LangChain's provider compatibility

## Installation

**Using pip**:

```bash
pip install streamlit-langgraph
# For Google Gemini support:
pip install langchain-google-genai
```

**Using UV**:

[UV](https://github.com/astral-sh/uv) is a fast Python package installer and resolver:
```bash
uv pip install streamlit-langgraph
```

Or if you're using UV for project management:
```bash
uv add streamlit-langgraph
```

## API Key Configuration

Before running your application, you need to configure your API keys. Create a `.streamlit/secrets.toml` file in your project root directory:

```toml
OPENAI_API_KEY = "your-openai-api-key-here"
GOOGLE_API_KEY = "your-google-api-key-here"
```

**File structure:**

```
your-project/
├── .streamlit/
│   └── secrets.toml
├── your_app.py
└── ...
```

## Quick Start

**Create YAML File**:

First, create a YAML configuration file:

```yaml
# configs/agent.yaml
- name: assistant
  role: AI Assistant
  instructions: You are a helpful AI assistant.
  provider: openai
  model: gpt-4.1-mini
```

**Create Your App**:

Then use it in your app:

```python
# your_app.py

import os
import streamlit as st
import streamlit_langgraph as slg

# Load agent from YAML (automatically loads for single agent)
config_path = os.path.join(os.path.dirname(__file__), "configs/agent.yaml")
assistant = slg.Agent(config_path)

# Configure UI
config = slg.UIConfig(
    title="My AI Assistant",
    welcome_message="Hello! How can I help you today?"
)

# Create and run chat interface
if "chat" not in st.session_state:
    st.session_state.chat = slg.LangGraphChat(
        agents=[assistant],
        config=config
    )
st.session_state.chat.run()
```

**Run your app**:

```bash
streamlit run your_app.py
```

## Examples

All examples are in the `examples/` directory.

### Simple Single Agent

**File**: `examples/01_basic_simple_example.py`

Basic chat interface with a single agent. No workflow orchestration.

```bash
streamlit run examples/01_basic_simple_example.py
```

### File Preprocessing Callback

**File**: `examples/06_feature_file_callback_example.py`

Demonstrates how to use the `file_callback` parameter to preprocess uploaded files before they are sent to OpenAI. The callback receives the file path and returns a processed file path, or optionally a tuple with additional files.

```bash
streamlit run examples/06_feature_file_callback_example.py
```

**Features**:
- Preprocess files (e.g., filter CSV columns) before upload
- Works with single agent and multi-agent workflows
- Support for returning additional files generated during preprocessing
- Automatically uploads additional CSV files to code_interpreter container

**Example - Simple Preprocessing**:
```python
import pandas as pd
import streamlit_langgraph as slg

def filter_columns(file_path: str) -> str:
    """Filter CSV to keep only columns starting with 'num_'."""
    if not file_path.endswith('.csv'):
        return file_path
    
    df = pd.read_csv(file_path)
    num_cols = [col for col in df.columns if col.startswith('num_')]
    df_filtered = df[num_cols] if num_cols else df
    
    processed_path = file_path.replace('.csv', '_filtered.csv')
    df_filtered.to_csv(processed_path, index=False)
    return processed_path

config = slg.UIConfig(
    title="File Preprocessing Example",
    file_callback=filter_columns,
)
```

**Example - Preprocessing with Additional Files**:
```python
from pathlib import Path
import streamlit_langgraph as slg

def preprocess_with_additional_files(file_path: str):
    """
    Preprocess file and generate additional CSV files.
    
    Returns:
        Tuple of (main_file_path, additional_files_directory) or
        Tuple of (main_file_path, [list_of_file_paths])
    """
    # Process the main file
    processed_path = process_main_file(file_path)
    
    # Generate additional CSV files in a directory
    output_dir = Path("outputs") / "generated_csvs"
    output_dir.mkdir(parents=True, exist_ok=True)
    
    # Generate multiple CSV files
    generate_csv_files(output_dir)
    
    # Return tuple: (main file, directory with additional files)
    # All CSV files in the directory will be automatically uploaded
    return (processed_path, output_dir)

config = slg.UIConfig(
    title="Multi-File Preprocessing Example",
    file_callback=preprocess_with_additional_files,
)
```

### Supervisor Sequential

**File**: `examples/02_workflow_supervisor_sequential_example.py`

Supervisor coordinates workers sequentially. Workers execute one at a time with full context.

**Config**: `examples/configs/supervisor_sequential.yaml`

```bash
streamlit run examples/02_workflow_supervisor_sequential_example.py
```

### Supervisor Parallel

**File**: `examples/03_workflow_supervisor_parallel_example.py`

Supervisor delegates tasks to multiple workers who can work in parallel.

**Config**: `examples/configs/supervisor_parallel.yaml`

```bash
streamlit run examples/03_workflow_supervisor_parallel_example.py
```

### Hierarchical Workflow

**File**: `examples/04_workflow_hierarchical_example.py`

Multi-level organization with top supervisor managing sub-supervisor teams.

**Config**: `examples/configs/hierarchical.yaml`

```bash
streamlit run examples/04_workflow_hierarchical_example.py
```

### Network Workflow

**File**: `examples/05_workflow_network_example.py`

Peer-to-peer network pattern where agents can communicate directly with any other agent. No central supervisor - agents form a mesh topology and can hand off work to any peer.

**Config**: `examples/configs/network.yaml`

```bash
streamlit run examples/05_workflow_network_example.py
```

**Features**:
- True peer-to-peer collaboration
- Agents can hand work back and forth dynamically
- No central coordinator - all agents are peers
- First agent in the list serves as the entry point
- Best for: Complex scenarios with interdependent concerns

**Use Case**: Strategic consulting teams where specialists need to collaborate dynamically, with work flowing back and forth as issues are identified and re-evaluated.

### Human-in-the-Loop

**File**: `examples/07_feature_human_in_the_loop_example.py`

Demonstrates HITL with tool execution approval. Users can approve, reject, or edit tool calls before execution.

**Config**: `examples/configs/human_in_the_loop.yaml`

```bash
streamlit run examples/07_feature_human_in_the_loop_example.py
```

**Features**:
- Custom tools with approval workflow
- Sentiment analysis example
- Review escalation with edit capability

### MCP Tools

**File**: `examples/08_feature_mcp_example.py`

Demonstrates integration with MCP (Model Context Protocol) servers to access external tools and resources.

```bash
streamlit run examples/08_feature_mcp_example.py
```

**Prerequisites**:
```bash
pip install fastmcp langchain-mcp-adapters
```

**Features**:
- Connect to MCP servers via stdio or HTTP transport
- Access tools from external MCP servers
- Works with both ResponseAPIExecutor and CreateAgentExecutor
- Example MCP servers included (math, weather)

**MCP Server Examples**:
- `examples/mcp_servers/math_server.py` - Math operations (add, multiply, subtract, divide)
- `examples/mcp_servers/weather_server.py` - Weather information

## Package Structure

This section provides an overview of the package's internal organization and module structure.

### Top-Level Modules

- **`agent.py`**: `Agent` class for agent configuration
- **`chat.py`**: `LangGraphChat` main interface and `UIConfig` for UI settings
- **`workflow/`**: Workflow builders and patterns (supervisor, hierarchical, network)

### Core Modules (`core/`)

Core contains no Streamlit dependencies.

**Executor (`core/executor/`):**
- `response_api.py`: `ResponseAPIExecutor` for OpenAI Responses API
- `create_agent.py`: `CreateAgentExecutor` for LangChain agents with HITL support
- `extractors.py`: Pure text/reasoning extraction helpers for executor outputs
- `registry.py`: `ExecutorRegistry` for automatic executor selection
- `workflow.py`: `WorkflowExecutor` for workflow execution
- `history/`: Conversation history tracking (data + mixin)

**History (`core/history/`):**
- `models.py`: `HistoryBlock` and `HistorySection` data structures
- `tracker.py`: `ConversationHistoryMixin` for history tracking

**Runtime (`core/runtime.py`):**
- `RuntimeHooks`: Internal runtime dependencies (executor registry, stream renderer, spinner)

**State (`core/state/`):**
- `state_schema.py`: `WorkflowState` TypedDict and `WorkflowStateManager`

**Middleware (`core/middleware/`):**
- `hitl.py`: `HITLUtils` for human-in-the-loop data utilities
- `interrupts.py`: `InterruptManager` for interrupt handling

### UI Modules (`ui/`)

Streamlit-specific rendering, state adapters, and HITL UX.

- `display_manager.py`: `DisplayManager`, `Section`, and `Block` for UI rendering
- `hitl_handler.py`: Streamlit HITL UI/UX handler
- `nonstream_processor.py`: Non-streamed response rendering helpers
- `stream_processor.py`: `StreamProcessor` for handling streaming responses
- `stream_renderer.py`: Streamlit stream renderer used internally by `RuntimeHooks`
- `streamlit_state.py`: Streamlit session state adapter

### Utility Modules (`utils/`)

- `file_handler.py`: `FileHandler` for file upload and processing
- `custom_tool.py`: `CustomTool` registry for custom tools
- `mcp_tool.py`: `MCPToolManager` for MCP server integration

### Workflow Modules (`workflow/`)

Workflow graph construction, patterns, and agent node factories.

- `builder.py`: `WorkflowBuilder` for creating workflows
- `patterns/`: Workflow pattern implementations (supervisor, hierarchical, network)
- `agent_nodes/`: Agent node factories and delegation patterns

## Core Logic

This section explains the internal architecture for rendering messages and managing state.

### Section and Block System

All chat messages are rendered through a **Section/Block** architecture:

- **Section**: Represents a single chat message (user or assistant). Contains multiple blocks.
- **Block**: Individual content units within a section:
  - `text`: Plain text content
  - `code`: Code blocks (collapsible)
  - `reasoning`: Reasoning/thinking blocks (collapsible)
  - `image`: Image content
  - `download`: Downloadable files

**Flow**:
1. User input → Creates a `Section` with `text` block
2. Agent response → Creates a `Section` with blocks based on content type
3. Streaming → Updates existing blocks or creates new ones as content arrives
4. All sections/blocks are saved to `workflow_state` for persistence

### Workflow State as Single Source of Truth

`workflow_state` is the **single source of truth** for all chat history and application state:

**Structure**:
```python
workflow_state = {
    "messages": [...],           # Conversation messages (user/assistant)
    "metadata": {
        "display_sections": [...], # UI sections/blocks for rendering
        "pending_interrupts": {...}, # HITL state
        "executors": {...},        # Executor metadata
        ...
    },
    "agent_outputs": {...},      # Agent responses by agent name
    "current_agent": "...",       # Currently active agent
    "files": [...]               # File metadata
}
```

**Key Points**:
- **All messages** (user and assistant) are stored in `workflow_state["messages"]`
- **All UI sections/blocks** are stored in `workflow_state["metadata"]["display_sections"]`
- **State persistence**: Workflow state persists across Streamlit reruns
- **Workflow execution**: LangGraph workflows read from and write to `workflow_state`
- **State synchronization**: `StateSynchronizer` manages updates to `workflow_state`
- **No fallbacks**: All state operations require `state_manager` - no direct `session_state` access for display sections

### Streamlit Session State Usage

`st.session_state` is used for **display management** and **runtime state**:

**Display Management**:
- `workflow_state`: The single source of truth (stored in session state for Streamlit persistence)
- `display_sections`: **Deprecated** - now stored in `workflow_state.metadata.display_sections`
- `agent_executors`: Runtime executor instances (not persisted in workflow_state)
- `uploaded_files`: File objects for current session (metadata stored in workflow_state)

**Key Separation**:
- **`workflow_state`**: Persistent, single source of truth for all chat data
- **`st.session_state`**: Streamlit-specific runtime state and references to workflow_state

**Why persistent message IDs are needed**:
- Streamlit reruns the script on each interaction, and workflows can emit repeated state snapshots while streaming.
- Stable `id` values let the UI deduplicate streamed content vs. the finalized message stored in `workflow_state`.
- IDs also prevent re-rendering the same message across reruns and preserve message order in history.

**State Flow**:
```
User Input
  ↓
StateSynchronizer.add_user_message()
  ↓
workflow_state["messages"] updated
  ↓
DisplayManager creates Section/Block
  ↓
Section._save_to_session_state()
  ↓
workflow_state["metadata"]["display_sections"] updated
  ↓
render_message_history() reads from workflow_state
  ↓
Streamlit renders UI
```

**Benefits**:
- **Consistency**: All state in one place (`workflow_state`)
- **Persistence**: State survives Streamlit reruns
- **Workflow compatibility**: LangGraph workflows can read/write state directly
- **UI synchronization**: Display always reflects workflow_state

## Core Concepts

### Agent Configuration

Agents are configured using YAML files for better organization and maintainability.

**Single Agent (YAML Configuration):**

```yaml
# configs/agent.yaml
- name: analyst
  role: Data Analyst
  instructions: |
    Analyze data and provide insights.
    Use available tools to process and visualize data.
  provider: openai
  model: gpt-4.1-mini
  temperature: 0.0
  tools:
    - tool1
    - tool2
  context: full
  human_in_loop: true
  interrupt_on:
    tool1:
      allowed_decisions: ["approve", "reject", "edit"]
```

Load the agent:
```python
import os
import streamlit_langgraph as slg

# Load single agent from YAML (automatically loads)
config_path = os.path.join(os.path.dirname(__file__), "configs/agent.yaml")
agent = slg.Agent(config_path)
```

**Multi-Agent (YAML Configuration):**

For multi-agent workflows, define multiple agents in a single YAML file:

```yaml
# configs/agents.yaml
- name: supervisor
  role: Project Manager
  instructions: |
    You coordinate tasks and delegate to specialists.
    Analyze user requests and assign work appropriately.
  provider: openai
  model: gpt-4.1-mini
  temperature: 0.0
  tools:
    - tool_name
  context: full

- name: worker
  role: Specialist
  instructions: |
    You handle specific tasks delegated by the supervisor.
  provider: openai
  model: gpt-4.1-mini
  temperature: 0.0
```

Load multiple agents:
```python
import streamlit_langgraph as slg

# Load multiple agents from YAML file (returns list automatically)
agents = slg.Agent("configs/agents.yaml")
supervisor = agents[0]
workers = agents[1:]
```

**Path Handling:**
- Use absolute paths or relative paths as needed
- For relative paths, use `os.path.join(os.path.dirname(__file__), "configs/agents.yaml")` to resolve relative to your script

For complete parameter reference, see [Agent API Reference](#agent).

### UI Configuration

Configure the Streamlit interface using `UIConfig`:

```python
import streamlit as st
import streamlit_langgraph as slg

config = slg.UIConfig(
    title="My Multiagent App",
    welcome_message="Welcome! Ask me anything.",
    user_avatar="👤",
    assistant_avatar="🤖",
    page_icon="🤖",
    page_layout="wide",
    enable_file_upload="multiple",
    show_sidebar=True,
    stream=True,
    file_callback=None
)

if "chat" not in st.session_state:
    st.session_state.chat = slg.LangGraphChat(workflow=workflow, agents=agents, config=config)
st.session_state.chat.run()
```

**Custom Sidebar:**
```python
import streamlit as st
import streamlit_langgraph as slg

config = slg.UIConfig(show_sidebar=False)  # Disable default sidebar

# Define your own sidebar
with st.sidebar:
    st.header("Custom Sidebar")
    option = st.selectbox("Choose option", ["A", "B", "C"])
    # Your custom controls

if "chat" not in st.session_state:
    st.session_state.chat = slg.LangGraphChat(
        workflow=workflow,
        agents=agents,
        config=config
    )
st.session_state.chat.run()
```

For complete parameter reference, see [UIConfig API Reference](#uiconfig).

### Workflow Patterns

#### **Supervisor Pattern**
A supervisor agent coordinates worker agents:
- **Sequential**: Workers execute one at a time
- **Parallel**: Workers can execute simultaneously
- **Handoff**: Full context transfer between agents (works with both ResponseAPIExecutor and CreateAgentExecutor)
- **Tool Calling**: Workers called as tools

#### **Hierarchical Pattern**
Multiple supervisor teams coordinated by a top supervisor:
- Top supervisor delegates to sub-supervisors
- Each sub-supervisor manages their own team
- Multi-level organizational structure

#### **Network Pattern**
Peer-to-peer mesh topology where agents can communicate directly:
- No central supervisor - all agents are peers
- Any agent can hand off to any other agent
- First agent in the list serves as the entry point
- Best for: Complex scenarios with interdependent concerns where work needs to flow back and forth

#### **Pattern Selection Guide**

| Pattern | Use Case | Execution | Best For |
|---------|----------|-----------|----------|
| **Supervisor Sequential** | Tasks need full context from previous steps | Sequential | Research, analysis pipelines |
| **Supervisor Parallel** | Independent tasks can run simultaneously | Parallel | Data processing, multi-source queries |
| **Hierarchical** | Complex multi-level organization | Sequential | Large teams, department structure |
| **Network** | Interdependent concerns, dynamic collaboration | Peer-to-peer | Strategic consulting, complex problem-solving with back-and-forth |

### Executor Architecture

The system uses two executors that are automatically selected based on agent configuration:

#### **ResponseAPIExecutor**
- **When Used**: Native OpenAI tools enabled (`allow_code_interpreter`, `allow_web_search`, `allow_file_search`, `allow_image_generation`) AND HITL disabled
- **API**: Uses OpenAI's native Responses API directly
- **Features**:
  - Native tool support (code_interpreter, file_search, web_search, image_generation)
  - Custom tools support (converts LangChain tools to OpenAI function format)
  - MCP tools support (via OpenAI's MCP integration)
  - Streaming support
- **Limitations**: Does not support HITL (human-in-the-loop)

#### **CreateAgentExecutor**
- **When Used**: HITL enabled OR native tools disabled
- **API**: Uses ChatCompletion API via LangChain's `create_agent`
- **Features**:
  - Full HITL support with approval workflows
  - Multi-provider support (OpenAI, Anthropic, Google, etc.)
  - Custom tools support (LangChain StructuredTool)
  - MCP tools support (via LangChain MCP adapters)
  - Streaming support
- **Note**: When HITL is enabled, native OpenAI tools are automatically disabled (HITL requires CreateAgentExecutor)

#### **Automatic Selection**

The `ExecutorRegistry` automatically selects the appropriate executor:

```python
# Selection logic:
# - If HITL enabled → CreateAgentExecutor (native tools disabled)
# - If native tools enabled AND HITL disabled → ResponseAPIExecutor
# - Otherwise → CreateAgentExecutor
```

**Example**:
```yaml
# configs/response_api_agent.yaml - Uses ResponseAPIExecutor (native tools, no HITL)
- name: assistant
  role: Assistant
  instructions: You are a helpful assistant.
  allow_code_interpreter: true
  allow_web_search: true
```

```yaml
# configs/hitl_agent.yaml - Uses CreateAgentExecutor (HITL enabled)
- name: assistant
  role: Assistant
  instructions: You are a helpful assistant.
  human_in_loop: true
  interrupt_on:
    tool_name:
      allowed_decisions: ["approve", "reject"]
```

```python
import os
import streamlit_langgraph as slg

# Uses ResponseAPIExecutor
agent = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/response_api_agent.yaml"))

# Uses CreateAgentExecutor
agent = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/hitl_agent.yaml"))
```

#### **Handoff Delegation Support**

Both executors work seamlessly with handoff delegation patterns:
- **ResponseAPIExecutor**: Uses OpenAI ChatCompletion API with function calling for delegation
- **CreateAgentExecutor**: Uses LangChain tool calling for delegation
- The delegation system automatically routes to the appropriate execution method based on executor type

#### **Message Format Conversion**

Both executors convert API response objects to plain dictionary format before returning:

**Why not use API message objects directly?**

1. **Serialization Requirements**: 
   - LangGraph state must be JSON-serializable for checkpointing
   - Streamlit `session_state` requires JSON-serializable data
   - LangChain `AIMessage` and OpenAI Response objects are not JSON-serializable

2. **LangGraph State Management**:
   - `WorkflowState` defines `messages: List[Dict[str, Any]]` (plain dicts)
   - State reducers (`operator.add`) work with lists of dicts, not objects
   - LangGraph checkpointers serialize state, requiring plain data structures

3. **Unified Format**:
   - User messages are plain dicts: `{"id": "...", "role": "user", "content": "...", "agent": None}`
   - Assistant messages need the same structure for consistency
   - Both types go into the same `workflow_state["messages"]` list

4. **Custom Fields**:
   - We need `agent` field (which agent generated the message) - not in API responses
   - We need `id` field (UUID for deduplication) - APIs may not provide stable IDs
   - Unified format enables consistent display/rendering logic

**Message Structure**:
```python
{
    "id": str(uuid.uuid4()),      # Unique identifier for deduplication
    "role": "assistant",           # Message role
    "content": str,                # Message content
    "agent": str                   # Agent name that generated the message
}
```

This conversion happens in:
- `CreateAgentExecutor._extract_response_text()` - Extracts content from LangChain `AIMessage`
- `ResponseAPIExecutor._extract_response_content()` - Extracts content from OpenAI Response API

### Conversation History Modes

Control how conversation history is managed for agents:

#### **`full`**
- Agent sees **all previous messages** in the conversation
- Best for: Tasks requiring complete conversation context
- Use case: Long-running conversations, context-dependent tasks

#### **`filtered`** (Default)
- Agent sees **filtered conversation history** (system messages and relevant context)
- Best for: Most use cases, balances context with efficiency
- Use case: General purpose agents, standard workflows

#### **`disable`**
- Agent sees **no conversation history** (only current turn)
- Best for: Stateless operations, independent tasks
- Use case: One-off computations, API calls, isolated operations

```yaml
# configs/agent.yaml
- name: analyst
  role: Data Analyst
  instructions: Analyze data
  conversation_history_mode: filtered  # Default
```

```python
import os
import streamlit_langgraph as slg

agent = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/agent.yaml"))
```

### Context Modes

Control how much context each agent receives from workflow execution:

#### **`full`**
- Agent sees **all messages** and previous worker outputs
- Best for: Tasks requiring complete conversation history
- Use case: Analysis, synthesis, decision-making

#### **`summary`**
- Agent sees **summarized context** from previous steps
- Best for: Tasks that need overview but not details
- Use case: High-level coordination, routing decisions

#### **`least`** (Default)
- Agent sees **only supervisor instructions** for their task
- Best for: Focused, independent tasks
- Use case: Specialized computations, API calls

```yaml
# configs/agent.yaml
- name: analyst
  role: Data Analyst
  instructions: Analyze the provided data
  context: least  # Default: sees only task instructions
  conversation_history_mode: filtered  # Default: filtered conversation history
```

```python
import os
import streamlit_langgraph as slg

analyst = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/agent.yaml"))
```

### Human-in-the-Loop (HITL)

Enable human approval for critical agent actions:

#### **Key Features**
- **Tool Execution Approval**: Human reviews tool calls before execution
- **Decision Types**: Approve, Reject, or Edit tool inputs
- **Interrupt-Based**: Workflow pauses until human decision

#### **Use Cases**
- Sensitive operations (data deletion, API calls)
- Financial transactions
- Content moderation
- Compliance requirements

```yaml
# configs/hitl_agent.yaml
- name: executor
  role: Action Executor
  instructions: Execute approved actions
  tools:
    - delete_data
    - send_email
  human_in_loop: true  # Enable HITL
  interrupt_on:
    delete_data:
      allowed_decisions: ["approve", "reject"]
    send_email:
      allowed_decisions: ["approve", "reject", "edit"]
```

```python
import os
import streamlit_langgraph as slg

executor = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/hitl_agent.yaml"))
```

#### **HITL Decision Types**
- **Approve**: Execute tool with provided inputs
- **Reject**: Skip tool execution, continue workflow
- **Edit**: Modify tool inputs before execution

### Custom Tools

Extend agent capabilities by registering custom functions as tools:

#### **Creating a Custom Tool**

```python
import streamlit_langgraph as slg

def analyze_data(data: str, method: str = "standard") -> str:
    """
    Analyze data using specified method.
    
    This docstring is shown to the LLM, so be descriptive about:
    - What the tool does
    - When to use it
    - What each parameter means
    
    Args:
        data: The data to analyze (JSON string, CSV, etc.)
        method: Analysis method - "standard", "advanced", or "quick"
    
    Returns:
        Analysis results with insights and recommendations
    """
    # Your tool logic here
    result = f"Analyzed {len(data)} characters using {method} method"
    return result

# Register the tool
slg.CustomTool.register_tool(
    name="analyze_data",
    description=(
        "Analyze structured data using various methods. "
        "Use this when you need to process and extract insights from data. "
        "Supports JSON, CSV, and plain text formats."
    ),
    function=analyze_data
)
```

#### **Using Tools in Agents**

```yaml
# configs/agent.yaml
- name: analyst
  role: Data Analyst
  instructions: Use analyze_data tool to process user data
  tools:
    - analyze_data  # Tool name from registration
```

```python
import os
import streamlit_langgraph as slg

# Reference registered tools by name
agent = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/agent.yaml"))
```

#### **Tool Best Practices**

1. **Descriptive Docstrings**: LLM uses these to understand when/how to use the tool
2. **Type Hints**: Help with parameter validation and documentation
3. **Clear Names**: Use descriptive names that indicate purpose
4. **Error Handling**: Return error messages as strings, don't raise exceptions
5. **Return Strings**: Always return string results for LLM consumption

#### **Tool with HITL**

```python
import streamlit_langgraph as slg

def delete_records(record_ids: str, reason: str) -> str:
    """
    Delete records from database. REQUIRES APPROVAL.
    
    Args:
        record_ids: Comma-separated list of record IDs
        reason: Justification for deletion
    
    Returns:
        Confirmation message with deleted record count
    """
    ids = record_ids.split(",")
    return f"Deleted {len(ids)} records. Reason: {reason}"

slg.CustomTool.register_tool(
    name="delete_records",
    description="Delete database records (requires human approval)",
    function=delete_records
)

# Agent with HITL for this tool
```yaml
# configs/admin_agent.yaml
- name: admin
  role: Database Administrator
  instructions: Manage database operations
  tools:
    - delete_records
  human_in_loop: true
  interrupt_on:
    delete_records:
      allowed_decisions: ["approve", "reject", "edit"]
```

```python
import os
import streamlit_langgraph as slg

agent = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/admin_agent.yaml"))
```

### MCP (Model Context Protocol)

MCP (Model Context Protocol) is an open protocol for standardizing how applications provide tools and context to LLMs. This package supports connecting to MCP servers to access external tools and resources.

#### **What is MCP?**

MCP enables LLMs to interact with external systems through a standardized interface. MCP servers expose tools, resources, and prompts that agents can use, making it easy to integrate with databases, APIs, file systems, and other services.

#### **Transport Types**

MCP servers can communicate via different transport protocols:

1. **STDIO Transport** (Default)
   - Communicates through standard input/output
   - Perfect for local development and command-line tools
   - Each client spawns a new server process
   - Works with all agents (unified executor)

2. **HTTP Transport (streamable_http)**
   - Network-accessible web service
   - Supports multiple concurrent clients
   - Works with all agents (unified executor)
   - When using native OpenAI tools with Responses API: Server must be publicly accessible (not localhost)

3. **SSE Transport** (Legacy)
   - Server-Sent Events transport
   - Backward compatibility only
   - Use HTTP transport for new projects

#### **Configuring MCP Servers**

Configure MCP servers in your agent YAML:

```yaml
# configs/mcp_agent.yaml
# STDIO transport (for local development)
- name: calculator
  role: Calculator
  instructions: Use MCP tools to perform calculations
  provider: openai
  model: gpt-4o-mini
  mcp_servers:
    math:
      transport: stdio
      command: python
      args: ["./mcp_servers/math_server.py"]
```

```yaml
# configs/mcp_http_agent.yaml
# HTTP transport (for network-accessible servers)
# Note: When using native OpenAI tools with Responses API, server must be publicly accessible
- name: calculator
  role: Calculator
  instructions: Use MCP tools to perform calculations
  provider: openai
  model: gpt-4o-mini
  mcp_servers:
    math:
      transport: http  # or "streamable_http" (both accepted)
      url: http://your-server.com:8000/mcp  # Public URL required when using Responses API
```

```python
import os
import streamlit_langgraph as slg

# Load agent with MCP servers
agent = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/mcp_agent.yaml"))
```

#### **Creating MCP Servers**

Use FastMCP to create MCP servers:

```python
# math_server.py
from fastmcp import FastMCP

mcp = FastMCP("Math")

@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

@mcp.tool()
def multiply(a: int, b: int) -> int:
    """Multiply two numbers"""
    return a * b

if __name__ == "__main__":
    mcp.run()  # STDIO transport (default)
    # Or: mcp.run(transport="http", port=8000)  # HTTP transport
```

**Running MCP Servers**:

```bash
# Using FastMCP CLI
fastmcp run math_server.py

# Using FastMCP CLI with HTTP transport
fastmcp run math_server.py --transport http
```

#### **Transport Compatibility**

| Transport | Support | Notes |
|-----------|---------|-------|
| **stdio** | ✅ Supported | Local only, perfect for development |
| **http** | ✅ Supported | Network-accessible, supports multiple clients |
| **sse** | ✅ Supported | Legacy, use HTTP instead |

**Important Notes**:
- Executor selection is automatic based on agent configuration (ResponseAPIExecutor or CreateAgentExecutor)
- When using native OpenAI tools (code_interpreter, web_search, etc.) without HITL, ResponseAPIExecutor is used
- **For ResponseAPIExecutor with MCP tools**: MCP servers must be **publicly accessible** (not localhost)
- OpenAI's servers connect to your MCP server when using Responses API, so `localhost` won't work
- For local development with native tools, use stdio transport or deploy MCP servers publicly
- For local development without native tools, stdio or localhost HTTP works fine
- CreateAgentExecutor supports all MCP transport types (stdio, HTTP, localhost)

#### **Example: Local Development**

```python
# Use stdio transport for local development
mcp_servers = {
    "math": {
        "transport": "stdio",
        "command": "python",
        "args": ["math_server.py"]
    }
}

```yaml
# configs/mcp_agent.yaml
- name: calculator
  role: Calculator
  instructions: Use MCP tools for calculations
  mcp_servers:
    math:
      transport: stdio
      command: python
      args: [math_server.py]
```

```python
import os
import streamlit_langgraph as slg

agent = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/mcp_agent.yaml"))
```

#### **Example: Production Deployment**

```yaml
# configs/mcp_prod_agent.yaml
- name: calculator
  role: Calculator
  instructions: Use MCP tools for calculations
  mcp_servers:
    math:
      transport: http
      url: https://your-mcp-server.com/mcp  # Public URL
```

```python
import os
import streamlit_langgraph as slg

agent = slg.Agent(os.path.join(os.path.dirname(__file__), "configs/mcp_prod_agent.yaml"))
```

#### **MCP Server Requirements**

For agents using native OpenAI tools (Responses API) with HTTP transport:
1. MCP server must be publicly accessible (not localhost)
2. Server should bind to `0.0.0.0` (not `127.0.0.1`) to accept external connections
3. Security groups/firewalls m