Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 46 additions & 24 deletions DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,33 +66,57 @@ cp .env.example .env
Required environment variables:

```bash
# GitHub App Configuration
APP_NAME_GITHUB=your-app-name
CLIENT_ID_GITHUB=your-app-id
APP_CLIENT_SECRET=your-client-secret
PRIVATE_KEY_BASE64_GITHUB=your-base64-private-key
WEBHOOK_SECRET_GITHUB=your-webhook-secret

# AI Configuration
OPENAI_API_KEY=your-openai-api-key
AI_MODEL=gpt-4.1-mini
# GitHub Configuration (required)
APP_NAME_GITHUB=your_app_name
APP_CLIENT_ID_GITHUB=your_client_id
APP_CLIENT_SECRET_GITHUB=your_client_secret
PRIVATE_KEY_BASE64_GITHUB=your_private_key_base64
WEBHOOK_SECRET_GITHUB=your_webhook_secret

# AI Provider Selection
AI_PROVIDER=openai # Options: openai, bedrock, vertex_ai

# Common AI Settings (defaults for all agents)
AI_MAX_TOKENS=4096
AI_TEMPERATURE=0.1

# OpenAI Configuration (when AI_PROVIDER=openai)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4.1-mini # Optional, defaults to gpt-4.1-mini

# Engine Agent Configuration
AI_ENGINE_MAX_TOKENS=8000 # Default: 8000
AI_ENGINE_TEMPERATURE=0.1

# Feasibility Agent Configuration
AI_FEASIBILITY_MAX_TOKENS=4096
AI_FEASIBILITY_TEMPERATURE=0.1

# Acknowledgment Agent Configuration
AI_ACKNOWLEDGMENT_MAX_TOKENS=2000
AI_ACKNOWLEDGMENT_TEMPERATURE=0.1

# LangSmith Configuration
LANGCHAIN_TRACING_V2=true
LANGCHAIN_TRACING_V2=false
LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
LANGCHAIN_API_KEY=your-langsmith-api-key
LANGCHAIN_API_KEY=your_langsmith_api_key
LANGCHAIN_PROJECT=watchflow-dev

# Development Settings
DEBUG=true
LOG_LEVEL=DEBUG
ENVIRONMENT=development

# CORS Configuration
CORS_HEADERS=["*"]
CORS_ORIGINS='["http://localhost:3000", "http://127.0.0.1:3000"]'
CORS_ORIGINS=["http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5500", "https://warestack.github.io", "https://watchflow.dev"]

# Repository Configuration
REPO_CONFIG_BASE_PATH=.watchflow
REPO_CONFIG_RULES_FILE=rules.yaml

# Logging Configuration
LOG_LEVEL=INFO
LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s

# Development Settings
DEBUG=false
ENVIRONMENT=development
```

### 4. GitHub App Setup
Expand Down Expand Up @@ -306,22 +330,20 @@ With LangSmith configured, you can:

### Local Rule Testing

**💡 Tip**: Test your natural language rules at [watchflow.dev](https://watchflow.dev) to verify they're supported and get the generated YAML. Copy the output directly into your `rules.yaml` file.

Create a test repository with `.watchflow/rules.yaml`:

```yaml
rules:
- id: test-rule
name: Test Rule
description: Test rule for development
- description: Test rule for development
enabled: true
severity: medium
event_types: [pull_request]
parameters:
test_param: "test_value"

- id: status-check-required
name: Status Check Required
description: All PRs must pass required status checks
- description: All PRs must pass required status checks
enabled: true
severity: high
event_types: [pull_request]
Expand Down
68 changes: 44 additions & 24 deletions LOCAL_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,35 +162,57 @@ cp .env.example .env
Edit your `.env` file with the following configuration:

```bash
# GitHub App Configuration
# GitHub Configuration (required)
APP_NAME_GITHUB=watchflow-dev
CLIENT_ID_GITHUB=your_app_id_from_github_app_settings
CLIENT_SECRET_GITHUB=your_client_secret_from_github_app_settings
APP_CLIENT_ID_GITHUB=your_app_id_from_github_app_settings
APP_CLIENT_SECRET_GITHUB=your_client_secret_from_github_app_settings
PRIVATE_KEY_BASE64_GITHUB=your_base64_encoded_private_key
REDIRECT_URI_GITHUB=http://localhost:3000

# GitHub Webhook Configuration
WEBHOOK_SECRET_GITHUB=your_webhook_secret_from_step_1

# OpenAI API Configuration
OPENAI_API_KEY=your-openai-api-key
# AI Provider Selection
AI_PROVIDER=openai # Options: openai, bedrock, vertex_ai

# Common AI Settings (defaults for all agents)
AI_MAX_TOKENS=4096
AI_TEMPERATURE=0.1

# OpenAI Configuration (when AI_PROVIDER=openai)
OPENAI_API_KEY=your_openai_api_key_here
OPENAI_MODEL=gpt-4.1-mini # Optional, defaults to gpt-4.1-mini

# Engine Agent Configuration
AI_ENGINE_MAX_TOKENS=8000 # Default: 8000
AI_ENGINE_TEMPERATURE=0.1

# Feasibility Agent Configuration
AI_FEASIBILITY_MAX_TOKENS=4096
AI_FEASIBILITY_TEMPERATURE=0.1

# Acknowledgment Agent Configuration
AI_ACKNOWLEDGMENT_MAX_TOKENS=2000
AI_ACKNOWLEDGMENT_TEMPERATURE=0.1

# LangChain Configuration (Optional - for AI debugging)
LANGCHAIN_TRACING_V2=true
# LangSmith Configuration
LANGCHAIN_TRACING_V2=false
LANGCHAIN_ENDPOINT=https://api.smith.langchain.com
LANGCHAIN_API_KEY=your-langsmith-api-key
LANGCHAIN_API_KEY=your_langsmith_api_key
LANGCHAIN_PROJECT=watchflow-dev

# Application Configuration
ENVIRONMENT=development

# CORS Configuration
CORS_HEADERS=["*"]
CORS_ORIGINS='["http://localhost:3000", "http://127.0.0.1:3000"]'
CORS_ORIGINS=["http://localhost:3000", "http://127.0.0.1:3000", "http://localhost:5500", "https://warestack.github.io", "https://watchflow.dev"]

# Repository Configuration
REPO_CONFIG_BASE_PATH=.watchflow
REPO_CONFIG_RULES_FILE=rules.yaml

# AWS Configuration (if using AWS services)
AWS_ACCESS_KEY_ID=your_aws_access_key_id
AWS_SECRET_ACCESS_KEY=your_aws_secret_access_key
# Logging Configuration
LOG_LEVEL=INFO
LOG_FORMAT=%(asctime)s - %(name)s - %(levelname)s - %(message)s

# Development Settings
DEBUG=false
ENVIRONMENT=development
```

### 4.3 Encode Your Private Key
Expand Down Expand Up @@ -271,22 +293,20 @@ Open your browser and navigate to:

### 7.3 Test Rule Evaluation

**💡 Tip**: You can test your natural language rules at [watchflow.dev](https://watchflow.dev) to see if they're supported and get the generated YAML configuration. Then copy and paste it into your repository's `rules.yaml` file.

Create a test rule in a monitored repository by adding `.watchflow/rules.yaml`:

```yaml
rules:
- id: test-rule
name: Test Rule for Local Development
description: Simple rule to test local setup
- description: Simple rule to test local setup
enabled: true
severity: medium
event_types: [pull_request]
parameters:
test_param: "local_test"

- id: pr-approval-required
name: PR Approval Required
description: All pull requests must have at least 1 approval
- description: All pull requests must have at least 1 approval
enabled: true
severity: high
event_types: [pull_request]
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Learn how to create effective governance rules that adapt to your team's needs a

## Rule Configuration

**💡 Pro Tip**: Not sure if your rule is supported? Test it at [watchflow.dev](https://watchflow.dev) first! Enter your natural language rule description, and the tool will generate the YAML configuration for you. Simply copy and paste it into your `rules.yaml` file.

### Basic Rule Structure

Rules are defined in YAML format and stored in `.watchflow/rules.yaml` in your repository. Each rule consists of
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ Get Watchflow up and running in minutes to replace static protection rules with

## Step 2: Create Your Rules

**💡 Pro Tip**: Before writing rules manually, test your natural language rules at [watchflow.dev](https://watchflow.dev) to see if they're supported. The tool will generate the YAML configuration for you - just copy and paste it into your `rules.yaml` file!

Create `.watchflow/rules.yaml` in your repository root to define your governance rules:

```yaml
Expand Down
7 changes: 3 additions & 4 deletions src/agents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,16 @@
"""

from src.agents.acknowledgment_agent import AcknowledgmentAgent
from src.agents.base import AgentResult, BaseAgent, SupervisorAgent
from src.agents.base import AgentResult, BaseAgent
from src.agents.engine_agent import RuleEngineAgent
from src.agents.factory import get_agent
from src.agents.feasibility_agent import RuleFeasibilityAgent
from src.agents.supervisor_agent import RuleSupervisorAgent

__all__ = [
"BaseAgent",
"SupervisorAgent",
"AgentResult",
"RuleFeasibilityAgent",
"RuleEngineAgent",
"AcknowledgmentAgent",
"RuleSupervisorAgent",
"get_agent",
]
2 changes: 1 addition & 1 deletion src/agents/acknowledgment_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from src.agents.acknowledgment_agent.models import AcknowledgmentContext, AcknowledgmentEvaluation
from src.agents.acknowledgment_agent.prompts import create_evaluation_prompt, get_system_prompt
from src.agents.base import AgentResult, BaseAgent
from src.providers import get_chat_model
from src.integrations.providers import get_chat_model

logger = logging.getLogger(__name__)

Expand Down
66 changes: 19 additions & 47 deletions src/agents/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
Base agent classes and utilities for agents.
"""

import asyncio
import logging
from abc import ABC, abstractmethod
from typing import Any, TypeVar

from src.providers import get_chat_model
from src.core.utils.timeout import execute_with_timeout
from src.integrations.providers import get_chat_model

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -70,24 +70,20 @@ async def _retry_structured_output(self, llm, output_model, prompt, **kwargs) ->
Raises:
Exception: If all retries fail
"""
structured_llm = llm.with_structured_output(output_model)
from src.core.utils.retry import retry_async

for attempt in range(self.max_retries):
try:
result = await structured_llm.ainvoke(prompt, **kwargs)
if attempt > 0:
logger.info(f"✅ Structured output succeeded on attempt {attempt + 1}")
return result
except Exception as e:
if attempt == self.max_retries - 1:
logger.error(f"❌ Structured output failed after {self.max_retries} attempts: {e}")
raise Exception(f"Structured output failed after {self.max_retries} attempts: {str(e)}") from e
structured_llm = llm.with_structured_output(output_model)

wait_time = self.retry_delay * (2**attempt)
logger.warning(f"⚠️ Structured output attempt {attempt + 1} failed, retrying in {wait_time}s: {e}")
await asyncio.sleep(wait_time)
async def _invoke_structured() -> T:
"""Inner function to invoke structured LLM."""
return await structured_llm.ainvoke(prompt, **kwargs)

raise Exception(f"Structured output failed after {self.max_retries} attempts")
return await retry_async(
_invoke_structured,
max_retries=self.max_retries,
initial_delay=self.retry_delay,
exceptions=(Exception,),
)

async def _execute_with_timeout(self, coro, timeout: float = 30.0):
"""
Expand All @@ -101,39 +97,15 @@ async def _execute_with_timeout(self, coro, timeout: float = 30.0):
The result of the coroutine

Raises:
Exception: If the operation times out
TimeoutError: If the operation times out
"""
try:
return await asyncio.wait_for(coro, timeout=timeout)
except TimeoutError as err:
raise Exception(f"Operation timed out after {timeout} seconds") from err
return await execute_with_timeout(
coro,
timeout=timeout,
timeout_message=f"Operation timed out after {timeout} seconds",
)

@abstractmethod
async def execute(self, **kwargs) -> AgentResult:
"""Execute the agent with given parameters."""
pass


class SupervisorAgent(BaseAgent):
"""
Supervisor agent that coordinates multiple sub-agents.
"""

def __init__(self, sub_agents: dict[str, BaseAgent] = None, **kwargs):
self.sub_agents = sub_agents or {}
super().__init__(**kwargs)

async def coordinate_agents(self, task: str, **kwargs) -> AgentResult:
"""
Coordinate multiple agents to complete a complex task.

Args:
task: Description of the task to coordinate
**kwargs: Additional parameters for the task

Returns:
AgentResult with the coordinated results
"""
# This is a template for supervisor coordination
# Subclasses should implement specific coordination logic
raise NotImplementedError("Subclasses must implement coordinate_agents")
1 change: 0 additions & 1 deletion src/agents/engine_agent/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ class LLMEvaluationResponse(BaseModel):
details: dict[str, Any] = Field(
description="Detailed reasoning and metadata",
default_factory=dict,
json_schema_extra={"additionalProperties": False},
)
how_to_fix: str | None = Field(description="Specific instructions on how to fix the violation", default=None)

Expand Down
5 changes: 3 additions & 2 deletions src/agents/engine_agent/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
create_validation_strategy_prompt,
get_llm_evaluation_system_prompt,
)
from src.providers import get_chat_model
from src.integrations.providers import get_chat_model
from src.rules.validators import VALIDATOR_REGISTRY

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -297,7 +297,8 @@ async def _execute_single_llm_evaluation(
messages = [SystemMessage(content=get_llm_evaluation_system_prompt()), HumanMessage(content=evaluation_prompt)]

# Use structured output for reliable parsing
structured_llm = llm.with_structured_output(LLMEvaluationResponse)
# Use function_calling method for better OpenAI compatibility
structured_llm = llm.with_structured_output(LLMEvaluationResponse, method="function_calling")
evaluation_result = await structured_llm.ainvoke(messages)

execution_time = (time.time() - start_time) * 1000
Expand Down
Loading