Compare commits

..
4804 changed files with 20435 additions and 1171162 deletions

No files matched your search

-51
View File
@@ -1,51 +0,0 @@
# PraisonAI Environment Variables
# Copy this file to .env and update with your actual values
# === Required ===
OPENAI_API_KEY=sk-... # OpenAI key for LLM calls. Get yours at https://platform.openai.com
# === Required for Claw web-search tool ===
TAVILY_API_KEY=tvly-... # Get yours at https://app.tavily.com (free tier available)
# === Optional: Claw dashboard security ===
PRAISONAI_ALLOW_LOCAL_TOOLS=0 # Set to 1 to enable local shell tools (security risk)
# === Optional: Alternative LLM Providers ===
ANTHROPIC_API_KEY=sk-ant-... # Anthropic Claude models
GOOGLE_API_KEY=... # Google Gemini models
GROQ_API_KEY=gsk_... # Groq (fast inference)
COHERE_API_KEY=... # Cohere models
AZURE_OPENAI_API_KEY=... # Azure OpenAI
AZURE_OPENAI_ENDPOINT=... # Azure OpenAI endpoint
# === Optional: Memory backends ===
MEM0_API_KEY=... # Mem0 memory service
REDIS_URL=redis://localhost:6379 # Redis for state management
# === Optional: Observability ===
LANGFUSE_SECRET_KEY=sk-lf-... # Langfuse tracing
LANGFUSE_PUBLIC_KEY=pk-lf-... # Langfuse public key
# === Optional: Bot platforms ===
TELEGRAM_BOT_TOKEN=... # Telegram bot token
DISCORD_BOT_TOKEN=... # Discord bot token
SLACK_BOT_TOKEN=xoxb-... # Slack bot token
SLACK_APP_TOKEN=xapp-... # Slack app token
WHATSAPP_ACCESS_TOKEN=... # WhatsApp bot access token
WHATSAPP_PHONE_NUMBER_ID=... # WhatsApp phone number ID
# === Optional: Database ===
DATABASE_URL=sqlite:///~/.praison/database.sqlite # Database connection string
# === Optional: Tool Security ===
# ALLOWED_TOOLS=search,send_message # Whitelist specific tools to prevent name collisions
# Useful in multi-environment setups with overlapping tool names
# Leave unset to use all tools (with collision warnings)
# Do NOT set to empty string: that's treated as configuration error
# In CI, unknown tool names in the whitelist cause strict startup failure
# HERMES_ONLY_TOOLS=search,send_message # (Deprecated) Backward compatibility alias for ALLOWED_TOOLS
# === Optional: Development ===
LOGLEVEL=INFO # Logging level (DEBUG, INFO, WARNING, ERROR)
CHAINLIT_AUTH_SECRET=... # Chainlit authentication secret
DEBUG=false # Enable debug mode
-2
View File
@@ -1,2 +0,0 @@
patreon: MervinPraison
ko_fi: MervinPraison
-34
View File
@@ -1,34 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: '[BUG] '
labels: bug
assignees: ''
---
## Environment
- Provider (select one):
- [ ] Anthropic
- [ ] OpenAI
- [ ] Google Vertex AI
- [ ] AWS Bedrock
- [ ] Other: <!-- specify -->
- PraisonAI version: <!-- if known -->
- Operating System: <!-- e.g. macOS 14.3, Windows 11, Ubuntu 22.04 -->
## Full Code
<!-- Full code that reproduces the bug -->
## Steps to Reproduce
1. <!-- First step -->
2. <!-- Second step -->
3. <!-- And so on... -->
## Expected Behavior
<!-- What you expected to happen -->
## Actual Behavior
<!-- What actually happened -->
## Additional Context
<!-- Add any other context about the problem here, such as screenshots, logs, etc. -->
@@ -1,330 +0,0 @@
name: "Claude Code Action"
description: "Run Claude Code in GitHub Actions workflows"
inputs:
anthropic_api_key:
description: "Anthropic API key"
required: false
claude_code_oauth_token:
description: "Claude Code OAuth Token for Claude Pro"
required: false
github_token:
description: "GitHub token for Claude to operate with"
required: false
default: ${{ github.token }}
trigger_phrase:
description: "The trigger phrase to look for in comments, issue/PR bodies, and issue titles"
required: false
default: "@claude"
assignee_trigger:
description: "The assignee username that triggers the action (e.g. @claude). Only used for issue assignment"
required: false
max_turns:
description: "Maximum number of conversation turns Claude can take"
required: false
timeout_minutes:
description: "Timeout in minutes for execution"
required: false
default: "30"
model:
description: "Model to use (provider-specific format required for Bedrock/Vertex)"
required: false
use_bedrock:
description: "Use Amazon Bedrock with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
use_vertex:
description: "Use Google Vertex AI with OIDC authentication instead of direct Anthropic API"
required: false
default: "false"
allowed_tools:
description: "Additional tools for Claude to use (the base GitHub tools will always be included)"
required: false
default: ""
disallowed_tools:
description: "Tools that Claude should never use"
required: false
default: ""
custom_instructions:
description: "Additional custom instructions to include in the prompt for Claude"
required: false
default: ""
mcp_config:
description: "Additional MCP configuration (JSON string) that merges with the built-in GitHub MCP servers"
required: false
default: ""
claude_env:
description: "Custom environment variables to pass to Claude Code execution (YAML format)"
required: false
default: ""
direct_prompt:
description: "Direct prompt for Claude to execute automatically without needing a trigger (for automated workflows)"
required: false
runs:
using: "composite"
steps:
- name: Install Claude Code
shell: bash
run: npm install -g @anthropic-ai/claude-code
- name: Install GitHub MCP Server
shell: bash
env:
INPUT_GITHUB_TOKEN: ${{ inputs.github_token }}
run: |
claude mcp add-json github '{
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITHUB_PERSONAL_ACCESS_TOKEN",
"ghcr.io/github/github-mcp-server:sha-ff3036d"
],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "'"${INPUT_GITHUB_TOKEN}"'"
}
}'
- name: Extract GitHub Context and Create Prompt
shell: bash
id: prepare_context
env:
# Pass user-controlled inputs via environment variables to prevent script injection (GHSL-2025-093)
EVENT_COMMENT_BODY: ${{ github.event.comment.body }}
EVENT_ISSUE_TITLE: ${{ github.event.issue.title }}
EVENT_ISSUE_BODY: ${{ github.event.issue.body }}
EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
EVENT_PR_TITLE: ${{ github.event.pull_request.title }}
EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
EVENT_REVIEW_BODY: ${{ github.event.review.body }}
EVENT_ASSIGNEE_LOGIN: ${{ github.event.assignee.login }}
INPUT_TRIGGER_PHRASE: ${{ inputs.trigger_phrase }}
INPUT_ASSIGNEE_TRIGGER: ${{ inputs.assignee_trigger }}
INPUT_DIRECT_PROMPT: ${{ inputs.direct_prompt }}
EVENT_ACTION: ${{ github.event.action }}
EVENT_NAME: ${{ github.event_name }}
REPO_NAME: ${{ github.repository }}
run: |
echo "🔍 Extracting GitHub context from event: $EVENT_NAME"
# Function to check for trigger phrase
check_trigger() {
local text="$1"
local trigger="$INPUT_TRIGGER_PHRASE"
if [[ "$text" == *"$trigger"* ]]; then
return 0
fi
return 1
}
# Extract context based on event type
TRIGGER_FOUND="false"
USER_REQUEST=""
CONTEXT_INFO=""
case "$EVENT_NAME" in
"issue_comment")
COMMENT_BODY="$EVENT_COMMENT_BODY"
ISSUE_TITLE="$EVENT_ISSUE_TITLE"
ISSUE_NUMBER="$EVENT_ISSUE_NUMBER"
if check_trigger "$COMMENT_BODY"; then
TRIGGER_FOUND="true"
USER_REQUEST="$COMMENT_BODY"
CONTEXT_INFO="Issue Comment on #$ISSUE_NUMBER: $ISSUE_TITLE"
fi
;;
"pull_request_review_comment")
COMMENT_BODY="$EVENT_COMMENT_BODY"
PR_TITLE="$EVENT_PR_TITLE"
PR_NUMBER="$EVENT_PR_NUMBER"
if check_trigger "$COMMENT_BODY"; then
TRIGGER_FOUND="true"
USER_REQUEST="$COMMENT_BODY"
CONTEXT_INFO="PR Comment on #$PR_NUMBER: $PR_TITLE"
fi
;;
"pull_request_review")
REVIEW_BODY="$EVENT_REVIEW_BODY"
PR_TITLE="$EVENT_PR_TITLE"
PR_NUMBER="$EVENT_PR_NUMBER"
if check_trigger "$REVIEW_BODY"; then
TRIGGER_FOUND="true"
USER_REQUEST="$REVIEW_BODY"
CONTEXT_INFO="PR Review on #$PR_NUMBER: $PR_TITLE"
fi
;;
"issues")
ISSUE_BODY="$EVENT_ISSUE_BODY"
ISSUE_TITLE="$EVENT_ISSUE_TITLE"
ISSUE_NUMBER="$EVENT_ISSUE_NUMBER"
if check_trigger "$ISSUE_TITLE" || check_trigger "$ISSUE_BODY"; then
TRIGGER_FOUND="true"
USER_REQUEST="$ISSUE_BODY"
CONTEXT_INFO="Issue #$ISSUE_NUMBER: $ISSUE_TITLE"
elif [[ "$EVENT_ACTION" == "assigned" && -n "$INPUT_ASSIGNEE_TRIGGER" ]]; then
ASSIGNEE="$EVENT_ASSIGNEE_LOGIN"
if [[ "$ASSIGNEE" == "$INPUT_ASSIGNEE_TRIGGER" ]]; then
TRIGGER_FOUND="true"
USER_REQUEST="$ISSUE_BODY"
CONTEXT_INFO="Issue #$ISSUE_NUMBER assigned to $ASSIGNEE: $ISSUE_TITLE"
fi
fi
;;
esac
# Check for direct prompt override
if [[ -n "$INPUT_DIRECT_PROMPT" ]]; then
TRIGGER_FOUND="true"
USER_REQUEST="$INPUT_DIRECT_PROMPT"
CONTEXT_INFO="Automated GitHub workflow"
fi
if [[ "$TRIGGER_FOUND" != "true" ]]; then
echo "❌ No trigger phrase found or direct prompt provided. Exiting gracefully."
echo "SKIP_EXECUTION=true" >> $GITHUB_ENV
exit 0
fi
echo "✅ Trigger found! Context: $CONTEXT_INFO"
# Create comprehensive prompt
mkdir -p /tmp/claude-action
cat > /tmp/claude-action/github-context-prompt.txt << EOF
You are Claude Code, an AI assistant helping with GitHub workflows and code.
Repository: $REPO_NAME
Context: $CONTEXT_INFO
Event: $EVENT_NAME
User Request:
$USER_REQUEST
Please analyze the request and provide helpful assistance.
CRITICAL INSTRUCTION: If your task involves modifying codebase files to add a feature or fix a bug:
STEP 0 — SETUP GIT IDENTITY:
git config user.name "MervinPraison"
git config user.email "[email protected]"
STEP 1 — READ GUIDELINES:
MANDATORY: Before writing any code, you MUST carefully read "src/praisonai-agents/AGENTS.md" to understand this repository's extremely specific Architecture, Protocol-First design rules, and Agent-Centric Philosophy.
STEP 2 — ARCHITECTURE VALIDATION & ROUTING (MANDATORY before writing code):
Before implementing anything, answer these questions:
- CORE vs WRAPPER vs TOOLS ROUTING:
1. Core SDK (praisonaiagents/): Only core protocols, base classes, decorators. No heavy implementations.
2. Wrapper (praisonai/): CLI, heavy implementations, optional dependencies.
3. Tools (PraisonAI-Tools): Specific integrations, external tools (e.g. SurrealDB, Slack), and community extensions MUST go to the `MervinPraison/PraisonAI-Tools` repository. If the feature is a tool, clone `https://github.com/MervinPraison/PraisonAI-Tools`, implement it there, and create the PR using `gh pr create -R MervinPraison/PraisonAI-Tools`.
- Does it duplicate existing functionality? Check if Agent already supports this via existing params (reflection, planning, tools, hooks, memory).
- Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
- Does it inherit from Agent properly? New agent types MUST inherit Agent, not wrap it with composition.
- Does it add new dependencies? Only optional deps allowed, must be lazy-imported.
- Will agent.py grow larger? If the change adds >50 lines to agent.py, find a way to extract instead. PRs with >100 lines added to agent.py require manual review (merge gate will not auto-merge).
- Is there a name collision with existing exports in __init__.py?
If ANY of these conceptual checks fail (excluding routing), add a comment to the issue explaining why and close it. Do NOT create a PR.
STEP 3 — IMPLEMENT:
- Create a new git branch appropriately named (e.g. \`feature/issue-<number>\`)
- Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper
- Keep changes small and backward-compatible
STEP 4 — TEST:
- Run: cd src/praisonai-agents && PYTHONPATH=. python -m pytest tests/ -x -q --timeout=30
- Ensure no regressions
STEP 5 — CREATE PR:
- Commit with descriptive message, push the branch
- Use the GitHub CLI (\`gh pr create --title "..." --body "Fixes #<number>"\`) to open a Pull Request
CRITICAL: You MUST create the PR automatically using \`gh pr create\`. Do NOT just provide a link.
Respond naturally and helpfully to the user's request using the available tools.
EOF
echo "PROMPT_FILE=/tmp/claude-action/github-context-prompt.txt" >> $GITHUB_ENV
echo "SKIP_EXECUTION=false" >> $GITHUB_ENV
- name: Run Claude Code
if: env.SKIP_EXECUTION != 'true'
shell: bash
env:
ANTHROPIC_API_KEY: ${{ inputs.anthropic_api_key }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ inputs.claude_code_oauth_token }}
GITHUB_TOKEN: ${{ inputs.github_token }}
INPUT_MODEL: ${{ inputs.model }}
INPUT_MAX_TURNS: ${{ inputs.max_turns }}
INPUT_ALLOWED_TOOLS: ${{ inputs.allowed_tools }}
INPUT_DISALLOWED_TOOLS: ${{ inputs.disallowed_tools }}
INPUT_TIMEOUT_MINUTES: ${{ inputs.timeout_minutes }}
CLAUDE_CODE_SUBAGENT_MODEL: inherit
ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4-6
run: |
echo "🚀 Running Claude Code with GitHub context..."
# Build command arguments
CMD_ARGS=("-p" "--verbose" "--output-format" "stream-json")
if [[ -n "$INPUT_MODEL" ]]; then
CMD_ARGS+=("--model" "$INPUT_MODEL")
export ANTHROPIC_MODEL="$INPUT_MODEL"
fi
# Add max turns if specified
if [[ -n "$INPUT_MAX_TURNS" ]]; then
CMD_ARGS+=("--max-turns" "$INPUT_MAX_TURNS")
fi
# Add allowed tools (include GitHub tools by default)
TOOLS="mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues,mcp__github__create_comment,Read,Write,Edit,Bash"
if [[ -n "$INPUT_ALLOWED_TOOLS" ]]; then
TOOLS="$TOOLS,$INPUT_ALLOWED_TOOLS"
fi
# Remove any explicitly disallowed tools from the effective allow-list so
# the read-only contract is enforced deterministically (no reliance on CLI
# precedence between --allowedTools and --disallowedTools).
if [[ -n "$INPUT_DISALLOWED_TOOLS" ]]; then
DISALLOWED_CSV=$(echo "$INPUT_DISALLOWED_TOOLS" | tr ',\n' ' ' | xargs)
FILTERED=""
IFS=',' read -ra TOOL_ARR <<< "$TOOLS"
for tool in "${TOOL_ARR[@]}"; do
tool=$(echo "$tool" | xargs)
[[ -z "$tool" ]] && continue
skip="false"
for bad in $DISALLOWED_CSV; do
if [[ "$tool" == "$bad" ]]; then
skip="true"
break
fi
done
if [[ "$skip" == "false" ]]; then
FILTERED="${FILTERED:+$FILTERED,}$tool"
fi
done
TOOLS="$FILTERED"
fi
CMD_ARGS+=("--allowedTools" "$TOOLS")
# Add disallowed tools
if [[ -n "$INPUT_DISALLOWED_TOOLS" ]]; then
DISALLOWED_FLAG=$(echo "$INPUT_DISALLOWED_TOOLS" | tr '\n' ',' | sed 's/,*$//')
CMD_ARGS+=("--disallowedTools" "$DISALLOWED_FLAG")
fi
echo "📝 Executing Claude Code with prompt from file..."
# Execute Claude Code with timeout, using stdin for the prompt
TIMEOUT_SECONDS=$(($INPUT_TIMEOUT_MINUTES * 60))
timeout $TIMEOUT_SECONDS claude "${CMD_ARGS[@]}" < "$PROMPT_FILE"
echo "✅ Claude Code execution completed"
@@ -1,92 +0,0 @@
name: "Claude Issue Triage Action"
description: "Automatically triage GitHub issues using Claude Code"
inputs:
timeout_minutes:
description: "Timeout in minutes for execution"
required: false
default: "5"
claude_code_oauth_token:
description: "Claude Code OAuth token for Pro subscription access"
required: true
github_token:
description: "GitHub token with repo and issues permissions"
required: true
runs:
using: "composite"
steps:
- name: Checkout repository code
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- name: Create prompt
id: generate_prompt
shell: bash
run: |
mkdir -p /tmp/claude-prompts
cat > /tmp/claude-prompts/claude-issue-triage-prompt.txt << 'EOF'
You're an issue triage assistant for GitHub issues. Your task is to analyze the issue and select appropriate labels from the provided list.
IMPORTANT: Don't post any comments or messages to the issue. Your only action should be to apply labels.
Issue Information:
- REPO: ${{ github.repository }}
- ISSUE_NUMBER: ${{ github.event.issue.number }}
TASK OVERVIEW:
1. First, fetch the list of labels available in this repository by running: `gh label list`. Run exactly this command with nothing else.
2. Next, use the GitHub tools to get context about the issue:
- You have access to these tools:
- mcp__github__get_issue: Use this to retrieve the current issue's details including title, description, and existing labels
- mcp__github__get_issue_comments: Use this to read any discussion or additional context provided in the comments
- mcp__github__update_issue: Use this to apply labels to the issue (do not use this for commenting)
- mcp__github__search_issues: Use this to find similar issues that might provide context for proper categorization and to identify potential duplicate issues
- mcp__github__list_issues: Use this to understand patterns in how other issues are labeled
- Start by using mcp__github__get_issue to get the issue details
3. Analyze the issue content, considering:
- The issue title and description
- The type of issue (bug report, feature request, question, etc.)
- Technical areas mentioned
- Severity or priority indicators
- User impact
- Components affected
4. Select appropriate labels from the available labels list provided above:
- Choose labels that accurately reflect the issue's nature
- Be specific but comprehensive
- Select priority labels if you can determine urgency (high-priority, med-priority, or low-priority)
- Consider platform labels (android, ios) if applicable
- If you find similar issues using mcp__github__search_issues, consider using a "duplicate" label if appropriate. Only do so if the issue is a duplicate of another OPEN issue.
5. Apply the selected labels:
- Use mcp__github__update_issue to apply your selected labels
- DO NOT post any comments explaining your decision
- DO NOT communicate directly with users
- If no labels are clearly applicable, do not apply any labels
IMPORTANT GUIDELINES:
- Be thorough in your analysis
- Only select labels from the provided list above
- DO NOT post any comments to the issue
- Your ONLY action should be to apply labels using mcp__github__update_issue
- It's okay to not add any labels if none are clearly applicable
EOF
echo "PROMPT<<DELIMITER" >> $GITHUB_OUTPUT
cat /tmp/claude-prompts/claude-issue-triage-prompt.txt >> $GITHUB_OUTPUT
echo "DELIMITER" >> $GITHUB_OUTPUT
- name: Run Claude Code
uses: ./.github/actions/claude-code-action
with:
direct_prompt: ${{ steps.generate_prompt.outputs.PROMPT }}
allowed_tools: "Bash(gh label list),mcp__github__get_issue,mcp__github__get_issue_comments,mcp__github__update_issue,mcp__github__search_issues,mcp__github__list_issues"
timeout_minutes: ${{ inputs.timeout_minutes }}
claude_code_oauth_token: ${{ inputs.claude_code_oauth_token }}
github_token: ${{ inputs.github_token }}
-90
View File
@@ -1,90 +0,0 @@
name: 'Gemini CLI Action'
description: 'Uses the Gemini CLI to automate software development tasks.'
author: 'Gemini'
inputs:
prompt:
description: 'A specific prompt to guide Gemini.'
required: false
default: 'You are a helpful assistant.'
GEMINI_API_KEY:
description: 'Your Gemini API key.'
required: true
OTLP_GCP_WIF_PROVIDER:
description: 'The workload identity provider for GCP authentication.'
required: false
OTLP_GOOGLE_CLOUD_PROJECT:
description: 'The Google Cloud project for telemetry.'
required: false
settings_json:
description: |
A JSON string to configure the Gemini CLI. This will be written to .gemini/settings.json.
To enable observability with Google Cloud, include:
{"telemetry": {"enabled": true, "target": "gcp"}, "sandbox": false}
required: false
model:
description: |
The Gemini model to use. Options: gemini-2.5-pro, gemini-2.5-flash, gemini-2.5-flash-lite.
Use flash-lite for highest free-tier quota (1,000 RPD).
required: false
default: 'gemini-3.1-flash-lite-preview'
version:
description: |
The version of @google/gemini-cli to execute.
Can be a specific version from npm (e.g., '0.1.0', 'latest'), a branch name (e.g., 'main'), or a commit hash.
required: false
default: 'latest'
runs:
using: "composite"
steps:
- name: Configure Gemini CLI
if: inputs.settings_json != ''
run: |
mkdir -p .gemini
echo "$SETTINGS_JSON" > .gemini/settings.json
shell: bash
env:
SETTINGS_JSON: ${{ inputs.settings_json }}
- name: Authenticate to Google Cloud for Telemetry
if: inputs.OTLP_GCP_WIF_PROVIDER != ''
id: auth
uses: 'google-github-actions/auth@v2'
with:
project_id: ${{ inputs.OTLP_GOOGLE_CLOUD_PROJECT }}
workload_identity_provider: ${{ inputs.OTLP_GCP_WIF_PROVIDER }}
- name: Install Gemini CLI
id: install
env:
INPUT_VERSION: ${{ inputs.version }}
run: |
if [[ "$INPUT_VERSION" == "latest" || "$INPUT_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9\.-]+)?(\+[a-zA-Z0-9\.-]+)?$ ]]; then
echo "Installing Gemini CLI from npm: @google/gemini-cli@$INPUT_VERSION"
npm install -g @google/gemini-cli@$INPUT_VERSION
else
echo "Installing Gemini CLI from GitHub: github:google-gemini/gemini-cli#$INPUT_VERSION"
npm install -g github:google-gemini/gemini-cli#$INPUT_VERSION
fi
echo "Verifying installation:"
if command -v gemini >/dev/null 2>&1; then
gemini --version || echo "Gemini CLI installed successfully (version command not available)"
else
echo "Error: Gemini CLI not found in PATH"
exit 1
fi
shell: bash
- name: Run Gemini CLI
run: |
gemini --yolo --model "$GEMINI_MODEL" --prompt "$PROMPT"
shell: bash
env:
GEMINI_API_KEY: ${{ inputs.GEMINI_API_KEY }}
GEMINI_MODEL: ${{ inputs.model }}
PROMPT: ${{ inputs.prompt }}
branding:
icon: 'terminal'
color: 'blue'
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 429 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 872 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

-72
View File
@@ -1,72 +0,0 @@
framework: "praisonai"
process: workflow
topic: "Autonomous Issue Resolution"
planning: false
roles:
issue_analyst:
role: "GitHub Issue Analyst"
goal: "Read the GitHub issue, understand the required changes, and map the problem to the local codebase"
backstory: "You are an expert software analyst who reads bug reports and feature requests. You excel at taking a problem description and finding exactly which files in the codebase need to be modified. Your primary job is discovery and planning."
tools:
- "execute_command"
software_engineer:
role: "Autonomous Software Engineer"
goal: "Implement required code changes on a branch, run tests, commit, and push"
backstory: "You are a senior software engineer. You always branch first, implement changes using shell commands, verify with git diff, run tests, then commit and push. You never create a Pull Request unless the issue body explicitly requests one."
tools:
- "execute_command"
- "github_create_branch"
- "github_commit_and_push"
- "github_create_pull_request"
steps:
- name: Analyze Issue
agent: issue_analyst
action: >
Thoroughly analyze the issue and the repository. Follow these steps in order.
First, set up git identity: execute_command 'git config user.name PraisonAI' then
execute_command 'git config user.email [email protected]'. Do NOT pass cwd.
Second, read the issue: execute_command 'cat .github/triage-context.md'. Do NOT pass cwd.
Third, read architecture guidelines: execute_command 'cat src/praisonai-agents/AGENTS.md'.
Fourth, decide if this issue requires code changes:
- If YES: identify the exact files and lines that must change. Output a concrete file-level plan.
- If NO (question, discussion, duplicate, invalid): say so clearly. No code changes needed.
Output: routing decision (Core SDK / Wrapper / Tools / No-change), validation result, and exact files/lines to change.
expected_output: A decision on whether code changes are needed, and if so a concrete file-level plan.
- name: Implement Fix
agent: software_engineer
action: >
You are working inside the repository at /Users/praison/praisonai-package.
Do NOT ask for confirmation. Do NOT use placeholder paths. Act immediately.
Read the analyst's output first. If the analyst said NO code changes are needed, stop here and
output a brief explanation — do NOT create a branch or PR.
If code changes ARE needed, follow these steps:
Step 1 — Create branch: github_create_branch with branch_name=praisonai/issue-{{ISSUE_NUMBER}}.
Step 2 — Implement changes using execute_command (no cwd argument).
To insert a line: python3 -c "lines=open('FILE').readlines(); lines.insert(N,'LINE\n'); open('FILE','w').writelines(lines)"
To replace text: python3 -c "t=open('FILE').read(); open('FILE','w').write(t.replace('OLD','NEW'))"
After each edit verify: execute_command 'git diff'
Step 3 — Run tests: execute_command 'python3 -m pytest src/praisonai-agents/tests/ -x -q --timeout=30 -p no:warnings'
Step 4 — Commit and push: github_commit_and_push with a descriptive commit_message.
Step 5 — Check if the issue body EXPLICITLY contains the phrase "create pull request" or "create PR"
(case-insensitive). ONLY in that case create a PR using github_create_pull_request with
title='fix: resolve issue #{{ISSUE_NUMBER}}',
body='Fixes #{{ISSUE_NUMBER}}.\n\nAutonomously resolved by PraisonAI.',
head_branch=praisonai/issue-{{ISSUE_NUMBER}}, base_branch=main.
Otherwise do NOT create a PR — just confirm the branch was pushed successfully.
expected_output: Either "No code changes needed", or "Branch pushed, no PR requested", or "Branch pushed and PR created at URL".
-116
View File
@@ -1,116 +0,0 @@
framework: "praisonai"
topic: "Pull Request Code Review Analysis"
roles:
security_reviewer:
role: "Security Code Reviewer"
goal: "Identify security vulnerabilities, authentication issues, input validation problems, and unsafe code practices in the PR changes"
backstory: "You are a cybersecurity expert specializing in code analysis. You have extensive experience in identifying common security flaws like injection attacks, authentication bypasses, exposed secrets, and unsafe data handling patterns."
tools:
- "execute_command"
performance_reviewer:
role: "Performance Code Reviewer"
goal: "Analyze code changes for performance implications, identify bottlenecks, inefficient algorithms, and resource usage issues"
backstory: "You are a performance optimization specialist with deep knowledge of algorithm efficiency, memory management, and system performance. You excel at spotting code that could cause performance degradation."
tools:
- "execute_command"
maintainability_reviewer:
role: "Code Quality & Maintainability Reviewer"
goal: "Evaluate code structure, readability, documentation, naming conventions, and adherence to best practices"
backstory: "You are a software engineering expert focused on code quality and maintainability. You ensure code follows established patterns, is well-documented, and will be easy for future developers to understand and modify."
tools:
- "execute_command"
lead_reviewer:
role: "Lead Technical Reviewer"
goal: "Synthesize all review feedback, make final recommendations, and post comprehensive review comments to the GitHub PR"
backstory: "You are a senior technical lead responsible for final review decisions. You coordinate input from security, performance, and maintainability reviewers to provide balanced, actionable feedback to the development team."
tools:
- "execute_command"
steps:
- name: security_analysis
agent: security_reviewer
action: |
Analyze the PR diff for security issues:
1. Extract PR details: `echo "$PR_DATA"`
2. Get changed files: `echo "$CHANGED_FILES"`
3. Review full diff: `gh pr diff ${PR_NUMBER}`
4. Look for:
- Hardcoded secrets, API keys, passwords
- SQL injection vulnerabilities
- XSS vulnerabilities
- Authentication/authorization bypasses
- Unsafe file operations
- Command injection risks
- Missing input validation
- Exposed sensitive data
5. Document findings with file paths and line numbers
expected_output: "Detailed security analysis report with specific vulnerabilities found, severity levels, and remediation suggestions"
- name: performance_analysis
agent: performance_reviewer
action: |
Analyze the PR diff for performance issues:
1. Review algorithm complexity changes
2. Check for inefficient database queries
3. Identify memory leaks or excessive allocations
4. Look for hot-path regressions
5. Check for blocking I/O operations
6. Analyze module-level imports that could slow startup
7. Review caching implementations
8. Check for recursive operations without termination
9. Document performance concerns with impact estimates
expected_output: "Performance analysis report highlighting bottlenecks, efficiency concerns, and optimization recommendations"
- name: maintainability_analysis
agent: maintainability_reviewer
action: |
Analyze the PR diff for code quality and maintainability:
1. Check naming conventions consistency
2. Evaluate code structure and organization
3. Review documentation completeness
4. Check for DRY violations
5. Analyze error handling patterns
6. Review test coverage for changes
7. Check adherence to repository coding standards
8. Evaluate API design consistency
9. Look for technical debt introduction
expected_output: "Code quality analysis with specific recommendations for improving maintainability and readability"
- name: final_review
agent: lead_reviewer
action: |
Synthesize all reviews and post final comprehensive feedback:
1. Collect input from security, performance, and maintainability reviews
2. Prioritize findings by severity (Critical, High, Medium, Low)
3. Create structured review comment using this format:
## 📋 Review Summary
[2-3 sentence overview and assessment]
## 🔍 General Feedback
[Overall patterns and observations]
## 🎯 Specific Feedback
### 🔴 Critical (if any)
[Security vulnerabilities, breaking changes, major bugs]
### 🟡 High (if any)
[Performance issues, design flaws, significant bugs]
### 🟢 Medium (if any)
[Code quality improvements, minor optimizations]
### 🔵 Low (if any)
[Documentation, naming suggestions, minor refactoring]
## ✅ Highlights (if any)
[Positive aspects worth mentioning]
4. Post comprehensive review: `echo "[REVIEW_CONTENT]" | gh pr comment ${PR_NUMBER} -F -`
5. If critical issues found, suggest not merging until resolved
expected_output: "Final review comment posted to GitHub PR with comprehensive analysis from all reviewers"
dependencies: [security_analysis, performance_analysis, maintainability_analysis]
@@ -1,34 +0,0 @@
#!/usr/bin/env node
const chain = require('./bot-pr-review-chain.js');
let failed = 0;
function assert(name, cond) {
if (!cond) {
console.error('FAIL:', name);
failed++;
} else {
console.log('ok:', name);
}
}
assert('chainKickPosted requires both markers', chain.chainKickPosted([
{ user: { login: 'MervinPraison' }, body: '@coderabbitai review' },
{ user: { login: 'MervinPraison' }, body: '/review' },
]));
assert('chainKickPosted false with only coderabbit', !chain.chainKickPosted([
{ user: { login: 'MervinPraison' }, body: '@coderabbitai review' },
]));
assert('coderabbitKickPosted detects coderabbit', chain.coderabbitKickPosted([
{ user: { login: 'MervinPraison' }, body: '@coderabbitai review' },
]));
assert('qodoKickPosted detects qodo', chain.qodoKickPosted([
{ user: { login: 'github-actions[bot]' }, body: '/review' },
]));
assert('ignores coderabbit bot comment', !chain.coderabbitKickPosted([
{ user: { login: 'coderabbitai[bot]' }, body: '@coderabbitai review' },
]));
assert('bot PR author match', chain.isBotOpenedPr({ user: { login: 'praisonai-triage-agent[bot]' } }));
assert('generic bot type match', chain.isBotOpenedPr({ user: { login: 'my-bot', type: 'Bot' } }));
assert('human author excluded', !chain.isBotOpenedPr({ user: { login: 'MervinPraison', type: 'User' } }));
process.exit(failed ? 1 : 0);
-153
View File
@@ -1,153 +0,0 @@
/**
* Idempotent CodeRabbit/Qodo kick for bot-opened PRs.
* @see .github/workflows/auto-pr-comment.yml, claude.yml, bot-pr-recovery.yml
*/
const KICK_AUTHORS = new Set(['MervinPraison', 'github-actions[bot]']);
const BOT_PR_AUTHORS = new Set(['praisonai-triage-agent[bot]', 'github-actions[bot]']);
async function listAllComments(github, owner, repo, issueNumber) {
if (typeof github.paginate === 'function') {
return github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
}
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
return data;
}
function kickAuthored(comment, marker) {
return (
KICK_AUTHORS.has(comment.user?.login) &&
(comment.body || '').includes(marker)
);
}
function coderabbitKickPosted(comments) {
return comments.some((c) => kickAuthored(c, '@coderabbitai review'));
}
function qodoKickPosted(comments) {
return comments.some((c) => kickAuthored(c, '/review'));
}
function chainKickPosted(comments) {
return coderabbitKickPosted(comments) && qodoKickPosted(comments);
}
function isBotOpenedPr(pr) {
if (BOT_PR_AUTHORS.has(pr.user?.login)) return true;
return pr.user?.type === 'Bot';
}
async function kickReviewChain(github, owner, repo, prNumber, core, preFetchedComments = null) {
const comments = preFetchedComments || await listAllComments(github, owner, repo, prNumber);
const needCoderabbit = !coderabbitKickPosted(comments);
const needQodo = !qodoKickPosted(comments);
if (!needCoderabbit && !needQodo) {
core?.info?.(`Review chain already kicked on PR #${prNumber}`);
return { kicked: false, reason: 'already_kicked' };
}
if (needCoderabbit) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: '@coderabbitai review',
});
}
if (needQodo) {
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: '/review',
});
}
core?.info?.(`Kicked review chain for PR #${prNumber} (coderabbit=${needCoderabbit}, qodo=${needQodo})`);
return { kicked: true, coderabbit: needCoderabbit, qodo: needQodo };
}
async function findOpenPrForIssue(github, owner, repo, issueNumber) {
const prefix = `claude/issue-${issueNumber}-`;
const { data: prs } = await github.rest.pulls.list({
owner,
repo,
state: 'open',
sort: 'created',
direction: 'desc',
per_page: 30,
});
return (
prs.find(
(p) => (p.head?.ref || '').startsWith(prefix) && isBotOpenedPr(p)
) || null
);
}
async function kickReviewChainForIssue(github, owner, repo, issueNumber, core) {
const pr = await findOpenPrForIssue(github, owner, repo, issueNumber);
if (!pr) {
core?.info?.(`No open PR for issue #${issueNumber}, skipping review kick`);
return { kicked: false, reason: 'no_pr' };
}
const result = await kickReviewChain(github, owner, repo, pr.number, core);
return { ...result, prNumber: pr.number };
}
async function recoverStalledBotPrs(github, owner, repo, options, core) {
const { prNumber = null, minAgeMs = 10 * 60 * 1000, maxRecover = 10 } = options || {};
let prs;
if (prNumber) {
const { data } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
prs = [data];
} else if (typeof github.paginate === 'function') {
prs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
} else {
const { data } = await github.rest.pulls.list({ owner, repo, state: 'open', per_page: 100 });
prs = data;
}
const cutoff = Date.now() - minAgeMs;
let recovered = 0;
for (const pr of prs) {
if (recovered >= maxRecover) break;
if (!isBotOpenedPr(pr)) continue;
if (!prNumber && new Date(pr.created_at).getTime() > cutoff) continue;
const comments = await listAllComments(github, owner, repo, pr.number);
if (chainKickPosted(comments)) continue;
await kickReviewChain(github, owner, repo, pr.number, core, comments);
recovered += 1;
}
core?.info?.(`Recovery complete (${recovered} PR(s) kicked)`);
return recovered;
}
module.exports = {
KICK_AUTHORS,
BOT_PR_AUTHORS,
listAllComments,
chainKickPosted,
coderabbitKickPosted,
qodoKickPosted,
isBotOpenedPr,
kickReviewChain,
findOpenPrForIssue,
kickReviewChainForIssue,
recoverStalledBotPrs,
};
-151
View File
@@ -1,151 +0,0 @@
#!/usr/bin/env node
/**
* Local self-test for merge-gate.js heuristics (no GitHub API).
* Run: node .github/scripts/merge-gate-selftest.js
*/
const mg = require('./merge-gate.js');
let failed = 0;
function assert(name, cond) {
if (!cond) {
console.error('FAIL:', name);
failed++;
} else {
console.log('ok:', name);
}
}
// Stale FINAL: push after FINAL, no @claude since head
const finals = [
{ user: { login: 'github-actions[bot]' }, body: '@claude FINAL architecture reviewer', created_at: '2026-06-12T08:00:00Z' },
];
assert('stale when head after final', mg.isStaleFinalAfterPush(finals, '2026-06-12T09:00:00Z'));
const withRecovery = [
...finals,
{ user: { login: 'github-actions[bot]' }, body: '@claude FINAL architecture reviewer', created_at: '2026-06-12T09:30:00Z' },
];
assert('not stale when @claude after head', !mg.isStaleFinalAfterPush(withRecovery, '2026-06-12T09:00:00Z'));
// Bot CHANGES_REQUESTED then APPROVE
const reviews = [
{ user: { login: 'coderabbit[bot]', type: 'Bot' }, state: 'CHANGES_REQUESTED', submitted_at: '2026-06-12T08:00:00Z' },
{ user: { login: 'coderabbit[bot]', type: 'Bot' }, state: 'APPROVED', submitted_at: '2026-06-12T09:00:00Z' },
];
assert('bot approve clears CR', !mg.hasAnyChangesRequested(reviews));
assert('human CR blocks', mg.hasAnyChangesRequested([
{ user: { login: 'MervinPraison', type: 'User' }, state: 'CHANGES_REQUESTED', submitted_at: '2026-06-12T09:00:00Z' },
]));
// Verdict after HEAD
const verdictComments = [
{ body: 'MERGE_GATE_VERDICT: APPROVE', created_at: '2026-06-12T08:00:00Z' },
];
assert('verdict before head rejected', mg.findMergeGateVerdict(verdictComments, null, '2026-06-12T09:00:00Z') === null);
assert('verdict after head accepted', mg.findMergeGateVerdict(
[{ body: 'MERGE_GATE_VERDICT: APPROVE', created_at: '2026-06-12T10:00:00Z' }],
null,
'2026-06-12T09:00:00Z'
) === 'APPROVE');
const noise = [{ user: { login: 'MervinPraison' }, body: '**Merge gate scan** — wait for `@claude`', created_at: new Date().toISOString() }];
assert('diagnostic comment not a trigger', !mg.hasRecentClaudeTrigger(noise, 35));
// Cooldown skip requires an actual verdict on HEAD, not just a FINAL trigger comment
const recentFinalOnHead = [
{
user: { login: 'MervinPraison' },
body: '@claude You are the FINAL architecture reviewer.',
created_at: '2026-06-27T10:00:00Z',
},
];
assert(
'final trigger alone does not count as verdict on head',
mg.findMergeGateVerdict(recentFinalOnHead, null, '2026-06-27T09:55:00Z') === null
);
const recentVerdictOnHead = [
...recentFinalOnHead,
{
user: { login: 'github-actions[bot]' },
body: 'MERGE_GATE_VERDICT: APPROVE',
created_at: '2026-06-27T10:05:00Z',
},
];
assert(
'verdict on head skips cooldown gate',
mg.findMergeGateVerdict(recentVerdictOnHead, null, '2026-06-27T09:55:00Z') === 'APPROVE'
);
// Sensitive + secrets
assert('workflow path sensitive', mg.sensitivePathReasons([{ filename: '.github/workflows/foo.yml' }]).length === 1);
assert('ci-only label exempts workflows', mg.sensitivePathReasons(
[{ filename: '.github/workflows/foo.yml' }],
[mg.WORKFLOW_ONLY_LABEL]
).length === 0);
assert('ci-only label not exempt mixed changes', mg.sensitivePathReasons(
[{ filename: '.github/workflows/foo.yml' }, { filename: 'src/foo.py' }],
[mg.WORKFLOW_ONLY_LABEL]
).length === 1);
assert('secret in patch', mg.secretScanReasons([{ filename: 'x.py', patch: '+key = "ghp_abcdefghijklmnopqrstuvwxyz1234567890"' }]).length === 1);
// Claude run scoping — other PR branches must not block
assert('other branch claude does not block', !mg.hasBlockingClaudeRunForPr(
[{ event: 'issue_comment', head_branch: 'other-branch' }],
'my-branch'
));
assert('same branch claude blocks', mg.hasBlockingClaudeRunForPr(
[{ event: 'issue_comment', head_branch: 'my-branch' }],
'my-branch'
));
assert('issues event never blocks', !mg.hasBlockingClaudeRunForPr(
[{ event: 'issues', head_branch: 'my-branch' }],
'my-branch'
));
// Conflict rebase clears after bot completion + FINAL on HEAD
const conflictTrigger = {
user: { login: 'MervinPraison' },
body: '@claude this PR has merge conflicts with main. Please rebase',
created_at: '2026-06-26T10:30:38Z',
};
const rebaseDone = {
user: { login: 'praisonai-triage-agent[bot]' },
body: 'Rebase complete — PR #2308 onto latest main',
created_at: '2026-06-26T10:30:55Z',
};
const finalAfterRebase = {
user: { login: 'MervinPraison' },
body: '@claude You are the FINAL architecture reviewer.',
created_at: '2026-06-26T10:39:43Z',
};
const headAfterRebase = '2026-06-26T10:39:25Z';
const rebaseComments = [conflictTrigger, rebaseDone, finalAfterRebase];
assert('conflict blocks before rebase done', mg.hasRecentConflictComment([conflictTrigger], headAfterRebase));
assert('conflict clears after rebase + FINAL on HEAD', !mg.hasRecentConflictComment(rebaseComments, headAfterRebase));
assert('conflict still blocks without FINAL on HEAD', mg.hasRecentConflictComment(
[conflictTrigger, rebaseDone],
headAfterRebase
));
// Tests heuristic
assert('sdk without tests', mg.missingTestsReason([{ filename: 'src/praisonai-agents/a/b.py', additions: 3 }]) !== null);
assert('sdk with tests ok', mg.missingTestsReason([
{ filename: 'src/praisonai-agents/a/b.py', additions: 3 },
{ filename: 'src/praisonai-agents/tests/test_x.py', additions: 10 },
]) === null);
// PR size
assert('large PR blocked', mg.prSizeReasons([{ additions: 900 }]).length > 0);
assert('internal PR link accepted', mg.isInternalPullRequestLink(
{ base: { repo: { full_name: 'MervinPraison/PraisonAI' } } },
'MervinPraison',
'PraisonAI'
));
assert('fork sync PR link rejected', !mg.isInternalPullRequestLink(
{ number: 21, base: { repo: { full_name: 'Milkmange/PraisonAI' } } },
'MervinPraison',
'PraisonAI'
));
process.exit(failed ? 1 : 0);
-744
View File
@@ -1,744 +0,0 @@
/**
* Shared merge-gate helpers for claude-merge-gate.yml
* @see .github/workflows/claude-merge-gate.yml
*/
const CLAUDE_TRIGGER_LOGINS = ['MervinPraison', 'github-actions[bot]'];
const AUTO_ACTORS = CLAUDE_TRIGGER_LOGINS;
const CONFLICT_COOLDOWN_MS = 12 * 60 * 60 * 1000;
const CLAUDE_ACTIVE_MS = 35 * 60 * 1000;
const ALLOWED_MERGE_STATES = new Set(['CLEAN', 'UNSTABLE']);
const AGENT_PY_MAX_AUTO_LINES = 100;
const PR_MAX_AUTO_ADDITIONS = 800;
const PR_MAX_AUTO_FILES = 30;
const MANUAL_ONLY_LABELS = new Set(['security', 'breaking-change', 'needs-manual-review', 'release']);
const WORKFLOW_ONLY_LABEL = 'merge-gate-ci-only';
const CI_ONLY_PATH_PREFIXES = ['.github/workflows/', '.github/actions/', '.github/scripts/merge-gate'];
const SDK_PATH_PREFIXES = ['src/praisonai-agents/', 'src/praisonai/'];
const SENSITIVE_PATH_PATTERNS = [
/^\.github\/workflows\//,
/praisonaiagents\/(auth|approval|policy|sandbox)\//,
/^src\/praisonai-agents\/pyproject\.toml$/,
/^src\/praisonai\/pyproject\.toml$/,
/\.env(\.|$)/,
/credentials\.json$/i,
];
const REQUIRED_SDK_CHECK_PATTERNS = [/test/i, /smoke/i, /core/i, /python package/i, /comprehensive/i, /optimized/i];
const SECRET_PATTERNS = [
/sk-[a-zA-Z0-9]{20,}/,
/AKIA[0-9A-Z]{16}/,
/ghp_[a-zA-Z0-9]{36,}/,
/github_pat_/,
/Bearer eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/,
/-----BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY-----/,
];
const BLOCK_LABELS = new Set([
'claude-conflict-pending',
'claude-merge-gate-active',
'no-auto-merge',
'auto-merged-by-gate',
]);
const BOT_REVIEWER_PATTERNS = [
'coderabbit',
'qodo',
'gemini',
'copilot',
'greptile',
];
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
function isFinalClaudeTriggerComment(c) {
const body = (c.body || '').toLowerCase();
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!body.includes('@claude')) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
}
function isClaudeTriggerNoise(c) {
const body = c.body || '';
if (body.includes('Merge gate scan')) return true;
if (body.includes('MERGE_GATE_VERDICT')) return true;
if (body.includes('Merged by **Claude PR merge gate**')) return true;
return false;
}
function hasRecentClaudeTrigger(comments, minutes = 35) {
const cutoff = Date.now() - minutes * 60 * 1000;
return comments.some((c) => {
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;
if (isClaudeTriggerNoise(c)) return false;
if (!(c.body || '').includes('@claude')) return false;
return new Date(c.created_at).getTime() > cutoff;
});
}
function isConflictRebaseTriggerComment(c) {
if (!AUTO_ACTORS.includes(c.user.login)) return false;
const body = (c.body || '').toLowerCase();
return body.includes('@claude') && body.includes('merge conflict');
}
function isConflictRebaseCompletionComment(c) {
const login = (c.user?.login || '').toLowerCase();
if (!login.includes('praisonai-triage') && !login.includes('github-actions')) return false;
const body = (c.body || '').toLowerCase();
return (
body.includes('rebase complete') ||
body.includes('rebase onto') ||
(body.includes('conflict') && body.includes('resolved'))
);
}
function conflictRebaseQuiescent(comments, headPushedAt) {
const conflictTriggers = comments.filter(isConflictRebaseTriggerComment);
if (conflictTriggers.length === 0) return true;
const latestConflict = conflictTriggers.reduce((a, b) =>
new Date(a.created_at) > new Date(b.created_at) ? a : b
);
const conflictTime = new Date(latestConflict.created_at).getTime();
const rebaseDone = comments.some(
(c) =>
new Date(c.created_at).getTime() > conflictTime && isConflictRebaseCompletionComment(c)
);
if (!rebaseDone) return false;
return finalClaudeCompletedOnSha(comments, headPushedAt);
}
function hasRecentConflictComment(comments, headPushedAt = null) {
const cutoff = Date.now() - CONFLICT_COOLDOWN_MS;
const hasRecentTrigger = comments.some((c) => {
if (!isConflictRebaseTriggerComment(c)) return false;
return new Date(c.created_at).getTime() > cutoff;
});
if (!hasRecentTrigger) return false;
if (headPushedAt && conflictRebaseQuiescent(comments, headPushedAt)) {
return false;
}
return true;
}
function isBotReviewer(login, userType) {
const lower = (login || '').toLowerCase();
if (userType === 'Bot') return true;
if (lower.endsWith('[bot]')) return true;
return ['coderabbit', 'qodo', 'gemini', 'copilot', 'greptile'].some((p) => lower.includes(p));
}
function latestReviewsByUser(reviews) {
const latestByUser = new Map();
for (const r of reviews) {
const login = r.user?.login;
if (!login) continue;
const prev = latestByUser.get(login);
if (!prev || new Date(r.submitted_at) > new Date(prev.submitted_at)) {
latestByUser.set(login, r);
}
}
return latestByUser;
}
function hasHumanChangesRequested(reviews) {
for (const [login, review] of latestReviewsByUser(reviews)) {
if (review.state !== 'CHANGES_REQUESTED') continue;
if (!isBotReviewer(login, review.user?.type)) return true;
}
return false;
}
function hasAnyChangesRequested(reviews) {
for (const [, review] of latestReviewsByUser(reviews)) {
if (review.state === 'CHANGES_REQUESTED') return true;
}
return false;
}
function hasFinalClaudeReviewTrigger(comments) {
return comments.some(isFinalClaudeTriggerComment);
}
function isStaleFinalAfterPush(comments, headPushedAt) {
if (!headPushedAt) return false;
const headTime = new Date(headPushedAt).getTime();
const finals = comments.filter(isFinalClaudeTriggerComment);
if (finals.length === 0) return true;
const latestFinal = finals.reduce((a, b) =>
new Date(a.created_at) > new Date(b.created_at) ? a : b
);
const finalTime = new Date(latestFinal.created_at).getTime();
if (headTime <= finalTime + 60000) return false;
const claudeSinceHead = comments.some((c) => {
if (!CLAUDE_TRIGGER_LOGINS.includes(c.user.login)) return false;
if (isClaudeTriggerNoise(c)) return false;
if (!(c.body || '').includes('@claude')) return false;
return new Date(c.created_at).getTime() >= headTime - 60000;
});
return !claudeSinceHead;
}
function needsStaleFinalRecovery(comments, headPushedAt) {
return (
hasFinalClaudeReviewTrigger(comments) &&
isStaleFinalAfterPush(comments, headPushedAt)
);
}
function shouldSkipFinalRecovery(comments, headPushedAt) {
const isStale = isStaleFinalAfterPush(comments, headPushedAt);
if (isStale) return false;
return hasRecentClaudeTrigger(comments, 35);
}
function finalClaudeCompletedOnSha(comments, headPushedAt) {
if (!hasFinalClaudeReviewTrigger(comments)) return false;
if (isStaleFinalAfterPush(comments, headPushedAt)) return false;
return true;
}
const FINAL_CLAUDE_REVIEW_BODY =
'@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep\n5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items';
async function getMergeState(github, owner, repo, prNumber) {
const query = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
mergeStateStatus
maintainerCanModify
isDraft
headRefOid
headRef { repository { nameWithOwner } }
}
}
}
`;
for (let attempt = 0; attempt < 3; attempt++) {
const result = await github.graphql(query, { owner, repo, number: prNumber });
const prGql = result.repository.pullRequest;
const status = (prGql?.mergeStateStatus || '').toUpperCase();
if (status && status !== 'UNKNOWN') {
return {
status,
isDraft: prGql.isDraft,
headRepo: prGql.headRef?.repository?.nameWithOwner,
headSha: prGql.headRefOid,
maintainerCanModify: prGql.maintainerCanModify === true,
};
}
if (attempt < 2) await sleep(10000);
}
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
return {
status: (pr.mergeable_state || '').toUpperCase(),
isDraft: pr.draft,
headRepo: pr.head.repo?.full_name,
headSha: pr.head.sha,
maintainerCanModify: pr.maintainer_can_modify === true,
};
}
async function allChecksGreenOnSha(github, owner, repo, sha, core) {
const { data } = await github.rest.checks.listForRef({
owner,
repo,
ref: sha,
per_page: 100,
});
const runs = (data.check_runs || []).filter((r) => r.head_sha === sha);
if (runs.length === 0) {
core?.info?.(`No check runs on ${sha.slice(0, 7)} — allowing (e.g. docs-only PR)`);
return true;
}
for (const run of runs) {
if (run.status !== 'completed') {
core?.info?.(`Check pending: ${run.name} (${run.status})`);
return false;
}
const ok = ['success', 'neutral', 'skipped'].includes(run.conclusion);
if (!ok) {
core?.info?.(`Check failed: ${run.name} (${run.conclusion})`);
return false;
}
}
return true;
}
function claudeRunBlocksPr(run, headRef) {
if (!run || run.event === 'issues') return false;
if (!headRef) return true;
return run.head_branch === headRef;
}
function hasBlockingClaudeRunForPr(runs, headRef) {
return (runs || []).some((r) => claudeRunBlocksPr(r, headRef));
}
async function hasInProgressClaudeAssistant(github, owner, repo, prNumber = null) {
try {
const { data } = await github.rest.actions.listWorkflowRuns({
owner,
repo,
workflow_id: 'claude.yml',
status: 'in_progress',
per_page: 20,
});
const runs = (data.workflow_runs || []).filter((r) => r.event !== 'issues');
if (runs.length === 0) return false;
if (prNumber == null) return runs.length > 0;
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
const headRef = pr.head.ref;
return hasBlockingClaudeRunForPr(runs, headRef);
} catch {
return false;
}
}
function isCiOnlyChange(files) {
if (!files.length) return false;
return files.every((f) => CI_ONLY_PATH_PREFIXES.some((p) => f.filename.startsWith(p)));
}
function isInternalPullRequestLink(link, owner, repo) {
if (link?.base?.repo?.full_name === `${owner}/${repo}`) return true;
const baseUrl = link?.base?.repo?.url || '';
return baseUrl.endsWith(`/repos/${owner}/${repo}`);
}
async function resolvePrNumberFromLinkedPullRequests(github, owner, repo, linked) {
const repoFull = `${owner}/${repo}`;
const internal = (linked || []).filter((l) => isInternalPullRequestLink(l, owner, repo));
for (const link of internal) {
if (!link.number) continue;
try {
const { data } = await github.rest.pulls.get({
owner,
repo,
pull_number: link.number,
});
if (data.state === 'open' && data.base?.repo?.full_name === repoFull) {
return data.number;
}
} catch (err) {
if (err.status !== 404) throw err;
}
}
return null;
}
async function resolvePrNumberFromHeadBranch(github, owner, repo, branch) {
if (!branch || branch === 'main' || branch === 'master') return null;
const { data } = await github.rest.pulls.list({
owner,
repo,
state: 'open',
head: `${owner}:${branch}`,
per_page: 1,
});
return data[0]?.number || null;
}
async function resolvePrNumberFromWorkflowRun(github, owner, repo, workflowRun) {
const fromLinked = await resolvePrNumberFromLinkedPullRequests(
github, owner, repo, workflowRun.pull_requests
);
if (fromLinked) return fromLinked;
const fromBranch = await resolvePrNumberFromHeadBranch(
github, owner, repo, workflowRun.head_branch
);
if (fromBranch) return fromBranch;
return null;
}
async function listPullFiles(github, owner, repo, prNumber) {
if (typeof github.paginate === 'function') {
return github.paginate(github.rest.pulls.listFiles, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
}
const { data } = await github.rest.pulls.listFiles({
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
return data;
}
function countNewAgentParams(patch) {
if (!patch) return 0;
return patch
.split('\n')
.filter((l) => l.startsWith('+') && !l.startsWith('+++'))
.filter((l) => {
const t = l.slice(1).trim();
if (!t || t.startsWith('#') || t.startsWith('"""') || t.startsWith("'''")) return false;
if (/^(def|class|@|return\b|if\b|elif\b|else\b|for\b|while\b)/.test(t)) return false;
if (/^\w+\s*[:=]/.test(t) && !t.startsWith('self.')) return true;
return false;
}).length;
}
function getAgentPyChangeFromFiles(files) {
const agentFile = files.find((f) => f.filename.endsWith('praisonaiagents/agent/agent.py'));
if (!agentFile) {
return { touched: false, additions: 0, newParams: 0 };
}
return {
touched: true,
additions: agentFile.additions || 0,
newParams: countNewAgentParams(agentFile.patch || ''),
};
}
async function getAgentPyChange(github, owner, repo, prNumber) {
const files = await listPullFiles(github, owner, repo, prNumber);
return getAgentPyChangeFromFiles(files);
}
function touchesSdk(files) {
return files.some((f) => SDK_PATH_PREFIXES.some((p) => f.filename.startsWith(p)));
}
function hasManualOnlyLabel(labels) {
return labels.some((l) => MANUAL_ONLY_LABELS.has(l));
}
function sensitivePathReasons(files, labels = []) {
if (labels.includes(WORKFLOW_ONLY_LABEL) && isCiOnlyChange(files)) {
return [];
}
const reasons = [];
for (const f of files) {
if (SENSITIVE_PATH_PATTERNS.some((p) => p.test(f.filename))) {
reasons.push(`sensitive path: ${f.filename}`);
break;
}
}
return reasons;
}
function prSizeReasons(files) {
const reasons = [];
const totalAdditions = files.reduce((sum, f) => sum + (f.additions || 0), 0);
if (totalAdditions > PR_MAX_AUTO_ADDITIONS) {
reasons.push(`PR +${totalAdditions} lines (>${PR_MAX_AUTO_ADDITIONS}) requires manual review`);
}
if (files.length > PR_MAX_AUTO_FILES) {
reasons.push(`${files.length} files changed (>${PR_MAX_AUTO_FILES}) requires manual review`);
}
return reasons;
}
function missingTestsReason(files) {
const sdkAdds = files.filter(
(f) =>
f.filename.startsWith('src/praisonai-agents/') &&
f.filename.endsWith('.py') &&
!f.filename.endsWith('__init__.py') &&
(f.additions || 0) > 0
);
if (sdkAdds.length === 0) return null;
const hasTestChange = files.some(
(f) => /\/tests?\//.test(f.filename) || /test_.*\.py$/.test(f.filename) || /_test\.py$/.test(f.filename)
);
if (!hasTestChange) return 'SDK code added without test file changes — requires manual review';
return null;
}
function secretScanReasons(files) {
for (const f of files) {
if (/\/tests?\//.test(f.filename) || /test_.*\.py$/.test(f.filename)) continue;
const patch = f.patch || '';
if (!patch) continue;
for (const pattern of SECRET_PATTERNS) {
if (pattern.test(patch)) {
return [`possible secret in diff (${f.filename}) — requires manual review`];
}
}
}
return [];
}
async function sdkTestChecksReason(github, owner, repo, sha, files, core) {
if (!touchesSdk(files)) return null;
const { data } = await github.rest.checks.listForRef({
owner,
repo,
ref: sha,
per_page: 100,
});
const runs = (data.check_runs || []).filter((r) => r.head_sha === sha);
if (runs.length === 0) {
return 'SDK code changed but no CI checks on HEAD';
}
const testRuns = runs.filter((r) => REQUIRED_SDK_CHECK_PATTERNS.some((p) => p.test(r.name || '')));
if (testRuns.length === 0) {
return 'SDK code changed but no test check runs on HEAD';
}
core?.info?.(`SDK test checks on HEAD: ${testRuns.map((r) => r.name).join(', ')}`);
return null;
}
function manualReviewReasonForAgentPy(agentChange) {
if (!agentChange.touched) return null;
if (agentChange.additions > AGENT_PY_MAX_AUTO_LINES) {
return `agent.py +${agentChange.additions} lines (>${AGENT_PY_MAX_AUTO_LINES}) requires manual review`;
}
if (agentChange.newParams > 0) {
return `agent.py adds ${agentChange.newParams} new Agent param(s) — requires manual review`;
}
return null;
}
async function listAllComments(github, owner, repo, issueNumber) {
if (typeof github.paginate === 'function') {
return github.paginate(github.rest.issues.listComments, {
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
}
const { data } = await github.rest.issues.listComments({
owner,
repo,
issue_number: issueNumber,
per_page: 100,
});
return data;
}
async function getHeadCommitDate(github, owner, repo, prNumber) {
try {
let commits;
if (typeof github.paginate === 'function') {
commits = await github.paginate(github.rest.pulls.listCommits, {
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
} else {
const { data } = await github.rest.pulls.listCommits({
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
commits = data;
}
const last = commits[commits.length - 1];
return last?.commit?.committer?.date || last?.commit?.author?.date || null;
} catch {
return null;
}
}
async function loadPrContext(github, owner, repo, prNumber) {
const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
const { data: issue } = await github.rest.issues.get({ owner, repo, issue_number: prNumber });
const comments = await listAllComments(github, owner, repo, prNumber);
const { data: reviews } = await github.rest.pulls.listReviews({
owner,
repo,
pull_number: prNumber,
per_page: 100,
});
const mergeState = await getMergeState(github, owner, repo, prNumber);
const headSha = mergeState.headSha || pr.head.sha;
const headCommitDate = await getHeadCommitDate(github, owner, repo, prNumber);
const headPushedAt = headCommitDate || pr.updated_at;
return {
pr,
issue,
comments,
reviews,
mergeState,
headSha,
headPushedAt,
labels: issue.labels.map((l) => l.name),
baseRepo: `${owner}/${repo}`,
};
}
async function evaluatePipelineQuiescent(github, owner, repo, prNumber, core, options = {}) {
const {
forMergeStep = false,
skipGlobalClaudeRunCheck = false,
skipRecentClaudeCooldown = false,
} = options;
const ctx = await loadPrContext(github, owner, repo, prNumber);
const reasons = [];
if (ctx.pr.draft) reasons.push('draft');
if (ctx.pr.state !== 'open') reasons.push('not open');
if (ctx.labels.includes('auto-merged-by-gate')) reasons.push('already merged by gate');
if (ctx.labels.includes('no-auto-merge')) reasons.push('no-auto-merge label');
if (ctx.labels.includes('claude-conflict-pending')) reasons.push('claude-conflict-pending');
if (!forMergeStep && ctx.labels.includes('claude-merge-gate-active')) {
reasons.push('claude-merge-gate-active');
}
const { status, headRepo, maintainerCanModify } = ctx.mergeState;
if (!ALLOWED_MERGE_STATES.has(status)) reasons.push(`mergeState=${status}`);
if (headRepo && headRepo !== ctx.baseRepo) {
reasons.push('fork PR');
}
if (hasRecentConflictComment(ctx.comments, ctx.headPushedAt)) {
reasons.push('recent merge-conflict @claude');
}
if (!skipRecentClaudeCooldown && hasRecentClaudeTrigger(ctx.comments, 35)) {
const verdictOnHead = findMergeGateVerdict(ctx.comments, null, ctx.headPushedAt) !== null;
if (!verdictOnHead) reasons.push('recent @claude within 35min');
}
if (!skipGlobalClaudeRunCheck && (await hasInProgressClaudeAssistant(github, owner, repo, prNumber))) {
reasons.push('claude.yml in progress');
}
const checksOk = await allChecksGreenOnSha(github, owner, repo, ctx.headSha, core);
if (!checksOk) reasons.push('CI not green on HEAD');
if (!finalClaudeCompletedOnSha(ctx.comments, ctx.headPushedAt)) {
if (!hasFinalClaudeReviewTrigger(ctx.comments)) {
reasons.push('no FINAL Claude review trigger');
} else if (isStaleFinalAfterPush(ctx.comments, ctx.headPushedAt)) {
reasons.push('stale FINAL after new commits (needs @claude re-review)');
} else {
reasons.push('FINAL Claude not complete on HEAD');
}
}
if (hasAnyChangesRequested(ctx.reviews)) reasons.push('CHANGES_REQUESTED review');
if (hasManualOnlyLabel(ctx.labels)) {
const manualLabel = ctx.labels.find((l) => MANUAL_ONLY_LABELS.has(l));
reasons.push(`manual-only label: ${manualLabel}`);
}
const pullFiles = await listPullFiles(github, owner, repo, prNumber);
const agentChange = getAgentPyChangeFromFiles(pullFiles);
const agentManual = manualReviewReasonForAgentPy(agentChange);
if (agentManual) reasons.push(agentManual);
reasons.push(...sensitivePathReasons(pullFiles, ctx.labels));
reasons.push(...prSizeReasons(pullFiles));
reasons.push(...secretScanReasons(pullFiles));
const testsReason = missingTestsReason(pullFiles);
if (testsReason) reasons.push(testsReason);
const sdkCheckReason = await sdkTestChecksReason(github, owner, repo, ctx.headSha, pullFiles, core);
if (sdkCheckReason) reasons.push(sdkCheckReason);
if (ctx.pr.mergeable === false) reasons.push('not mergeable');
const ready = reasons.length === 0;
return {
ready,
reasons,
headSha: ctx.headSha,
prNumber,
};
}
async function selectMergeGateCandidates(github, owner, repo, prNumbers, maxCandidates, core) {
const readyList = [];
const skipped = [];
for (const num of prNumbers) {
const result = await evaluatePipelineQuiescent(github, owner, repo, num, core);
if (result.ready) {
readyList.push({ pr_number: num, head_sha: result.headSha });
} else {
skipped.push({ pr: num, reasons: result.reasons });
}
}
readyList.sort((a, b) => a.pr_number - b.pr_number);
return {
candidates: readyList.slice(0, maxCandidates),
skipped,
};
}
function findMergeGateVerdict(comments, minCreatedAt = null, headPushedAt = null) {
const minTime = minCreatedAt ? new Date(minCreatedAt).getTime() : 0;
const headTime = headPushedAt ? new Date(headPushedAt).getTime() - 60000 : 0;
const gateComments = comments
.filter((c) => {
if (!(c.body || '').includes('MERGE_GATE_VERDICT:')) return false;
const created = new Date(c.created_at).getTime();
if (minTime && created < minTime) return false;
if (headTime && created < headTime) return false;
return true;
})
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
if (gateComments.length === 0) return null;
const body = gateComments[0].body || '';
if (body.includes('MERGE_GATE_VERDICT: APPROVE')) return 'APPROVE';
if (body.includes('MERGE_GATE_VERDICT: BLOCK')) return 'BLOCK';
return null;
}
module.exports = {
CLAUDE_TRIGGER_LOGINS,
AUTO_ACTORS,
isFinalClaudeTriggerComment,
isClaudeTriggerNoise,
hasRecentClaudeTrigger,
hasRecentConflictComment,
isConflictRebaseTriggerComment,
isConflictRebaseCompletionComment,
conflictRebaseQuiescent,
hasHumanChangesRequested,
hasAnyChangesRequested,
hasFinalClaudeReviewTrigger,
finalClaudeCompletedOnSha,
getMergeState,
allChecksGreenOnSha,
hasInProgressClaudeAssistant,
claudeRunBlocksPr,
hasBlockingClaudeRunForPr,
isCiOnlyChange,
isInternalPullRequestLink,
resolvePrNumberFromLinkedPullRequests,
resolvePrNumberFromHeadBranch,
resolvePrNumberFromWorkflowRun,
WORKFLOW_ONLY_LABEL,
getAgentPyChange,
getAgentPyChangeFromFiles,
manualReviewReasonForAgentPy,
countNewAgentParams,
listPullFiles,
touchesSdk,
hasManualOnlyLabel,
sensitivePathReasons,
prSizeReasons,
missingTestsReason,
secretScanReasons,
sdkTestChecksReason,
listAllComments,
getHeadCommitDate,
isStaleFinalAfterPush,
needsStaleFinalRecovery,
shouldSkipFinalRecovery,
FINAL_CLAUDE_REVIEW_BODY,
loadPrContext,
evaluatePipelineQuiescent,
selectMergeGateCandidates,
findMergeGateVerdict,
};
@@ -1,76 +0,0 @@
#!/usr/bin/env node
/**
* Run: node .github/scripts/pipeline-status-selftest.js
*/
const ps = require('./pipeline-status.js');
const mg = require('./merge-gate.js');
let failed = 0;
function assert(name, cond) {
if (!cond) {
console.error('FAIL:', name);
failed++;
} else {
console.log('ok:', name);
}
}
const kickComments = [
{ user: { login: 'MervinPraison' }, body: '@coderabbitai review' },
{ user: { login: 'MervinPraison' }, body: '/review' },
];
const finalComment = {
user: { login: 'MervinPraison' },
body: '@claude You are the FINAL architecture reviewer.',
created_at: '2026-06-26T10:00:00Z',
};
assert(
'reviews pending when not kicked',
ps.deriveStage([], { ready: false, reasons: ['no FINAL Claude review trigger'] })
=== 'pipeline/reviews-pending'
);
assert(
'final pending after kick',
ps.deriveStage(kickComments, { ready: false, reasons: ['no FINAL Claude review trigger'] })
=== 'pipeline/final-claude-pending'
);
assert(
'awaiting merge gate after final',
ps.deriveStage(
[...kickComments, finalComment],
{ ready: false, reasons: ['recent @claude within 35min'] }
) === 'pipeline/awaiting-merge-gate'
);
assert(
'merge ready',
ps.deriveStage([...kickComments, finalComment], { ready: true, reasons: [] })
=== 'pipeline/merge-ready'
);
const blockers = ps.deriveBlockerLabels({
ready: false,
reasons: ['CI not green on HEAD', 'SDK code added without test file changes — requires manual review'],
});
assert('maps ci blocker', blockers.includes('pipeline/blocked:ci'));
assert('maps manual blocker', blockers.includes('pipeline/blocked:manual-review'));
assert(
'internal link matches upstream base',
mg.isInternalPullRequestLink(
{ base: { repo: { full_name: 'MervinPraison/PraisonAI' } } },
'MervinPraison',
'PraisonAI'
)
);
assert(
'fork sync link rejected',
!mg.isInternalPullRequestLink(
{ number: 21, base: { repo: { full_name: 'Milkmange/PraisonAI' } } },
'MervinPraison',
'PraisonAI'
)
);
process.exit(failed ? 1 : 0);
-245
View File
@@ -1,245 +0,0 @@
/**
* Sync pipeline stage/blocker labels on open PRs.
* @see .github/workflows/pipeline-status-sync.yml
*/
const mergeGate = require('./merge-gate.js');
const chain = require('./bot-pr-review-chain.js');
const PIPELINE_PREFIX = 'pipeline/';
const STAGE_LABELS = [
'pipeline/reviews-pending',
'pipeline/final-claude-pending',
'pipeline/awaiting-merge-gate',
'pipeline/merge-ready',
'pipeline/merged',
];
const BLOCKER_LABELS = [
'pipeline/blocked:ci',
'pipeline/blocked:conflict',
'pipeline/blocked:manual-review',
'pipeline/blocked:cooldown',
'pipeline/blocked:stale-final',
'pipeline/blocked:no-final',
];
const ALL_PIPELINE_LABELS = [...STAGE_LABELS, ...BLOCKER_LABELS];
const LABEL_SPECS = [
{ name: 'pipeline/reviews-pending', color: 'fbca04', description: 'Waiting for CodeRabbit/Qodo/Copilot reviews' },
{ name: 'pipeline/final-claude-pending', color: 'd4c5f9', description: 'Reviews done; waiting for FINAL @claude' },
{ name: 'pipeline/awaiting-merge-gate', color: 'c5def5', description: 'FINAL done; waiting for merge gate / CI' },
{ name: 'pipeline/merge-ready', color: '0e8a16', description: 'Eligible for merge gate auto-merge' },
{ name: 'pipeline/merged', color: '6f42c1', description: 'Merged via pipeline' },
{ name: 'pipeline/blocked:ci', color: 'd93f0b', description: 'Blocked: CI not green on HEAD' },
{ name: 'pipeline/blocked:conflict', color: 'b60205', description: 'Blocked: merge conflict or rebase pending' },
{ name: 'pipeline/blocked:manual-review', color: 'e99695', description: 'Blocked: requires manual review' },
{ name: 'pipeline/blocked:cooldown', color: 'fef2c0', description: 'Blocked: post-push or @claude cooldown' },
{ name: 'pipeline/blocked:stale-final', color: 'f9d0c4', description: 'Blocked: FINAL stale after new commits' },
{ name: 'pipeline/blocked:no-final', color: 'ededed', description: 'Blocked: no FINAL @claude trigger yet' },
];
function deriveStage(comments, evalResult) {
if (evalResult.ready) return 'pipeline/merge-ready';
if (!chain.chainKickPosted(comments)) return 'pipeline/reviews-pending';
if (!mergeGate.hasFinalClaudeReviewTrigger(comments)) return 'pipeline/final-claude-pending';
return 'pipeline/awaiting-merge-gate';
}
function reasonToBlockerLabel(reason) {
const r = (reason || '').toLowerCase();
if (r.includes('ci not green') || r.includes('sdk code changed but no ci')) return 'pipeline/blocked:ci';
if (
r.includes('conflict') ||
r.includes('mergestate=dirty') ||
r.includes('not mergeable')
) return 'pipeline/blocked:conflict';
if (
r.includes('manual') ||
r.includes('requires manual') ||
r.includes('sensitive path') ||
r.includes('no-auto-merge') ||
r.includes('manual-only label') ||
r.includes('without test') ||
r.includes('files changed') ||
r.includes('possible secret') ||
r.includes('agent.py')
) return 'pipeline/blocked:manual-review';
if (r.includes('recent @claude') || r.includes('post-push buffer')) return 'pipeline/blocked:cooldown';
if (r.includes('stale final')) return 'pipeline/blocked:stale-final';
if (r.includes('no final claude')) return 'pipeline/blocked:no-final';
if (r.includes('final claude not complete')) return 'pipeline/blocked:stale-final';
if (r.includes('claude.yml in progress') || r.includes('claude-merge-gate-active')) {
return null;
}
return null;
}
function deriveBlockerLabels(evalResult) {
if (evalResult.ready) return [];
const labels = new Set();
for (const reason of evalResult.reasons || []) {
const mapped = reasonToBlockerLabel(reason);
if (mapped && mapped.startsWith('pipeline/blocked:')) labels.add(mapped);
}
return [...labels];
}
function computePipelineLabels(comments, evalResult) {
const stage = deriveStage(comments, evalResult);
const blockers = stage === 'pipeline/merge-ready' ? [] : deriveBlockerLabels(evalResult);
return { stage, blockers, all: [stage, ...blockers] };
}
async function ensurePipelineLabels(github, owner, repo, core) {
let existing;
if (typeof github.paginate === 'function') {
existing = await github.paginate(github.rest.issues.listLabelsForRepo, {
owner,
repo,
per_page: 100,
});
} else {
({ data: existing } = await github.rest.issues.listLabelsForRepo({
owner,
repo,
per_page: 100,
}));
}
const names = new Set(existing.map((l) => l.name));
for (const spec of LABEL_SPECS) {
if (names.has(spec.name)) continue;
try {
await github.rest.issues.createLabel({
owner,
repo,
name: spec.name,
color: spec.color,
description: spec.description,
});
core?.info?.(`Created label ${spec.name}`);
} catch (err) {
if (err.status !== 422) throw err;
core?.info?.(`Label ${spec.name} already exists`);
}
}
}
async function syncPipelineLabels(github, owner, repo, prNumber, core) {
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
if (ctx.pr.state !== 'open') return { synced: false, reason: 'not_open' };
const evalResult = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core
);
const { stage, blockers, all } = computePipelineLabels(ctx.comments, evalResult);
const current = ctx.labels.filter((l) => l.startsWith(PIPELINE_PREFIX));
const desired = new Set(all);
const toRemove = current.filter((l) => !desired.has(l) && l !== 'pipeline/merged');
const toAdd = all.filter((l) => !current.includes(l));
for (const name of toRemove) {
try {
await github.rest.issues.removeLabel({ owner, repo, issue_number: prNumber, name });
} catch (err) {
if (err.status !== 404) throw err;
}
}
if (toAdd.length) {
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: toAdd,
});
}
core?.info?.(`PR #${prNumber}: stage=${stage} blockers=[${blockers.join(', ')}]`);
return {
synced: true,
stage,
blockers,
ready: evalResult.ready,
reasons: evalResult.reasons,
labels: ctx.labels,
createdAt: new Date(ctx.pr.created_at).getTime(),
};
}
async function dispatchMergeGateForOldestReady(github, owner, repo, readyCandidates, core) {
if (!readyCandidates.length) return 0;
readyCandidates.sort((a, b) => a.createdAt - b.createdAt);
for (const cand of readyCandidates) {
if ((cand.labels || []).includes('claude-merge-gate-active')) {
core?.info?.(`Skip dispatch PR #${cand.prNumber}: merge gate already active`);
continue;
}
await github.rest.repos.createDispatchEvent({
owner,
repo,
event_type: 'claude-merge-gate',
client_payload: { pr_number: cand.prNumber },
});
core?.info?.(`Dispatched merge gate for ready PR #${cand.prNumber}`);
return cand.prNumber;
}
return 0;
}
async function syncOpenPullRequests(github, owner, repo, options, core) {
const { maxPrs = 20, dispatchMergeGate = true } = options || {};
await ensurePipelineLabels(github, owner, repo, core);
let prs;
if (maxPrs <= 100) {
({ data: prs } = await github.rest.pulls.list({
owner,
repo,
state: 'open',
per_page: 100,
}));
} else {
prs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
}
let synced = 0;
const readyCandidates = [];
for (const pr of prs) {
if (synced >= maxPrs) break;
if (pr.draft) continue;
if (pr.head?.repo?.full_name && pr.head.repo.full_name !== `${owner}/${repo}`) continue;
const result = await syncPipelineLabels(github, owner, repo, pr.number, core);
if (result.ready) {
readyCandidates.push({
prNumber: pr.number,
createdAt: result.createdAt || new Date(pr.created_at).getTime(),
labels: result.labels || [],
});
}
synced += 1;
}
let dispatched = 0;
if (dispatchMergeGate && readyCandidates.length) {
dispatched = await dispatchMergeGateForOldestReady(
github, owner, repo, readyCandidates, core
);
}
core?.info?.(`Pipeline label sync complete (${synced} PR(s), dispatched=${dispatched || 'none'})`);
return synced;
}
module.exports = {
STAGE_LABELS,
BLOCKER_LABELS,
ALL_PIPELINE_LABELS,
deriveStage,
deriveBlockerLabels,
computePipelineLabels,
ensurePipelineLabels,
syncPipelineLabels,
syncOpenPullRequests,
dispatchMergeGateForOldestReady,
};
-3
View File
@@ -1,3 +0,0 @@
# GitHub Issue #1350: Add debug log on successful branch checkout
The github_create_branch function in src/praisonai-agents/praisonaiagents/tools/github_tools.py should call logger.debug after a successful checkout.
-46
View File
@@ -1,46 +0,0 @@
name: Auto Issue Comment
on:
workflow_dispatch:
# issues:
# types: [opened]
jobs:
# Trigger Claude by adding the 'claude' label (only if not already labeled)
# Note: @claude in comments only works from human users (not bots)
# The claude.yml workflow listens for issues:labeled event with 'claude' label
trigger-claude:
# Skip if issue was already created with 'claude' label to avoid double-trigger
if: |
!contains(toJSON(github.event.issue.labels), 'claude')
runs-on: ubuntu-latest
permissions:
issues: write
steps:
- name: Add claude label to trigger analysis
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
// Check if 'claude' label already exists on this issue
const labels = context.payload.issue.labels.map(l => l.name);
if (labels.includes('claude')) {
console.log('Issue already has claude label, skipping');
return;
}
// Add label to trigger claude.yml via issues:labeled event
await github.rest.issues.addLabels({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['claude']
});
// Also post instructions as a comment for context
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '🤖 Auto-triaging with Claude. Analyzing issue and implementing a fix...\n\nClaude will:\n1. Classify this issue\n2. Search codebase for root cause\n3. Implement a minimal fix following AGENTS.md\n4. Create a PR with the changes'
});
-684
View File
@@ -1,684 +0,0 @@
name: Auto PR Comment
# Review chain: CodeRabbit/Qodo/Gemini review first → Copilot → Claude (final)
# For human PRs: CodeRabbit/Qodo auto-review → this workflow triggers Copilot → then Claude
# For bot PRs: CodeRabbit/Qodo skip bots, so we trigger them explicitly first
#
# IMPORTANT: @copilot ignores bot comments. All @copilot mentions must use
# PAT_TOKEN so the comment posts as MervinPraison (a real user).
#
# Claude trigger runs IN-BAND (same workflow) to avoid GitHub Actions
# blocking bot-triggered workflows with action_required approval gates.
on:
issue_comment:
types: [created]
pull_request_review:
types: [submitted]
pull_request:
types: [opened, synchronize]
schedule:
- cron: '30 3,9,15,21 * * *'
workflow_dispatch:
env:
MAX_POLL_ATTEMPTS: 20
POLL_DELAY_MS: 30000
CLAUDE_TRIGGER_LOGINS: '["MervinPraison","github-actions[bot]"]'
jobs:
# ================================================================
# HUMAN PRs: Trigger @copilot AFTER CodeRabbit posts its summary
# ================================================================
copilot-after-coderabbit:
if: |
github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
github.event.comment.user.login == 'coderabbitai[bot]' &&
contains(github.event.comment.body, 'summarize by coderabbit')
runs-on: ubuntu-latest
permissions:
pull-requests: write
outputs:
pr_number: ${{ steps.trigger.outputs.pr_number }}
triggered: ${{ steps.trigger.outputs.triggered }}
steps:
- name: Post Copilot review request (as user via PAT)
id: trigger
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const comments = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});
// Check for duplicates — posted by MervinPraison (PAT) or github-actions[bot] (legacy)
const alreadyPosted = comments.data.some(c =>
c.body.includes('@copilot') &&
(c.user.login === 'MervinPraison' || c.user.login === 'github-actions[bot]')
);
if (alreadyPosted) {
console.log('Copilot already triggered, skipping');
core.setOutput('triggered', 'false');
core.setOutput('pr_number', context.issue.number.toString());
return;
}
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first — incorporate their findings.\n\nReview areas:\n1. **Bloat check**: Are changes minimal and focused? Any unnecessary code or scope creep?\n2. **Security**: Any hardcoded secrets, unsafe eval/exec, missing input validation?\n3. **Performance**: Any module-level heavy imports? Hot-path regressions?\n4. **Tests**: Are tests included? Do they cover the changes adequately?\n5. **Backward compat**: Any public API changes without deprecation?\n6. **Code quality**: DRY violations, naming conventions, error handling?\n7. **Address reviewer feedback**: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review\n8. Suggest specific improvements with code examples where possible'
});
core.setOutput('triggered', 'true');
core.setOutput('pr_number', context.issue.number.toString());
# Fallback: Trigger @copilot after Qodo review (if Coderabbit doesn't fire)
copilot-after-qodo:
if: |
github.event_name == 'pull_request_review' &&
github.event.review.user.login == 'qodo-code-review[bot]'
runs-on: ubuntu-latest
permissions:
pull-requests: write
outputs:
pr_number: ${{ steps.trigger.outputs.pr_number }}
triggered: ${{ steps.trigger.outputs.triggered }}
steps:
- name: Post Copilot review request (as user via PAT)
id: trigger
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const comments = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c =>
c.body.includes('@copilot') &&
(c.user.login === 'MervinPraison' || c.user.login === 'github-actions[bot]')
);
if (alreadyPosted) {
console.log('Copilot already triggered, skipping');
core.setOutput('triggered', 'false');
core.setOutput('pr_number', context.issue.number.toString());
return;
}
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above first.\n\nReview areas:\n1. **Bloat check**: Are changes minimal and focused?\n2. **Security**: Any hardcoded secrets, unsafe eval/exec, missing input validation?\n3. **Performance**: Any module-level heavy imports? Hot-path regressions?\n4. **Tests**: Are tests included? Do they cover the changes adequately?\n5. **Backward compat**: Any public API changes without deprecation?\n6. **Code quality**: DRY violations, naming conventions, error handling?\n7. Suggest specific improvements with code examples where possible'
});
core.setOutput('triggered', 'true');
core.setOutput('pr_number', context.issue.number.toString());
# ================================================================
# FALLBACK: Claude trigger for cases where Copilot chain fails/skips
# Ensures Claude always runs after sufficient review time
# ================================================================
claude-fallback-timeout:
if: |
github.event_name == 'pull_request' &&
github.event.action == 'opened'
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
issues: write
pull-requests: write
steps:
- name: Wait for review window (15 min)
run: sleep 900
- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c => {
const body = (c.body || '').toLowerCase();
if (!body.includes('@claude') || !triggerLogins.includes(c.user.login)) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
});
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
core.setOutput('comments_count', comments.data.length.toString());
// Collect all bot reviewer comments for context
const botReviews = comments.data.filter(c =>
['coderabbitai[bot]', 'qodo-code-review[bot]', 'gemini-code-assist[bot]',
'copilot-swe-agent', 'Copilot', 'greptile-apps[bot]'].some(bot =>
c.user.login.toLowerCase().includes(bot.toLowerCase())
)
);
core.setOutput('review_count', botReviews.length.toString());
- name: Aggregate reviews and trigger Claude
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const comments = await github.rest.issues.listComments({issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, per_page: 100});
const reviews = {coderabbit: [], qodo: [], gemini: [], copilot: [], greptile: [], others: []};
comments.data.forEach(c => {
const login = c.user.login.toLowerCase();
const body = c.body.substring(0, 500);
if (login.includes('coderabbit')) reviews.coderabbit.push(body);
else if (login.includes('qodo')) reviews.qodo.push(body);
else if (login.includes('gemini')) reviews.gemini.push(body);
else if (login.includes('copilot')) reviews.copilot.push(body);
else if (login.includes('greptile')) reviews.greptile.push(body);
else if (c.user.type === 'Bot') reviews.others.push(body);
});
const summaryParts = [];
if (reviews.coderabbit.length) summaryParts.push('CodeRabbit: ' + reviews.coderabbit.length + ' comments');
if (reviews.qodo.length) summaryParts.push('Qodo: ' + reviews.qodo.length + ' comments');
if (reviews.gemini.length) summaryParts.push('Gemini: ' + reviews.gemini.length + ' comments');
if (reviews.copilot.length) summaryParts.push('Copilot: ' + reviews.copilot.length + ' comments');
if (reviews.greptile.length) summaryParts.push('Greptile: ' + reviews.greptile.length + ' comments');
const reviewSummary = summaryParts.length ? summaryParts.join(' | ') : 'No bot reviews detected';
const claudeBody = '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. ' + reviewSummary + '.\n\n' +
'Review Context: ' + comments.data.length + ' total comments, ' +
(reviews.coderabbit.length + reviews.qodo.length + reviews.gemini.length + reviews.copilot.length + reviews.greptile.length) + ' bot reviews.\n\n' +
'Phase 1: Review per AGENTS.md principles (protocol-driven, backward compatible, no perf impact, SDK value gate — no feature bloat).\n' +
'Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.\n' +
'Phase 2: FIX valid issues found by prior reviewers.\n' +
'Phase 3: Provide final verdict (approve or request changes).';
await github.rest.issues.createComment({issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: claudeBody});
console.log('Claude fallback triggered on PR #' + context.issue.number + ' with ' + reviewSummary);
# ================================================================
# Claude FINAL review — polls for Copilot's response, then triggers
# This runs IN-BAND under the PAT context (MervinPraison), avoiding
# the action_required approval gate that blocks bot-triggered workflows.
# ================================================================
claude-after-copilot:
needs: [copilot-after-coderabbit]
if: |
always() &&
needs.copilot-after-coderabbit.result == 'success' &&
needs.copilot-after-coderabbit.outputs.triggered == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Wait for Copilot to respond (poll)
id: poll
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
MAX_POLL_ATTEMPTS: ${{ env.MAX_POLL_ATTEMPTS }}
POLL_DELAY_MS: ${{ env.POLL_DELAY_MS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const owner = context.repo.owner;
const repo = context.repo.repo;
// Poll for Copilot's response — check every 30s for up to 10 minutes
const maxAttempts = parseInt(process.env.MAX_POLL_ATTEMPTS, 10);
const delayMs = parseInt(process.env.POLL_DELAY_MS, 10);
for (let i = 0; i < maxAttempts; i++) {
console.log(`Poll attempt ${i + 1}/${maxAttempts}...`);
const comments = await github.rest.issues.listComments({
issue_number: pr,
owner,
repo,
per_page: 50,
sort: 'created',
direction: 'desc'
});
// Look for Copilot's response (copilot-swe-agent or Copilot)
const copilotResponse = comments.data.find(c =>
(c.user.login === 'copilot-swe-agent' ||
c.user.login === 'Copilot' ||
c.user.login.toLowerCase().includes('copilot')) &&
c.user.type === 'Bot'
);
if (copilotResponse) {
console.log(`Copilot responded at ${copilotResponse.created_at}`);
core.setOutput('copilot_responded', 'true');
return;
}
// Also check PR reviews for Copilot
const reviews = await github.rest.pulls.listReviews({
pull_number: pr,
owner,
repo
});
const copilotReview = reviews.data.find(r =>
r.user.login.toLowerCase().includes('copilot')
);
if (copilotReview) {
console.log(`Copilot review submitted at ${copilotReview.submitted_at}`);
core.setOutput('copilot_responded', 'true');
return;
}
if (i < maxAttempts - 1) {
console.log(`Copilot hasn't responded yet, waiting ${delayMs/1000}s...`);
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
console.log('Copilot did not respond within timeout, triggering Claude anyway');
core.setOutput('copilot_responded', 'timeout');
- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c => {
const body = (c.body || '').toLowerCase();
if (!body.includes('@claude') || !triggerLogins.includes(c.user.login)) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
});
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
- name: Trigger Claude final review
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
await github.rest.issues.createComment({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep\n5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items'
});
console.log(`Claude triggered on PR #${pr}`);
- name: Sync pipeline status labels
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-coderabbit.outputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const pr = parseInt(process.env.PR_NUMBER, 10);
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, pr, core);
# ================================================================
# Claude after Qodo fallback path (same polling approach)
# ================================================================
claude-after-copilot-qodo:
needs: [copilot-after-qodo]
if: |
always() &&
needs.copilot-after-qodo.result == 'success' &&
needs.copilot-after-qodo.outputs.triggered == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Wait for Copilot to respond (poll)
id: poll
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }}
MAX_POLL_ATTEMPTS: ${{ env.MAX_POLL_ATTEMPTS }}
POLL_DELAY_MS: ${{ env.POLL_DELAY_MS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const owner = context.repo.owner;
const repo = context.repo.repo;
const maxAttempts = parseInt(process.env.MAX_POLL_ATTEMPTS, 10);
const delayMs = parseInt(process.env.POLL_DELAY_MS, 10);
for (let i = 0; i < maxAttempts; i++) {
console.log(`Poll attempt ${i + 1}/${maxAttempts}...`);
const comments = await github.rest.issues.listComments({
issue_number: pr, owner, repo,
per_page: 50, sort: 'created', direction: 'desc'
});
const copilotResponse = comments.data.find(c =>
(c.user.login === 'copilot-swe-agent' ||
c.user.login === 'Copilot' ||
c.user.login.toLowerCase().includes('copilot')) &&
c.user.type === 'Bot'
);
if (copilotResponse) {
console.log(`Copilot responded at ${copilotResponse.created_at}`);
core.setOutput('copilot_responded', 'true');
return;
}
const reviews = await github.rest.pulls.listReviews({
pull_number: pr, owner, repo
});
const copilotReview = reviews.data.find(r =>
r.user.login.toLowerCase().includes('copilot')
);
if (copilotReview) {
console.log(`Copilot review submitted at ${copilotReview.submitted_at}`);
core.setOutput('copilot_responded', 'true');
return;
}
if (i < maxAttempts - 1) {
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
console.log('Copilot did not respond within timeout, triggering Claude anyway');
core.setOutput('copilot_responded', 'timeout');
- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }}
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c => {
const body = (c.body || '').toLowerCase();
if (!body.includes('@claude') || !triggerLogins.includes(c.user.login)) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
});
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
- name: Trigger Claude final review
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const pr = parseInt(process.env.PR_NUMBER, 10);
await github.rest.issues.createComment({
issue_number: pr,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep\n5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items'
});
console.log(`Claude triggered on PR #${pr}`);
- name: Sync pipeline status labels
uses: actions/github-script@v7
env:
PR_NUMBER: ${{ needs.copilot-after-qodo.outputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const pr = parseInt(process.env.PR_NUMBER, 10);
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, pr, core);
# ================================================================
# Claude after Gemini — restores the trigger lost from the deleted
# chain-claude-after-copilot.yml workflow.
# Fires when Gemini posts a review comment on a PR or issue.
# ================================================================
claude-after-gemini:
if: |
github.event_name == 'issue_comment' &&
(
github.event.comment.user.login == 'gemini-code-assist[bot]' ||
contains(github.event.comment.body, 'Review completed by Gemini CLI') ||
contains(github.event.comment.body, 'The fix is ready for review. Please test')
)
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: write
steps:
- name: Check if Claude already triggered
id: check_claude
uses: actions/github-script@v7
env:
TRIGGER_LOGINS: ${{ env.CLAUDE_TRIGGER_LOGINS }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const triggerLogins = JSON.parse(process.env.TRIGGER_LOGINS);
const comments = await github.rest.issues.listComments({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo
});
const alreadyPosted = comments.data.some(c => {
const body = (c.body || '').toLowerCase();
if (!body.includes('@claude') || !triggerLogins.includes(c.user.login)) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
});
core.setOutput('already_triggered', alreadyPosted ? 'true' : 'false');
- name: Post Claude analysis request (Lead Engineer)
if: steps.check_claude.outputs.already_triggered != 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: '@claude You are the Lead Engineer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL analysis and reviews above carefully (Gemini, CodeRabbit, Qodo, Copilot, etc).\n\n**Phase 1: Review per AGENTS.md**\n1. Protocol-driven: check heavy implementations vs core SDK\n2. Backward compatible: ensure zero feature regressions\n3. Performance: no hot-path regressions\n4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep\n5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.\n\n**Phase 2: FIX Valid Issues**\n5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix\n6. Push all code fixes directly to THIS branch (do NOT create a new PR)\n7. Comment a summary of exact files modified and what you skipped\n\n**Phase 3: Final Verdict**\n8. If all issues are resolved, approve the PR / close the Issue\n9. If blocking issues remain, request changes / leave clear action items'
});
# ================================================================
# Recovery: post FINAL @claude if merge-conflict comment blocked the chain
# (e.g. conflict fired before claude-after-copilot could post review prompt)
# ================================================================
claude-review-recovery:
if: |
github.event_name == 'pull_request' &&
github.event.action == 'synchronize'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: read
steps:
- uses: actions/checkout@v4
- name: Re-post FINAL Claude review when stale or missing
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const pr = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;
const comments = await mergeGate.listAllComments(github, owner, repo, pr);
const headPushedAt = (await mergeGate.getHeadCommitDate(github, owner, repo, pr))
|| context.payload.pull_request.updated_at;
const hasFinal = mergeGate.hasFinalClaudeReviewTrigger(comments);
const isStale = mergeGate.isStaleFinalAfterPush(comments, headPushedAt);
if (mergeGate.shouldSkipFinalRecovery(comments, headPushedAt)) {
core.info('Recent @claude within 35min and FINAL not stale — skip recovery');
return;
}
const finalBody = mergeGate.FINAL_CLAUDE_REVIEW_BODY;
if (hasFinal && isStale) {
await github.rest.issues.createComment({ issue_number: pr, owner, repo, body: finalBody });
core.info(`Posted stale-FINAL recovery @claude on PR #${pr}`);
return;
}
if (hasFinal) {
core.info('FINAL Claude review current on HEAD — skip recovery');
return;
}
const hasReviewerActivity = comments.some(c => {
const login = c.user.login.toLowerCase();
return login.includes('coderabbit') || login.includes('copilot') ||
login.includes('greptile') || login.includes('qodo') || login.includes('gemini');
});
if (!hasReviewerActivity) {
core.info('No bot reviewer activity yet, skipping recovery');
return;
}
await github.rest.issues.createComment({ issue_number: pr, owner, repo, body: finalBody });
core.info(`Posted missing FINAL Claude review on PR #${pr}`);
- name: Sync pipeline status labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const pr = context.payload.pull_request.number;
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, pr, core);
# Scheduled: re-post @claude when FINAL is stale (Claude pushed after last FINAL trigger)
stale-final-recovery-scheduled:
if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
issues: write
pull-requests: read
steps:
- uses: actions/checkout@v4
- name: Re-post stale FINAL @claude on open internal PRs
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const prs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', per_page: 100,
});
let posted = 0;
for (const pr of prs) {
if (pr.draft) continue;
if (pr.head?.repo?.full_name && pr.head.repo.full_name !== `${owner}/${repo}`) continue;
const comments = await mergeGate.listAllComments(github, owner, repo, pr.number);
const headPushedAt = (await mergeGate.getHeadCommitDate(github, owner, repo, pr.number)) || pr.updated_at;
if (!mergeGate.needsStaleFinalRecovery(comments, headPushedAt)) continue;
if (mergeGate.hasRecentClaudeTrigger(comments, 10)) {
core.info(`PR #${pr.number}: stale FINAL but @claude within 10min — skip`);
continue;
}
await github.rest.issues.createComment({
issue_number: pr.number, owner, repo,
body: mergeGate.FINAL_CLAUDE_REVIEW_BODY,
});
posted++;
core.info(`Posted scheduled stale-FINAL recovery on PR #${pr.number}`);
if (posted >= 5) break;
}
core.info(`Scheduled stale-FINAL recovery complete (${posted} posted)`);
# ================================================================
# BOT PRs: CodeRabbit/Qodo skip bot-authored PRs by default.
# Trigger them explicitly. Do NOT trigger Copilot here — it will
# be triggered by copilot-after-coderabbit/qodo when they finish.
# ================================================================
bot-pr-trigger-reviews:
if: |
github.event_name == 'pull_request' &&
github.event.action == 'opened' &&
(
github.event.pull_request.user.login == 'github-actions[bot]' ||
github.event.pull_request.user.login == 'praisonai-triage-agent[bot]' ||
github.event.pull_request.user.type == 'Bot'
)
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Trigger CodeRabbit and Qodo reviews
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/bot-pr-review-chain.js'));
const pr = context.payload.pull_request.number;
await chain.kickReviewChain(github, context.repo.owner, context.repo.repo, pr, core);
- name: Sync pipeline status labels
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const pr = context.payload.pull_request.number;
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, pr, core);
-153
View File
@@ -1,153 +0,0 @@
name: Performance Benchmarks
on:
workflow_dispatch:
inputs:
iterations:
description: 'Number of benchmark iterations'
required: false
default: '100'
fail_on_regression:
description: 'Fail if performance regresses'
required: false
default: 'true'
jobs:
benchmark:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e src/praisonai-agents[all]
pip install agno crewai langgraph
- name: Run Import Time Benchmark
id: import_time
env:
FAIL_ON_REGRESSION: ${{ inputs.fail_on_regression }}
run: |
cd src/praisonai-agents
python benchmarks/import_time.py 2>&1 | tee import_time_output.txt
# Extract import time
IMPORT_TIME=$(grep -oP 'Median:\s+\K[\d.]+' import_time_output.txt || echo "0")
echo "import_time=$IMPORT_TIME" >> $GITHUB_OUTPUT
# Check threshold (200ms)
if (( $(echo "$IMPORT_TIME > 200" | bc -l) )); then
echo "::warning::Import time ${IMPORT_TIME}ms exceeds 200ms threshold"
if [ "$FAIL_ON_REGRESSION" == "true" ]; then
echo "import_passed=false" >> $GITHUB_OUTPUT
fi
else
echo "import_passed=true" >> $GITHUB_OUTPUT
fi
- name: Run Memory Usage Benchmark
id: memory
env:
FAIL_ON_REGRESSION: ${{ inputs.fail_on_regression }}
run: |
cd src/praisonai-agents
python benchmarks/memory_usage.py 2>&1 | tee memory_output.txt
# Extract peak memory
PEAK_MEMORY=$(grep -oP 'Peak:\s+\K[\d.]+' memory_output.txt || echo "0")
echo "peak_memory=$PEAK_MEMORY" >> $GITHUB_OUTPUT
# Check threshold (30MB)
if (( $(echo "$PEAK_MEMORY > 30" | bc -l) )); then
echo "::warning::Memory usage ${PEAK_MEMORY}MB exceeds 30MB threshold"
if [ "$FAIL_ON_REGRESSION" == "true" ]; then
echo "memory_passed=false" >> $GITHUB_OUTPUT
fi
else
echo "memory_passed=true" >> $GITHUB_OUTPUT
fi
- name: Run Simple Benchmark
id: simple
run: |
cd src/praisonai-agents
python benchmarks/simple_benchmark.py 2>&1 | tee simple_output.txt
# Extract PraisonAI time
PRAISON_TIME=$(grep -oP 'PraisonAI\s+\K[\d.]+' simple_output.txt | head -1 || echo "0")
echo "praison_time=$PRAISON_TIME" >> $GITHUB_OUTPUT
- name: Run Tools Benchmark
id: tools
run: |
cd src/praisonai-agents
python benchmarks/tools_benchmark.py 2>&1 | tee tools_output.txt
# Extract PraisonAI time with tools
PRAISON_TOOLS_TIME=$(grep -oP 'PraisonAI\s+\K[\d.]+' tools_output.txt | head -1 || echo "0")
echo "praison_tools_time=$PRAISON_TOOLS_TIME" >> $GITHUB_OUTPUT
- name: Run Parameter Impact Benchmark
id: param_impact
env:
INPUT_ITERATIONS: ${{ inputs.iterations }}
run: |
cd src/praisonai-agents
python benchmarks/param_impact_benchmark.py --no-save --iterations "$INPUT_ITERATIONS" 2>&1 | tee param_impact_output.txt
- name: Check Lazy Imports
id: lazy_imports
run: |
cd src/praisonai-agents
python -c "
import sys
from praisonaiagents import Agent
agent = Agent(name='Test', output='silent')
# Check litellm is NOT loaded
if 'litellm' in sys.modules:
print('FAIL: litellm loaded during silent agent creation')
exit(1)
else:
print('PASS: litellm not loaded (lazy import working)')
"
- name: Generate Benchmark Summary
if: always()
run: |
echo "## Performance Benchmark Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Metric | Value | Threshold | Status |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|-----------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Import Time | ${{ steps.import_time.outputs.import_time }}ms | <200ms | ${{ steps.import_time.outputs.import_passed == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Peak Memory | ${{ steps.memory.outputs.peak_memory }}MB | <30MB | ${{ steps.memory.outputs.memory_passed == 'true' && '✅' || '❌' }} |" >> $GITHUB_STEP_SUMMARY
echo "| Agent Instantiation | ${{ steps.simple.outputs.praison_time }}μs | - | ℹ️ |" >> $GITHUB_STEP_SUMMARY
echo "| Agent with Tools | ${{ steps.tools.outputs.praison_tools_time }}μs | - | ℹ️ |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Lazy Import Check" >> $GITHUB_STEP_SUMMARY
echo "✅ litellm not loaded during silent agent creation" >> $GITHUB_STEP_SUMMARY
- name: Fail on Regression
if: inputs.fail_on_regression == 'true'
run: |
if [ "${{ steps.import_time.outputs.import_passed }}" == "false" ]; then
echo "::error::Import time regression detected"
exit 1
fi
if [ "${{ steps.memory.outputs.memory_passed }}" == "false" ]; then
echo "::error::Memory usage regression detected"
exit 1
fi
echo "All performance checks passed!"
-43
View File
@@ -1,43 +0,0 @@
name: Bot PR Recovery
# Belt-and-braces: kick CodeRabbit/Qodo when pull_request:opened never fired for bot PRs.
on:
schedule:
- cron: '*/15 * * * *'
workflow_dispatch:
inputs:
pr_number:
description: 'Specific PR number to kick (optional)'
required: false
type: string
permissions:
pull-requests: write
issues: write
jobs:
recover:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Find and kick stalled bot PRs
uses: actions/github-script@v7
env:
PR_NUMBER_INPUT: ${{ github.event.inputs.pr_number }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/bot-pr-review-chain.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const manual = (process.env.PR_NUMBER_INPUT || '').trim();
const prNumber = manual ? parseInt(manual, 10) : null;
if (manual && Number.isNaN(prNumber)) {
throw new Error(`Invalid pr_number: ${manual}`);
}
await chain.recoverStalledBotPrs(github, owner, repo, { prNumber }, core);
-43
View File
@@ -1,43 +0,0 @@
name: Build Claude Code Image
on:
workflow_dispatch:
inputs:
version:
description: 'Image version tag'
required: true
default: 'latest'
jobs:
build:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GH_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: ./.github/actions/claude-code-action
push: true
platforms: linux/amd64
tags: ghcr.io/mervinpraison/praisonai-claudecode:${{ inputs.version }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: false
-35
View File
@@ -1,35 +0,0 @@
name: Claude Issue Triage
description: "Automatically triage GitHub issues using Claude Code"
on:
workflow_dispatch:
# issues:
# types: [opened]
jobs:
triage-issue:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
issues: write
id-token: write
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
token: ${{ steps.app-token.outputs.token }}
- name: Run Claude Issue Triage
uses: ./.github/actions/claude-issue-triage-action
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
-504
View File
@@ -1,504 +0,0 @@
name: Claude PR Merge Gate
# Read-only Claude assesses merge readiness; GH_TOKEN merges (no @claude, no gh pr review).
# Does not overlap with merge-conflict-claude.yml or auto-pr-comment @claude chains.
on:
schedule:
# Scan for merge-ready PRs every 10 minutes (assess runs only when candidates exist).
- cron: '*/10 * * * *'
workflow_dispatch:
inputs:
pr_number:
description: 'Optional PR number (scan all open internal PRs if empty)'
required: false
type: string
merge_method:
description: 'Merge method'
required: false
type: choice
default: merge
options:
- merge
- squash
- rebase
workflow_run:
workflows:
- Claude Assistant
- Core Tests
types:
- completed
repository_dispatch:
types:
- claude-merge-gate
concurrency:
group: claude-merge-gate-${{ github.repository }}-${{ github.event.inputs.pr_number || github.event.client_payload.pr_number || github.event.workflow_run.id || 'scan' }}
cancel-in-progress: false
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
MAX_CANDIDATES: 1
DISPATCH_PR_NUMBER: ${{ github.event.inputs.pr_number || github.event.client_payload.pr_number || '' }}
permissions:
contents: read
pull-requests: read
issues: read
actions: read
jobs:
auto-dispatch:
if: github.event_name == 'workflow_run'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
actions: write
steps:
- uses: actions/checkout@v4
- name: Dispatch merge gate when PR pipeline is ready
uses: actions/github-script@v7
env:
PAT: ${{ secrets.GH_TOKEN }}
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const wr = context.payload.workflow_run;
const owner = context.repo.owner;
const repo = context.repo.repo;
if (!process.env.PAT) {
core.setFailed('GH_TOKEN secret is required for merge gate auto-dispatch (needs repo + workflow scope).');
return;
}
if (wr.conclusion !== 'success') {
core.info(`Skip dispatch: ${wr.name} conclusion=${wr.conclusion}`);
return;
}
const allowed = ['Claude Assistant', 'Core Tests'];
if (!allowed.includes(wr.name)) {
core.info(`Skip dispatch: workflow ${wr.name}`);
return;
}
const prNumber = await mergeGate.resolvePrNumberFromWorkflowRun(github, owner, repo, wr);
if (!prNumber) {
core.info('Skip dispatch: could not resolve PR from workflow run');
return;
}
const fromClaude = wr.name === 'Claude Assistant';
const check = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, {
skipGlobalClaudeRunCheck: fromClaude,
skipRecentClaudeCooldown: fromClaude,
}
);
if (!check.ready) {
core.info(`PR #${prNumber} not ready: ${check.reasons.join(', ')}`);
return;
}
await github.rest.repos.createDispatchEvent({
owner,
repo,
event_type: 'claude-merge-gate',
client_payload: { pr_number: prNumber },
});
core.info(`Dispatched merge gate for PR #${prNumber} after ${wr.name}`);
scan-candidates:
if: github.event_name != 'workflow_run'
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: write
actions: read
outputs:
matrix: ${{ steps.scan.outputs.matrix }}
has_candidates: ${{ steps.scan.outputs.has_candidates }}
steps:
- uses: actions/checkout@v4
- name: Scan open PRs for merge gate eligibility
id: scan
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const maxCandidates = parseInt(process.env.MAX_CANDIDATES || '5', 10);
const inputPr = String(process.env.DISPATCH_PR_NUMBER || '').trim();
const candidates = [];
const skipped = [];
if (inputPr) {
const n = parseInt(inputPr, 10);
if (Number.isNaN(n)) {
throw new Error(`Invalid pr_number input: ${inputPr}`);
}
const result = await mergeGate.evaluatePipelineQuiescent(github, owner, repo, n, core);
if (result.ready) {
candidates.push({ pr_number: n, head_sha: result.headSha });
} else {
skipped.push({ pr: n, reasons: result.reasons });
}
} else {
const prs = await github.paginate(github.rest.pulls.list, {
owner, repo, state: 'open', per_page: 100, sort: 'created', direction: 'asc',
});
const prNumbers = prs.map((p) => p.number);
const selected = await mergeGate.selectMergeGateCandidates(
github, owner, repo, prNumbers, maxCandidates, core
);
candidates.push(...selected.candidates);
skipped.push(...selected.skipped);
}
core.info('Skipped: ' + JSON.stringify(skipped));
core.setOutput('matrix', JSON.stringify({ include: candidates }));
core.setOutput('has_candidates', candidates.length > 0 ? 'true' : 'false');
core.summary.addRaw('## Merge gate scan\n');
core.summary.addRaw(`Candidates: ${candidates.length}\n`);
core.summary.addRaw('```json\n' + JSON.stringify({ candidates, skipped }, null, 2) + '\n```');
await core.summary.write();
if (inputPr && skipped.length === 1 && skipped[0].pr === parseInt(inputPr, 10)) {
const reasons = skipped[0].reasons;
const body = [
'**Merge gate scan** — not eligible for auto-merge.',
'',
...reasons.map((r) => `- \`${r}\``),
'',
'**Actions:** wait for CI and the Claude review chain, or add label `needs-manual-review` and merge manually.',
'**Opt out:** label `no-auto-merge`.',
].join('\n');
await github.rest.issues.createComment({
owner, repo, issue_number: skipped[0].pr, body,
});
} else if (inputPr && candidates.length === 1) {
await github.rest.issues.createComment({
owner, repo, issue_number: candidates[0].pr_number,
body: '**Merge gate scan** — eligible for assessment. Claude merge gate will assess and may auto-merge if `MERGE_GATE_VERDICT: APPROVE`.',
});
}
await ps.syncOpenPullRequests(github, owner, repo, { maxPrs: 20 }, core);
claude-assess:
needs: scan-candidates
if: needs.scan-candidates.outputs.has_candidates == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.scan-candidates.outputs.matrix) }}
permissions:
contents: read
pull-requests: read
issues: write
actions: read
id-token: write
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Mark merge gate active
id: mark_active
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
core.setOutput('started_at', new Date().toISOString());
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: ${{ matrix.pr_number }},
labels: ['claude-merge-gate-active'],
});
- name: Stash merge gate helpers
run: cp .github/scripts/merge-gate.js /tmp/merge-gate.js
- name: Fetch PR branch
env:
PR_NUMBER: ${{ matrix.pr_number }}
run: |
git fetch origin "pull/${PR_NUMBER}/head:pr-${PR_NUMBER}"
git checkout "pr-${PR_NUMBER}"
- name: Claude merge gate assessment (read-only)
id: assess
continue-on-error: true
uses: ./.github/actions/claude-code-action
env:
GH_TOKEN: ${{ steps.app-token.outputs.token }}
CLAUDE_CODE_SUBAGENT_MODEL: inherit
ANTHROPIC_DEFAULT_SONNET_MODEL: claude-sonnet-4-6
with:
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
model: claude-opus-4-8
trigger_phrase: '@claude-merge-gate-internal'
direct_prompt: |
You are the MERGE GATE reviewer for PraisonAI PR #${{ matrix.pr_number }}.
READ-ONLY: do not modify code, commit, push, rebase, or create branches.
CRITICAL OUTPUT RULE:
Post exactly one PR comment via GitHub MCP containing either:
MERGE_GATE_VERDICT: APPROVE
or
MERGE_GATE_VERDICT: BLOCK
followed by a short rationale on the next lines.
NEVER include @claude in any comment. NEVER mention merge conflict resolution.
Review checklist:
1. Read AGENTS.md and the PR diff on the current branch.
2. Read all PR comments and reviews (FINAL architecture reviewer / Lead Engineer must have run).
3. Confirm SDK value gate: change strengthens praisonaiagents (no scope creep).
4. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
5. If agent.py adds >100 lines or new Agent __init__ params, respond MERGE_GATE_VERDICT: BLOCK (manual review required).
6. BLOCK if labels security/breaking-change/needs-manual-review/release, sensitive paths (.github/workflows, auth, pyproject.toml), secrets in diff, PR >800 lines or >30 files, or SDK changes without tests. Label `merge-gate-ci-only` exempts CI-only changes under `.github/workflows/`, `.github/actions/`, or merge-gate scripts.
7. Confirm no CHANGES_REQUESTED reviews and CI green on HEAD ${{ matrix.head_sha }}.
8. If ready to merge: MERGE_GATE_VERDICT: APPROVE. Otherwise: MERGE_GATE_VERDICT: BLOCK with reasons.
Do not approve or merge via GitHub UI/API — only post the verdict comment.
disallowed_tools: |
Bash
Edit
Write
Replace
allowed_tools: |
View
GlobTool
GrepTool
Read
timeout_minutes: 15
- name: Post fallback verdict if Claude did not comment
id: fallback_verdict
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const mergeGate = require('/tmp/merge-gate.js');
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = ${{ matrix.pr_number }};
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
const minCreatedAt = new Date(Date.now() - 25 * 60 * 1000).toISOString();
const existing = mergeGate.findMergeGateVerdict(ctx.comments, minCreatedAt, ctx.headPushedAt);
if (existing) {
core.info(`Verdict already posted: ${existing}`);
core.setOutput('verdict', existing);
return;
}
const check = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, { forMergeStep: true }
);
const verdict = check.ready ? 'APPROVE' : 'BLOCK';
const lines = [
`MERGE_GATE_VERDICT: ${verdict}`,
'',
'Automated fallback — Claude assess did not post a verdict comment (e.g. GitHub MCP unavailable).',
];
if (check.ready) {
lines.push('All deterministic merge gates passed.');
} else {
lines.push('Blockers:', ...check.reasons.map((r) => `- ${r}`));
}
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber,
body: lines.join('\n'),
});
core.info(`Posted fallback verdict: ${verdict}`);
core.setOutput('verdict', verdict);
- name: Verify verdict posted
id: verdict
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const mergeGate = require('/tmp/merge-gate.js');
const comments = await mergeGate.listAllComments(
github, context.repo.owner, context.repo.repo, ${{ matrix.pr_number }}
);
const ctx = await mergeGate.loadPrContext(
github, context.repo.owner, context.repo.repo, ${{ matrix.pr_number }}
);
const minCreatedAt = new Date(Date.now() - 25 * 60 * 1000).toISOString();
const verdict = mergeGate.findMergeGateVerdict(comments, minCreatedAt, ctx.headPushedAt);
core.setOutput('verdict', verdict || 'NONE');
if (!verdict) core.warning('No recent MERGE_GATE_VERDICT comment found after assessment');
- name: Remove merge gate active label
if: always()
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const prNumber = ${{ matrix.pr_number }};
try {
await github.rest.issues.removeLabel({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
name: 'claude-merge-gate-active',
});
core.info(`Removed claude-merge-gate-active from PR #${prNumber}`);
} catch (e) {
core.info(`Label claude-merge-gate-active already removed or missing on PR #${prNumber}`);
}
- name: Fail job if assessment failed
if: steps.assess.outcome == 'failure'
run: exit 1
merge-only:
needs: [scan-candidates, claude-assess]
if: |
always() &&
needs.scan-candidates.outputs.has_candidates == 'true' &&
needs.claude-assess.result == 'success'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.scan-candidates.outputs.matrix) }}
permissions:
contents: write
pull-requests: write
issues: write
actions: read
steps:
- uses: actions/checkout@v4
- name: Re-check gates and merge
uses: actions/github-script@v7
env:
MERGE_METHOD: ${{ github.event.inputs.merge_method || 'merge' }}
SCAN_HEAD_SHA: ${{ matrix.head_sha }}
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = ${{ matrix.pr_number }};
const scanHeadSha = process.env.SCAN_HEAD_SHA;
const removeLabels = async () => {
try {
await github.rest.issues.removeLabel({
owner, repo, issue_number: prNumber, name: 'claude-merge-gate-active',
});
} catch (e) {
core.info('Label claude-merge-gate-active already removed or missing');
}
};
try {
const ctx = await mergeGate.loadPrContext(github, owner, repo, prNumber);
const comments = ctx.comments;
const verdictMin = new Date(Date.now() - 25 * 60 * 1000).toISOString();
const verdict = mergeGate.findMergeGateVerdict(comments, verdictMin, ctx.headPushedAt);
if (verdict !== 'APPROVE') {
core.info(`Skip merge: verdict=${verdict || 'NONE'}`);
await removeLabels();
return;
}
const check = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, { forMergeStep: true }
);
if (!check.ready) {
core.info(`Skip merge: ${check.reasons.join(', ')}`);
await removeLabels();
return;
}
if (check.headSha !== scanHeadSha) {
core.info(`Skip merge: HEAD moved ${scanHeadSha.slice(0, 7)} → ${check.headSha.slice(0, 7)}`);
await removeLabels();
return;
}
const method = process.env.MERGE_METHOD || 'merge';
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
let merged = false;
for (let attempt = 1; attempt <= 3; attempt++) {
try {
await github.rest.pulls.merge({
owner, repo, pull_number: prNumber, merge_method: method,
});
merged = true;
break;
} catch (e) {
const msg = (e.message || '').toLowerCase();
if (attempt < 3 && msg.includes('base branch was modified')) {
core.info(`Merge attempt ${attempt} failed (base moved), retrying in 8s...`);
await sleep(8000);
const recheck = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, { forMergeStep: true }
);
if (!recheck.ready) {
core.info(`Skip merge retry: ${recheck.reasons.join(', ')}`);
await removeLabels();
return;
}
continue;
}
throw e;
}
}
if (!merged) {
core.info('Skip merge: exhausted retries');
await removeLabels();
return;
}
await github.rest.issues.addLabels({
owner, repo, issue_number: prNumber, labels: ['auto-merged-by-gate'],
});
await github.rest.issues.createComment({
owner, repo, issue_number: prNumber,
body: [
'Merged by **Claude PR merge gate** (`claude-merge-gate.yml`).',
`Verdict: MERGE_GATE_VERDICT: APPROVE`,
`SHA: \`${check.headSha.slice(0, 7)}\``,
`Method: ${method}`,
].join('\n'),
});
core.info(`Merged PR #${prNumber}`);
} finally {
await removeLabels();
}
-21
View File
@@ -1,21 +0,0 @@
name: Claude OAuth
on:
workflow_dispatch:
inputs:
code:
description: 'Authorization code (leave empty for step 1)'
required: false
permissions:
actions: write # Required for cache management
contents: read # Required for basic repository access
jobs:
auth:
runs-on: ubuntu-latest
steps:
- uses: grll/claude-code-login@v1
with:
code: ${{ inputs.code }}
secrets_admin_pat: ${{ secrets.SECRETS_ADMIN_PAT }}
-395
View File
@@ -1,395 +0,0 @@
name: Claude Assistant
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
issues:
types: [assigned, labeled, opened]
pull_request_review:
types: [submitted]
jobs:
# ================================================================
# Job 1: Lightweight triage for ALL new issues (including external users)
# Posts welcome comment + adds triage labels. No Claude code execution.
# ================================================================
issue-triage:
if: |
github.event_name == 'issues' &&
github.event.action == 'opened' &&
!contains(toJSON(github.event.issue.labels), 'claude')
runs-on: ubuntu-latest
permissions:
issues: write
contents: read
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Triage and acknowledge issue
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const issue = context.payload.issue;
const title = (issue.title || '').toLowerCase();
const body = (issue.body || '').toLowerCase();
const content = title + ' ' + body;
const isOwner = ['OWNER', 'MEMBER', 'COLLABORATOR'].includes(
issue.author_association
);
// --- Label triage ---
const labels = [];
// Kind labels
if (content.includes('feature request') || content.includes('[feature')) {
labels.push('enhancement');
} else if (content.includes('bug') || content.includes('error') || content.includes('crash') || content.includes('traceback')) {
labels.push('bug');
} else if (content.includes('question') || content.includes('how do i') || content.includes('how to')) {
labels.push('question');
}
// Area labels
if (content.includes('security') || content.includes('cve') || content.includes('vulnerability')) {
labels.push('security');
}
if (content.includes('performance') || content.includes('slow') || content.includes('memory leak')) {
labels.push('performance');
}
if (content.includes('documentation') || content.includes('docs')) {
labels.push('documentation');
}
if (content.includes('typescript') || content.includes('javascript') || content.includes('npm')) {
labels.push('javascript');
}
// Apply labels
if (labels.length > 0) {
await github.rest.issues.addLabels({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels
});
}
// --- Acknowledgment comment (external users only) ---
if (!isOwner) {
await github.rest.issues.createComment({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: [
`👋 Thanks for opening this issue, @${issue.user.login}!`,
'',
'A maintainer will review this shortly. In the meantime:',
'- Make sure you\'ve included steps to reproduce (for bugs)',
'- Check [existing issues](https://github.com/MervinPraison/PraisonAI/issues) for duplicates',
'- Review the [documentation](https://docs.praison.ai) for related guides',
'',
'_A maintainer can trigger deeper analysis by commenting `@claude` on this issue._'
].join('\n')
});
}
// --- For owner: add 'claude' label to trigger the code-fix job ---
if (isOwner) {
await github.rest.issues.addLabels({
issue_number: issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
labels: ['claude']
});
}
# ================================================================
# Job 2: Full Claude code-fix (owner/collaborator only)
# Triggers on: labeled 'claude', @claude comments, assignments
# ================================================================
claude-response:
# Allow: human users, github-actions[bot] (auto-comments/labels)
# Block: dependabot, cursor, renovate, other bots
# Skip 'opened' events — triage job handles those above
if: |
github.event.action != 'opened' &&
(github.event.action != 'labeled' || github.event.label.name == 'claude') &&
(github.event_name != 'issue_comment' || contains(github.event.comment.body, '@claude')) &&
(github.event_name != 'pull_request_review' || contains(github.event.review.body, '@claude')) &&
(github.event_name != 'pull_request_review_comment' || contains(github.event.comment.body, '@claude')) &&
(
!contains(github.actor, '[bot]') ||
github.actor == 'github-actions[bot]' ||
github.actor == 'praisonai-triage-agent[bot]'
) &&
github.actor != 'dependabot[bot]' &&
github.actor != 'cursor[bot]' &&
github.actor != 'renovate[bot]'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
issues: write
actions: read
id-token: write
steps:
- name: Generate GitHub App Token
id: app-token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CLAUDE_APP_ID }}
private-key: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
owner: ${{ github.repository_owner }}
- name: Check for Fork PR
id: check_fork
uses: actions/github-script@v7
with:
github-token: ${{ steps.app-token.outputs.token }}
script: |
const isPR = context.eventName === 'pull_request'
|| context.eventName === 'pull_request_review'
|| context.eventName === 'pull_request_review_comment'
|| (context.payload.issue && context.payload.issue.pull_request);
if (!isPR) {
core.setOutput('is_pr', 'false');
core.setOutput('is_fork', 'false');
core.setOutput('can_push_fork', 'false');
return;
}
const prNumber = context.payload.pull_request
? context.payload.pull_request.number
: context.payload.issue.number;
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: prNumber
});
let mergeState = '';
try {
const gql = await github.graphql(
`query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) { mergeStateStatus }
}
}`,
{ owner: context.repo.owner, repo: context.repo.repo, number: prNumber }
);
mergeState = (gql.repository.pullRequest.mergeStateStatus || '').toUpperCase();
} catch (e) {
core.warning(`GraphQL mergeStateStatus failed: ${e.message}`);
}
if (!mergeState) {
mergeState = (pr.data.mergeable_state || pr.data.merge_state_status || '').toUpperCase();
}
core.setOutput('is_pr', 'true');
core.setOutput('pr_number', String(prNumber));
core.setOutput('pr_branch', pr.data.head.ref);
core.setOutput('head_repo', pr.data.head.repo.full_name);
core.setOutput('merge_state', mergeState);
const baseRepo = `${context.repo.owner}/${context.repo.repo}`;
const maintainerCanModify = pr.data.maintainer_can_modify === true;
core.setOutput('maintainer_can_modify', String(maintainerCanModify));
if (pr.data.head.repo && pr.data.head.repo.full_name !== baseRepo) {
core.setOutput('is_fork', 'true');
core.setOutput('can_push_fork', String(maintainerCanModify));
core.setOutput('fork_repo', pr.data.head.repo.full_name);
core.setOutput('clone_url', pr.data.head.repo.clone_url);
core.setOutput('branch', pr.data.head.ref);
return;
}
core.setOutput('is_fork', 'false');
core.setOutput('can_push_fork', 'false');
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
token: ${{ steps.app-token.outputs.token }}
- name: Fetch PR branch and Setup Remote
if: steps.check_fork.outputs.is_pr == 'true'
env:
PR_BRANCH: ${{ steps.check_fork.outputs.pr_branch }}
PR_NUMBER: ${{ steps.check_fork.outputs.pr_number }}
IS_FORK: ${{ steps.check_fork.outputs.is_fork }}
CAN_PUSH_FORK: ${{ steps.check_fork.outputs.can_push_fork }}
FORK_REPO: ${{ steps.check_fork.outputs.fork_repo }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
run: |
git fetch origin "pull/${PR_NUMBER}/head:${PR_BRANCH}"
git checkout "${PR_BRANCH}"
git fetch origin main
if [ "${IS_FORK}" = "true" ] && [ "${CAN_PUSH_FORK}" = "true" ]; then
git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${FORK_REPO}.git"
elif [ "${IS_FORK}" = "true" ]; then
# Block push: trick claude-code-action's `git fetch origin <branch>`
git remote set-url origin "file://$(pwd)"
fi
- uses: anthropics/claude-code-action@beta
env:
GH_TOKEN: ${{ steps.check_fork.outputs.can_push_fork == 'true' && secrets.GH_TOKEN || steps.app-token.outputs.token }}
with:
allowed_bots: 'praisonai-triage-agent[bot]'
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
github_token: ${{ steps.app-token.outputs.token }}
model: claude-opus-4-8
trigger_phrase: "@claude"
label_trigger: "claude"
direct_prompt: |
${{ steps.check_fork.outputs.is_fork == 'true' && steps.check_fork.outputs.can_push_fork != 'true' && 'CRITICAL: THIS IS A PULL REQUEST FROM A FORK WITHOUT MAINTAINER EDITS. YOU DO NOT HAVE PUSH PERMISSIONS. ONLY READ FILES, VALIDATE CODE, AND PROVIDE COMMENTS/FEEDBACK ON THIS PR. DO NOT ATTEMPT TO PUSH OR CREATE PULL REQUESTS.\n\n' || (steps.check_fork.outputs.can_push_fork == 'true' && format('NOTE: Fork PR with maintainer edits allowed. Push directly to fork {0} branch {1}.\n\n', steps.check_fork.outputs.fork_repo, steps.check_fork.outputs.pr_branch) || (steps.check_fork.outputs.is_pr == 'true' && steps.check_fork.outputs.is_fork != 'true' && 'NOTE: The branch is under MervinPraison/PraisonAI (not a fork). You are able to make modifications and push directly to this branch.\n\n' || (steps.check_fork.outputs.is_pr != 'true' && 'NOTE: This is an issue (not a PR). Create a new fix branch per STEP 3 below.\n\n' || ''))) }}
${{ steps.check_fork.outputs.is_pr == 'true' && format('CONTEXT: PR #{0} on branch {1}. head.repo={2}, is_fork={3}, can_push_fork={4}, merge_state={5}. NEVER treat cursor/*, claude/* branch names or bot authors as forks — only is_fork=true means fork.\n\n', steps.check_fork.outputs.pr_number, steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.head_repo, steps.check_fork.outputs.is_fork, steps.check_fork.outputs.can_push_fork, steps.check_fork.outputs.merge_state) || '' }}
You are working on the PraisonAI SDK. Follow AGENTS.md strictly.
STEP 0 — SETUP GIT IDENTITY & AUTH (GLOBAL — required for all repos):
git config --global user.name "MervinPraison"
git config --global user.email "[email protected]"
gh auth setup-git
STEP 1 — READ GUIDELINES:
Read AGENTS.md to understand the architecture rules.
SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify or touch praisonai-rust or praisonai-ts packages.
STEP 2 — ARCHITECTURE VALIDATION & ROUTING (MANDATORY before writing code):
Before implementing anything, answer these questions:
- CORE vs WRAPPER vs TOOLS vs DOCS vs PLUGINS ROUTING:
1. Core SDK (praisonaiagents/): Only core protocols, base classes, decorators. No heavy implementations.
2. Wrapper (praisonai/): CLI, heavy implementations, optional dependencies.
3. Tools (PraisonAI-Tools): Capabilities Agents actively call during execution (e.g. SurrealDB tool, Slack tool).
4. Documentation (PraisonAIDocs): Documentation pages, guides, API references.
5. Plugins (PraisonAI-Plugins): Framework lifecycle extensions replacing systemic behavior (e.g. tracing, logging, metrics, hooks, guardrails).
For items routed to an EXTERNAL repository (Tools, Docs, Plugins), follow STEP 3-ALT below.
- Does it duplicate existing functionality? Check if Agent already supports this via existing params (reflection, planning, tools, hooks, memory).
- Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.
- Does it inherit from Agent properly? New agent types MUST inherit Agent, not wrap it with composition.
- Does it add new dependencies? Only optional deps allowed, must be lazy-imported.
- Will agent.py grow larger? If the change adds >50 lines to agent.py, find a way to extract instead. PRs with >100 lines added to agent.py require manual review (merge gate will not auto-merge).
- Is there a name collision with existing exports in __init__.py?
If ANY of these conceptual checks fail (excluding routing), add a comment to the issue explaining why and close it. Do NOT create a PR.
${{ steps.check_fork.outputs.is_pr == 'true' && (steps.check_fork.outputs.is_fork != 'true' || steps.check_fork.outputs.can_push_fork == 'true') && steps.check_fork.outputs.merge_state == 'DIRTY' && format('STEP 3 — RESOLVE MERGE CONFLICTS ON PR (branch: {0}, PR #{1}{2}):\n- DO NOT create a new branch or PR\n- git fetch origin main && git rebase origin/main\n- Resolve conflicts: keep this PR intent, merge in main newer logic\n- git add -A && git rebase --continue (repeat until done)\n- git push --force-with-lease origin {0}\n- Run targeted pytest on changed paths\n- Comment files resolved and what you skipped\n', steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.pr_number, steps.check_fork.outputs.can_push_fork == 'true' && format(', fork: {0}', steps.check_fork.outputs.fork_repo) || '') || '' }}
${{ steps.check_fork.outputs.is_pr == 'true' && (steps.check_fork.outputs.is_fork != 'true' || steps.check_fork.outputs.can_push_fork == 'true') && steps.check_fork.outputs.merge_state != 'DIRTY' && format('STEP 3 — IMPLEMENT ON EXISTING PR (branch: {0}, PR #{1}{2}):\n- DO NOT create a new branch or PR\n- git checkout {0}\n- Implement minimal, focused fixes from reviewer feedback\n- Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper\n- Keep changes small and backward-compatible\n- Commit: git add -A && git commit -m "fix: <description>"\n- Push: git push origin {0}\n- Comment a summary of exact files modified and what you skipped\n', steps.check_fork.outputs.pr_branch, steps.check_fork.outputs.pr_number, steps.check_fork.outputs.can_push_fork == 'true' && format(', fork: {0}', steps.check_fork.outputs.fork_repo) || '') || '' }}
${{ steps.check_fork.outputs.is_pr != 'true' && 'STEP 3 — IMPLEMENT (for changes in THIS repo — PraisonAI):\n- Create a fix branch: git checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)\n- Implement a minimal, focused fix\n- Follow protocol-driven design: protocols in core SDK, heavy implementations in wrapper\n- Keep changes small and backward-compatible\n- Commit: git add -A && git commit -m "fix: <description> (fixes #$ISSUE_NUMBER)"\n- Push: git push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)\nCRITICAL: After pushing, you MUST immediately run: gh pr create --title "fix: <title>" --body "Fixes #$ISSUE_NUMBER" --head claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)\nDo NOT stop after pushing. Do NOT say "manually create a PR". Always create the PR automatically.\n' || '' }}
STEP 3-ALT — IMPLEMENT IN EXTERNAL REPO (for PraisonAI-Tools, PraisonAIDocs, PraisonAI-Plugins):
When work must happen in a different repository, follow these steps EXACTLY. Do NOT attempt to use `cd`, as directory state is not preserved. Use `git -C` for all commands.
a) Clone the repository:
gh repo clone MervinPraison/<REPO_NAME> /tmp/<REPO_NAME>
b) Copy GitHub authentication from the main repository so push works seamlessly:
git -C /tmp/<REPO_NAME> config http."https://github.com/".extraheader "$(git config --get http."https://github.com/".extraheader)"
c) Create a feature branch (NEVER commit to main):
git -C /tmp/<REPO_NAME> checkout -b claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
d) Make your changes using absolute paths (e.g. edit /tmp/<REPO_NAME>/docs.json)
e) Commit:
git -C /tmp/<REPO_NAME> add -A
git -C /tmp/<REPO_NAME> commit -m "feat: <description> (fixes #$ISSUE_NUMBER)"
f) Push the branch:
git -C /tmp/<REPO_NAME> push origin claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d)
g) Create PR in the EXTERNAL repo:
gh pr create -R MervinPraison/<REPO_NAME> --head claude/issue-$ISSUE_NUMBER-$(date +%Y%m%d) --title "feat: <title>" --body "Fixes MervinPraison/PraisonAI#$ISSUE_NUMBER
<body>"
STEP 4 — TEST:
- For SDK changes: cd src/praisonai-agents && PYTHONPATH=. python -m pytest tests/ -x -q --timeout=30
- For docs changes: verify files exist and are valid markdown/MDX
- Ensure no regressions
${{ steps.check_fork.outputs.is_pr == 'true' && 'STEP 5 — SKIP: Already on an open PR. Do NOT run gh pr create.\n' || 'STEP 5 — CREATE PR:\n- Commit with descriptive message, push, and create PR using `gh pr create`\n- For external repos, use `gh pr create -R MervinPraison/<REPO_NAME>`\nCRITICAL: You MUST create the PR automatically using `gh pr create`. Do NOT just provide a link or say "manual push required".\n' }}
NOTE: If you worked on an external repo, the wrapper action will report "No commits" for the main repo at the end. This is expected, do not worry about it.
allowed_tools: |
Bash(git:*)
Bash(python:*)
Bash(pip:*)
Bash(conda:*)
Bash(pytest:*)
Bash(gh:*)
Bash(python -m pytest:*)
Bash(python -m pip:*)
Bash(poetry:*)
View
GlobTool
GrepTool
BatchTool
Edit
Replace
mcp__github__get_issue
mcp__github__get_issue_comments
mcp__github__update_issue
timeout_minutes: 30
- name: Kick review chain for bot-created PR
if: |
steps.check_fork.outputs.is_pr != 'true' &&
(github.event_name == 'issues' || github.event_name == 'issue_comment') &&
github.event.issue != null
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const chain = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/bot-pr-review-chain.js'));
const issueNumber = context.payload.issue.number;
await chain.kickReviewChainForIssue(
github, context.repo.owner, context.repo.repo, issueNumber, core
);
- name: Sync pipeline status labels for PR
if: steps.check_fork.outputs.is_pr == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
const prNumber = parseInt('${{ steps.check_fork.outputs.pr_number }}', 10);
await ps.ensurePipelineLabels(github, context.repo.owner, context.repo.repo, core);
await ps.syncPipelineLabels(github, context.repo.owner, context.repo.repo, prNumber, core);
- name: Dispatch merge gate when PR pipeline ready
if: steps.check_fork.outputs.is_pr == 'true'
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const mergeGate = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/merge-gate.js'));
const owner = context.repo.owner;
const repo = context.repo.repo;
const prNumber = parseInt('${{ steps.check_fork.outputs.pr_number }}', 10);
const check = await mergeGate.evaluatePipelineQuiescent(
github, owner, repo, prNumber, core, {
skipGlobalClaudeRunCheck: true,
skipRecentClaudeCooldown: true,
}
);
if (!check.ready) {
core.info(`PR #${prNumber} not ready for merge gate: ${check.reasons.join(', ')}`);
return;
}
await github.rest.repos.createDispatchEvent({
owner,
repo,
event_type: 'claude-merge-gate',
client_payload: { pr_number: prNumber },
});
core.info(`Dispatched merge gate for PR #${prNumber}`);
@@ -1,52 +0,0 @@
name: Deploy install scripts to praison.ai
# When install.sh or install.ps1 change on main, trigger praison.ai CI which
# syncs scripts at build time and deploys to Azure AKS (existing azure-pipeline.yaml).
on:
push:
branches: [main]
paths:
- 'src/praisonai/scripts/install.sh'
- 'src/praisonai/scripts/install.ps1'
workflow_dispatch:
concurrency:
group: deploy-install-scripts
cancel-in-progress: false
permissions:
contents: read
jobs:
trigger-website-deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger praison.ai CI
env:
GH_TOKEN: ${{ secrets.SECRETS_ADMIN_PAT }}
SOURCE_SHA: ${{ github.sha }}
run: |
set -euo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
echo "SECRETS_ADMIN_PAT is not configured in this repository"
exit 1
fi
gh workflow run CI \
--repo MervinPraison/praison.ai \
--ref master
echo "Triggered praison.ai CI for PraisonAI commit ${SOURCE_SHA}"
echo "Monitor: https://github.com/MervinPraison/praison.ai/actions/workflows/azure-pipeline.yaml"
sleep 10
RUN_URL=$(gh run list \
--repo MervinPraison/praison.ai \
--workflow CI \
--limit 1 \
--json url,status,createdAt \
--jq '.[0].url' 2>/dev/null || true)
if [ -n "${RUN_URL}" ]; then
echo "Latest praison.ai CI run: ${RUN_URL}"
fi
-56
View File
@@ -1,56 +0,0 @@
name: Publish Docker
on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
description: 'Docker image tag (default: latest)'
required: false
default: 'latest'
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/mervinpraison/praisonai
tags: |
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
type=semver,pattern={{version}},enable=${{ github.event_name == 'release' }}
type=semver,pattern={{major}}.{{minor}},enable=${{ github.event_name == 'release' }}
type=raw,value=${{ github.event.inputs.tag || 'latest' }},enable=${{ github.event_name == 'workflow_dispatch' }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
file: ./docker/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
@@ -1,79 +0,0 @@
name: Gemini Automated Issue Triage
# on:
# issue_comment:
# types: [created]
on:
workflow_dispatch:
jobs:
triage-issue:
if: >
github.event_name == 'issue_comment' &&
!github.event.issue.pull_request &&
contains(github.event.comment.body, '@gemini triage') &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR')
timeout-minutes: 5
permissions:
issues: write
contents: read
id-token: write
concurrency:
group: ${{ github.workflow }}-${{ github.event.issue.number }}
cancel-in-progress: true
runs-on: ubuntu-latest
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
token: ${{ steps.generate_token.outputs.token }}
- name: Run Gemini Issue Triage
uses: google-gemini/gemini-cli-action@main
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
REPOSITORY: ${{ github.repository }}
with:
version: 0.1.8-rc.0
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
OTLP_GCP_WIF_PROVIDER: ${{ secrets.OTLP_GCP_WIF_PROVIDER }}
OTLP_GOOGLE_CLOUD_PROJECT: ${{ secrets.OTLP_GOOGLE_CLOUD_PROJECT }}
settings_json: |
{
"coreTools": [
"run_shell_command(gh label list)",
"run_shell_command(gh issue edit)"
],
"telemetry": {
"enabled": true,
"target": "gcp"
},
"sandbox": false
}
prompt: |
You are an issue triage assistant. Analyze the current GitHub issue and apply the most appropriate existing labels.
Steps:
1. Run: `gh label list --limit 100` to get all available labels.
2. Review the issue title and body provided in the environment variables.
3. Select the most relevant labels from the existing labels, focusing on kind/*, area/*, and priority/*.
4. Apply the selected labels to this issue using: `gh issue edit ISSUE_NUMBER --add-label "label1,label2"`
Guidelines:
- Only use labels that already exist in the repository.
- Do not add comments or modify the issue content.
- Triage only the current issue.
- Assign all applicable kind/*, area/*, and priority/* labels based on the issue content.
-201
View File
@@ -1,201 +0,0 @@
name: Gemini Issue Review
on:
workflow_dispatch:
# issue_comment:
# types: [created]
permissions:
contents: write
issues: write
pull-requests: read
jobs:
review-issue:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
!github.event.issue.pull_request &&
contains(github.event.comment.body, '@gemini') &&
!contains(github.event.comment.body, '@gemini triage') &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR' ||
github.event.comment.author_association == 'CONTRIBUTOR'))
runs-on: ubuntu-latest
env:
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
steps:
- name: Generate token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- name: Checkout repository
uses: actions/checkout@v4
with:
persist-credentials: false
token: ${{ steps.generate_token.outputs.token }}
- name: Set up Git
run: |
git config --local user.email "[email protected]"
git config --local user.name "MervinPraison"
- name: Get issue data
id: issue_data
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
# Pass user-controlled inputs via environment variables to prevent script injection (GHSL-2025-093)
EVENT_NAME: ${{ github.event_name }}
EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
EVENT_COMMENT_BODY: ${{ github.event.comment.body }}
run: |
# Get issue number from event or context
if [ "$EVENT_NAME" = "issue_comment" ]; then
ISSUE_NUMBER="$EVENT_ISSUE_NUMBER"
elif [ "$EVENT_NAME" = "issues" ]; then
ISSUE_NUMBER="$EVENT_ISSUE_NUMBER"
else
echo "Unable to determine issue number"
exit 1
fi
# Get issue details
gh issue view $ISSUE_NUMBER --json title,body,labels,assignees > issue_details.json
# Extract additional instructions from comment if triggered by comment
ADDITIONAL_INSTRUCTIONS=""
if [ "$EVENT_NAME" = "issue_comment" ]; then
COMMENT_BODY="$EVENT_COMMENT_BODY"
ADDITIONAL_INSTRUCTIONS=$(echo "$COMMENT_BODY" | sed -n 's/.*@gemini[[:space:]]*\(.*\)/\1/p' | head -1)
fi
echo "ISSUE_NUMBER=$ISSUE_NUMBER" >> $GITHUB_OUTPUT
echo "ADDITIONAL_INSTRUCTIONS=$ADDITIONAL_INSTRUCTIONS" >> $GITHUB_OUTPUT
- name: Run Gemini CLI
uses: google-github-actions/run-gemini-cli@v0
env:
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
ISSUE_NUMBER: ${{ steps.issue_data.outputs.ISSUE_NUMBER }}
ADDITIONAL_INSTRUCTIONS: ${{ steps.issue_data.outputs.ADDITIONAL_INSTRUCTIONS }}
REPOSITORY: ${{ github.repository }}
with:
gemini_api_key: ${{ secrets.GEMINI_API_KEY }}
gemini_model: 'gemini-2.5-flash'
prompt: |
You are a helpful GitHub issue assistant. Your task is to analyze and help with GitHub issues.
**Issue Details:**
- Issue Number: #${ISSUE_NUMBER}
- Repository: ${REPOSITORY}
- Issue URL: ${{ github.event.issue.html_url }}
**Additional Instructions:** ${ADDITIONAL_INSTRUCTIONS}
**CRITICAL: When making code modifications, follow this EXACT workflow:**
**STEP 1: Analyze the Issue**
- Use: `gh issue view ${ISSUE_NUMBER} --json title,body,labels,assignees`
- Analyze issue content thoroughly
- Search codebase for related files using `glob`, `search_file_content`, `list_directory`
**STEP 2: If Code Changes Are Needed**
MANDATORY FIRST STEP: Before writing any code, you MUST use `read_file` to carefully read "src/praisonai-agents/AGENTS.md". You must honor this repository's extremely specific Architecture, Protocol-First design rules, and Agent-Centric Philosophy!
Create a new branch with 'gemini/' prefix:
- Use: `git checkout -b gemini/issue-${ISSUE_NUMBER}-description`
- Example: `git checkout -b gemini/issue-123-fix-sequential-tools`
**STEP 3: Make Code Modifications**
- Use `read_file` to examine existing code
- Use `write_file` or `replace` to make changes
- Use `search_file_content` to find related code
- Make focused, targeted changes to fix the issue
**STEP 4: Test Changes (if applicable)**
- Run relevant tests if test files exist
- Use `run_shell_command` to execute test commands
**STEP 5: Commit and Push Changes**
- Use: `git add .` (or specific files)
- Use: `git commit -m "Gemini: [Issue #${ISSUE_NUMBER}] Brief description of fix"`
- Use: `git push -u origin gemini/issue-${ISSUE_NUMBER}-description`
**STEP 6: Create Pull Request**
- Use: `gh pr create --title "Fix #${ISSUE_NUMBER}: Issue description" --body "Fixes #${ISSUE_NUMBER}\n\nSummary of changes:\n- Change 1\n- Change 2" --head gemini/issue-${ISSUE_NUMBER}-description --base main`
- This will return a PR URL like: https://github.com/repo/pull/123
**STEP 7: Comment on Issue with Results**
Use: `gh issue comment ${ISSUE_NUMBER} -b "MESSAGE"`
**Your final comment MUST include:**
- Summary of what was analyzed
- What changes were made (if any)
- Link to the created PR (if applicable)
- Brief explanation of the solution
**Example final comment:**
```
Hi! I've analyzed and fixed this issue.
**Problem:** Sequential tool calling was getting stuck after first tool call.
**Solution:** Added proper prompt handling after each tool call in the Ollama model integration.
**Changes Made:**
- Modified `src/praisonai-agents/ollama-sequential.py`
- Added continuation prompts for multi-step tool execution
**Pull Request:** https://github.com/MervinPraison/PraisonAI/pull/856
The fix is ready for review. Please test and let me know if you encounter any issues!
```
**IMPORTANT NOTES:**
- Always create branches with 'gemini/' prefix
- Always include issue number in branch name and commit message
- Always create PR that references the issue
- Always provide PR link in your final response
- Use the GITHUB_TOKEN environment variable for authentication
Please proceed with helping resolve this issue following this exact workflow.
settings: |
{
"coreTools": [
"run_shell_command(echo)",
"run_shell_command(gh issue view)",
"run_shell_command(gh issue comment)",
"run_shell_command(gh issue list)",
"run_shell_command(gh issue edit)",
"run_shell_command(gh pr create)",
"run_shell_command(gh pr view)",
"run_shell_command(gh pr list)",
"run_shell_command(cat)",
"run_shell_command(head)",
"run_shell_command(tail)",
"run_shell_command(grep)",
"run_shell_command(find)",
"run_shell_command(git checkout)",
"run_shell_command(git add)",
"run_shell_command(git commit)",
"run_shell_command(git push)",
"run_shell_command(git status)",
"run_shell_command(git branch)",
"list_directory",
"read_file",
"read_many_files",
"write_file",
"replace",
"glob",
"search_file_content",
"web_fetch",
"google_web_search",
"save_memory"
]
}
-251
View File
@@ -1,251 +0,0 @@
name: Gemini PR Review
on:
# issue_comment:
# types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'PR number to review'
required: true
type: number
jobs:
review-pr:
if: >
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@gemini') &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'))
timeout-minutes: 15
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
pull-requests: write
issues: write
steps:
- name: Generate GitHub App Token
id: generate_token
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.PRIVATE_KEY }}
- name: Determine checkout ref
id: checkout_ref
run: |
if [ "${{ github.event_name }}" = "issue_comment" ]; then
echo "ref=refs/pull/${{ github.event.issue.number }}/head" >> "$GITHUB_OUTPUT"
else
echo "ref=${{ github.event.pull_request.head.sha }}" >> "$GITHUB_OUTPUT"
fi
- name: Checkout PR code
uses: actions/checkout@v4
with:
persist-credentials: false
token: ${{ steps.generate_token.outputs.token }}
ref: ${{ steps.checkout_ref.outputs.ref }}
fetch-depth: 0
- name: Get PR details
id: get_pr
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
# Pass user-controlled inputs via environment variables to prevent script injection (GHSL-2025-093)
EVENT_NAME: ${{ github.event_name }}
EVENT_INPUTS_PR_NUMBER: ${{ github.event.inputs.pr_number }}
EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
EVENT_PR_NUMBER: ${{ github.event.pull_request.number }}
EVENT_COMMENT_BODY: ${{ github.event.comment.body }}
run: |
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
PR_NUMBER=$EVENT_INPUTS_PR_NUMBER
elif [ "$EVENT_NAME" = "issue_comment" ]; then
PR_NUMBER=$EVENT_ISSUE_NUMBER
else
PR_NUMBER=$EVENT_PR_NUMBER
fi
echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"
# Extract additional instructions from comment (if triggered by comment)
ADDITIONAL_INSTRUCTIONS=""
if [ "$EVENT_NAME" = "issue_comment" ]; then
COMMENT_BODY="$EVENT_COMMENT_BODY"
ADDITIONAL_INSTRUCTIONS=$(echo "$COMMENT_BODY" | sed 's/.*@gemini//' | xargs)
fi
echo "additional_instructions=$ADDITIONAL_INSTRUCTIONS" >> "$GITHUB_OUTPUT"
# Get PR details
PR_DATA=$(gh pr view $PR_NUMBER --json title,body,additions,deletions,changedFiles,baseRefName,headRefName)
echo "pr_data=$PR_DATA" >> "$GITHUB_OUTPUT"
# Get file changes
CHANGED_FILES=$(gh pr diff $PR_NUMBER --name-only)
echo "changed_files<<EOF" >> "$GITHUB_OUTPUT"
echo "$CHANGED_FILES" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
- name: Run Gemini PR Review
uses: ./.github/actions/gemini
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }}
PR_DATA: ${{ steps.get_pr.outputs.pr_data }}
CHANGED_FILES: ${{ steps.get_pr.outputs.changed_files }}
ADDITIONAL_INSTRUCTIONS: ${{ steps.get_pr.outputs.additional_instructions }}
REPOSITORY: ${{ github.repository }}
with:
version: 0.1.8-rc.0
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
OTLP_GCP_WIF_PROVIDER: ${{ secrets.OTLP_GCP_WIF_PROVIDER }}
OTLP_GOOGLE_CLOUD_PROJECT: ${{ secrets.OTLP_GOOGLE_CLOUD_PROJECT }}
settings_json: |
{
"coreTools": [
"run_shell_command(echo)",
"run_shell_command(gh pr view)",
"run_shell_command(gh pr diff)",
"run_shell_command(gh pr comment)",
"run_shell_command(gh pr list)",
"run_shell_command(cat)",
"run_shell_command(head)",
"run_shell_command(tail)",
"run_shell_command(grep)",
"run_shell_command(find)",
"run_shell_command(git checkout)",
"run_shell_command(git add)",
"run_shell_command(git commit)",
"run_shell_command(git push)",
"run_shell_command(git status)",
"run_shell_command(git branch)",
"run_shell_command(git fetch)",
"list_directory",
"read_file",
"read_many_files",
"write_file",
"replace",
"glob",
"search_file_content",
"web_fetch",
"google_web_search",
"save_memory"
],
"telemetry": {
"enabled": false,
"target": "gcp"
},
"sandbox": false
}
prompt: |
You are an expert code reviewer and AI assistant for Pull Requests. You can review code AND make modifications to improve the PR.
**PR Details:**
- PR Number: #$PR_NUMBER
- Repository: $REPOSITORY
- Additional Instructions: $ADDITIONAL_INSTRUCTIONS
**CRITICAL: When making code modifications, follow this EXACT workflow:**
**STEP 1: Analyze the PR**
- Use: `echo "$PR_DATA"` to get PR details (JSON format)
- Use: `echo "$CHANGED_FILES"` to get changed files list
- Use: `gh pr diff ${PR_NUMBER}` to see the full diff
- Use: `gh pr view ${PR_NUMBER} --json title,body,headRefName` to get PR info
- Analyze the code changes thoroughly
**STEP 2: If Code Improvements Are Needed**
MANDATORY FIRST STEP: Before writing any code, you MUST use `read_file` to carefully read "src/praisonai-agents/AGENTS.md". You must honor this repository's extremely specific Architecture, Protocol-First design rules, and Agent-Centric Philosophy!
**IMPORTANT:** DO NOT create a new branch! Work on the EXISTING PR branch.
- Use: `git fetch origin` to get latest changes
- Get PR branch name: `gh pr view ${PR_NUMBER} --json headRefName`
- Switch to PR branch: `git checkout BRANCH_NAME` (use actual branch name from PR)
- Use: `git pull origin BRANCH_NAME` to get latest PR changes
**STEP 3: Make Code Modifications**
- Use `read_file` to examine existing code
- Use `write_file` or `replace` to make improvements
- Use `search_file_content` to find related code
- Make focused, targeted improvements to the PR
**STEP 4: Test Changes (if applicable)**
- Run relevant tests if test files exist
- Use `run_shell_command` to execute test commands
**STEP 5: Commit and Push Changes to EXISTING PR Branch**
- Use: `git add .` (or specific files)
- Use: `git commit -m "Gemini: [PR #${PR_NUMBER}] Brief description of improvements"`
- Use: `git push origin BRANCH_NAME` (push to EXISTING PR branch)
**STEP 6: Review and Comment on PR**
Write comprehensive review and post comment with your analysis and any improvements made.
Use: `gh pr comment ${PR_NUMBER} -b "MESSAGE"`
Review Guidelines:
- Focus on code quality, security, performance, and maintainability
- Check for common issues: potential bugs, security vulnerabilities, performance bottlenecks
- Verify error handling and edge cases
- Look for code style and best practices
- Comment on architecture and design decisions if significant
- Be constructive and specific in feedback
- Highlight both issues and positive aspects
- Suggest improvements with examples when possible
Review Areas:
- **Security**: Authentication, authorization, input validation, data sanitization
- **Performance**: Algorithms, database queries, caching, resource usage
- **Reliability**: Error handling, logging, testing coverage, edge cases
- **Maintainability**: Code structure, documentation, naming conventions
- **Functionality**: Logic correctness, requirements fulfillment
Output Format:
Structure your review using this exact format with markdown:
## 📋 Review Summary
Provide a brief 2-3 sentence overview of the PR and overall assessment.
## 🔍 General Feedback
- List general observations about code quality
- Mention overall patterns or architectural decisions
- Highlight positive aspects of the implementation
- Note any recurring themes across files
## 🎯 Specific Feedback
Only include sections below that have actual issues. If there are no issues in a priority category, omit that entire section.
### 🔴 Critical
(Only include this section if there are critical issues)
Issues that must be addressed before merging (security vulnerabilities, breaking changes, major bugs):
- **File: `filename:line`** - Description of critical issue with specific recommendation
### 🟡 High
(Only include this section if there are high priority issues)
Important issues that should be addressed (performance problems, design flaws, significant bugs):
- **File: `filename:line`** - Description of high priority issue with suggested fix
### 🟢 Medium
(Only include this section if there are medium priority issues)
Improvements that would enhance code quality (style issues, minor optimizations, better practices):
- **File: `filename:line`** - Description of medium priority improvement
### 🔵 Low
(Only include this section if there are suggestions)
Nice-to-have improvements and suggestions (documentation, naming, minor refactoring):
- **File: `filename:line`** - Description of suggestion or enhancement
**Note**: If no specific issues are found in any category, simply state "No specific issues identified in this review."
## ✅ Highlights
(Only include this section if there are positive aspects to highlight)
- Mention specific good practices or implementations
- Acknowledge well-written code sections
- Note improvements from previous versions
---
*Review completed by Gemini CLI*
@@ -1,158 +0,0 @@
name: Cross-Repo Integration Tests
on:
push:
branches: [ main ]
paths:
- 'src/praisonai/praisonai/integration/**'
- 'src/praisonai/tests/integration/test_aiui_*'
- 'src/praisonai/pyproject.toml'
pull_request:
branches: [ main ]
paths:
- 'src/praisonai/praisonai/integration/**'
- 'src/praisonai/tests/integration/test_aiui_*'
- 'src/praisonai/pyproject.toml'
workflow_dispatch:
jobs:
cross-repo-integration:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout PraisonAI
uses: actions/checkout@v4
with:
path: PraisonAI
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout PraisonAIUI
uses: actions/checkout@v4
with:
repository: MervinPraison/PraisonAIUI
path: PraisonAIUI
ref: main
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
# Install PraisonAI packages in development mode
cd PraisonAI/src/praisonai-agents
pip install -e .
cd ../praisonai
pip install -e ".[ui,dev]"
# Install PraisonAIUI in development mode
cd ../../../PraisonAIUI
pip install -e .
- name: Verify versions
run: |
python -c "import praisonaiagents; print(f'praisonaiagents: {praisonaiagents.__version__}')"
python -c "import praisonai; print(f'praisonai: {praisonai.__version__}')"
python -c "import praisonaiui; print(f'praisonaiui: {praisonaiui.__version__}')"
- name: Run PraisonAI integration tests
run: |
cd PraisonAI/src/praisonai
python -m pytest tests/integration/test_aiui_* -v
- name: Run PraisonAIUI integration tests
run: |
cd PraisonAIUI
if [ -f tests/integration/test_agentic_roundtrip.py ]; then
python -m pytest tests/integration/test_agentic_roundtrip.py -v
fi
if [ -f tests/test_feature_sdk_backends.py ]; then
python -m pytest tests/test_feature_sdk_backends.py -v
fi
- name: Test Pattern C CLI
run: |
cd PraisonAI/src/praisonai
# Test that ui-gateway command exists
python -m praisonai serve --help | grep ui-gateway
- name: Test public API exports
run: |
cd PraisonAI/src/praisonai
python -c "from praisonai import run_integrated_gateway; print('✓ run_integrated_gateway imported')"
python -c "from praisonai import configure_host; print('✓ configure_host imported')"
- name: Integration smoke test
env:
PRAISONAI_TEST_MODE: "1"
PYTHONPATH: ${{ github.workspace }}/PraisonAI/src/praisonai:${{ github.workspace }}/PraisonAI/src/praisonai-agents:${{ github.workspace }}/PraisonAIUI
run: |
cd PraisonAI/src/praisonai
python -c "
import sys
sys.path.insert(0, '.')
from praisonai.integration.host_app import configure_host
from praisonai import run_integrated_gateway
print('✓ Integration imports successful')
# Test configure_host with new parameters
try:
configure_host(
style='dashboard',
context_paths=['AGENTS.md'],
title='Test App'
)
print('✓ configure_host with new parameters works')
except Exception as e:
print(f'× configure_host failed: {e}')
sys.exit(1)
"
# Optional job with API key for agentic tests (if secret is available)
agentic-integration:
runs-on: ubuntu-latest
timeout-minutes: 30
if: ${{ vars.RUN_AGENTIC_TESTS == 'true' }}
needs: cross-repo-integration
steps:
- name: Checkout PraisonAI
uses: actions/checkout@v4
with:
path: PraisonAI
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
- name: Checkout PraisonAIUI
uses: actions/checkout@v4
with:
repository: MervinPraison/PraisonAIUI
path: PraisonAIUI
ref: main
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
cd PraisonAI/src/praisonai-agents
pip install -e .
cd ../praisonai
pip install -e ".[ui,dev]"
cd ../../../PraisonAIUI
pip install -e .
- name: Run agentic integration test
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
cd PraisonAI/src/praisonai
if [ -n "$OPENAI_API_KEY" ]; then
python -m pytest tests/integration/test_aiui_host_agentic.py -v
else
echo "OPENAI_API_KEY not available, skipping agentic tests"
fi
-184
View File
@@ -1,184 +0,0 @@
name: Auto Merge Conflict → Claude
# Detects internal PRs with mergeStateStatus=DIRTY and posts @claude to trigger claude.yml.
on:
push:
branches: [main]
pull_request:
types: [opened, synchronize, reopened]
workflow_dispatch:
schedule:
- cron: '0 */6 * * *'
concurrency:
group: merge-conflict-scan-${{ github.repository }}
cancel-in-progress: true
env:
# PAT posts as MervinPraison so issue_comment triggers claude.yml (GITHUB_TOKEN cannot chain workflows)
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
permissions:
pull-requests: write
issues: write
jobs:
detect-and-trigger:
runs-on: ubuntu-latest
steps:
- name: Detect conflicting PRs and trigger Claude
uses: actions/github-script@v7
with:
github-token: ${{ env.GH_TOKEN }}
script: |
const owner = context.repo.owner;
const repo = context.repo.repo;
const baseRepo = `${owner}/${repo}`;
const COOLDOWN_MS = 12 * 60 * 60 * 1000;
const AUTO_ACTORS = ['github-actions[bot]', 'MervinPraison'];
const LABEL = 'claude-conflict-pending';
const COMMENT_BODY = [
'@claude this PR has merge conflicts with `main`.',
'Please rebase onto latest `main`, resolve conflicts (keep this PR\'s intent, merge in newer main logic),',
'run targeted tests, and force-push with `--force-with-lease`.',
'Comment which files you resolved.',
'Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.',
].join(' ');
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
async function getMergeState(prNumber) {
const query = `
query($owner: String!, $repo: String!, $number: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $number) {
mergeStateStatus
maintainerCanModify
isDraft
headRef { repository { nameWithOwner } }
}
}
}
`;
for (let attempt = 0; attempt < 3; attempt++) {
const result = await github.graphql(query, { owner, repo, number: prNumber });
const prGql = result.repository.pullRequest;
const status = (prGql?.mergeStateStatus || '').toUpperCase();
if (status && status !== 'UNKNOWN') {
return {
status,
isDraft: prGql.isDraft,
headRepo: prGql.headRef?.repository?.nameWithOwner,
maintainerCanModify: prGql.maintainerCanModify === true,
};
}
if (attempt < 2) await sleep(10000);
}
const pr = await github.rest.pulls.get({ owner, repo, pull_number: prNumber });
return {
status: (pr.data.mergeable_state || '').toUpperCase(),
isDraft: pr.data.draft,
headRepo: pr.data.head.repo?.full_name,
maintainerCanModify: pr.data.maintainer_can_modify === true,
};
}
function hasFinalClaudeReviewTrigger(comments) {
return comments.some((c) => {
const body = (c.body || '').toLowerCase();
if (!AUTO_ACTORS.includes(c.user.login)) return false;
if (!body.includes('@claude')) return false;
if (body.includes('merge conflict')) return false;
return body.includes('final architecture reviewer') || body.includes('lead engineer');
});
}
function hasRecentAutoComment(comments) {
const cutoff = Date.now() - COOLDOWN_MS;
return comments.some((c) => {
if (!AUTO_ACTORS.includes(c.user.login)) return false;
const body = (c.body || '').toLowerCase();
if (!body.includes('@claude') || !body.includes('merge conflict')) return false;
return new Date(c.created_at).getTime() > cutoff;
});
}
async function processPR(prNumber) {
const { status, isDraft, headRepo, maintainerCanModify } = await getMergeState(prNumber);
core.info(`PR #${prNumber}: mergeState=${status}, head=${headRepo}, draft=${isDraft}, maintainerCanModify=${maintainerCanModify}`);
const { data: issue } = await github.rest.issues.get({
owner,
repo,
issue_number: prNumber,
});
const labels = issue.labels.map((l) => l.name);
if (status !== 'DIRTY' && labels.includes(LABEL)) {
await github.rest.issues.removeLabel({
owner,
repo,
issue_number: prNumber,
name: LABEL,
});
core.info(`Removed ${LABEL} from PR #${prNumber}`);
}
if (status !== 'DIRTY') return { skipped: true, reason: 'not dirty' };
if (isDraft) return { skipped: true, reason: 'draft' };
if (headRepo && headRepo !== baseRepo && !maintainerCanModify) {
return { skipped: true, reason: 'fork without maintainer edits' };
}
if (labels.includes(LABEL)) {
return { skipped: true, reason: 'conflict resolution pending' };
}
const { data: comments } = await github.rest.issues.listComments({
owner,
repo,
issue_number: prNumber,
per_page: 100,
});
if (hasRecentAutoComment(comments)) {
return { skipped: true, reason: 'recent auto comment' };
}
if (!hasFinalClaudeReviewTrigger(comments)) {
return { skipped: true, reason: 'awaiting final claude review trigger' };
}
await github.rest.issues.addLabels({
owner,
repo,
issue_number: prNumber,
labels: [LABEL],
});
await github.rest.issues.createComment({
owner,
repo,
issue_number: prNumber,
body: COMMENT_BODY,
});
return { triggered: true };
}
let prNumbers = [];
if (context.eventName === 'pull_request') {
prNumbers = [context.payload.pull_request.number];
} else {
const prs = await github.paginate(github.rest.pulls.list, {
owner,
repo,
state: 'open',
per_page: 100,
});
prNumbers = prs.map((p) => p.number);
}
const results = [];
for (const num of prNumbers) {
results.push({ pr: num, ...(await processPR(num)) });
}
core.summary.addRaw('```json\n' + JSON.stringify(results, null, 2) + '\n```');
await core.summary.write();
-126
View File
@@ -1,126 +0,0 @@
name: Nightly Release Gate
# Preflight checks before dispatching PyPI Release (patch bump).
# Runs nightly at 00:00 UTC (midnight).
on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:
inputs:
dry_run:
description: 'Run preflight only; do not dispatch pypi-release.yml'
required: false
default: false
type: boolean
permissions:
contents: read
actions: write
concurrency:
group: nightly-release-gate
cancel-in-progress: false
jobs:
preflight:
if: github.ref == 'refs/heads/main' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
steps:
- name: Checkout main
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: main
fetch-depth: 0
- name: Preflight — path changes and CI green
id: preflight
run: |
set -euo pipefail
git fetch --tags origin
LAST_TAG=$(git describe --tags --match 'v*' --abbrev=0 2>/dev/null || true)
if [ -z "$LAST_TAG" ]; then
echo "No v* tag found — skip automated release (first release must be manual)"
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "reason=no_tag" >> "$GITHUB_OUTPUT"
exit 0
fi
CHANGED=$(git diff --name-only "${LAST_TAG}" HEAD -- \
src/praisonai \
src/praisonai-agents || true)
if [ -z "$CHANGED" ]; then
echo "No changes in src/praisonai or src/praisonai-agents since ${LAST_TAG} — skip"
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "reason=no_path_changes" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Changes since ${LAST_TAG}:"
echo "$CHANGED"
HEAD_SHA=$(git rev-parse HEAD)
echo "main HEAD: ${HEAD_SHA}"
GREEN_SHA=$(gh run list \
--repo "${GITHUB_REPOSITORY}" \
--workflow "Core Tests" \
--branch main \
--status success \
--json headSha \
--jq '.[0].headSha // empty')
if [ "$GREEN_SHA" != "$HEAD_SHA" ]; then
echo "main@${HEAD_SHA} is not covered by a green Core Tests run (last green: ${GREEN_SHA:-none}) — skip"
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "reason=ci_not_green" >> "$GITHUB_OUTPUT"
exit 0
fi
IN_PROGRESS=$(gh run list \
--repo "${GITHUB_REPOSITORY}" \
--workflow "Core Tests" \
--branch main \
--json headSha,status \
--jq "[.[] | select(.headSha == \"${HEAD_SHA}\" and .status == \"in_progress\")] | length")
if [ "${IN_PROGRESS:-0}" != "0" ]; then
echo "Core Tests still in progress on ${HEAD_SHA} — skip"
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "reason=ci_in_progress" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "Preflight passed — ready to dispatch patch release"
echo "should_release=true" >> "$GITHUB_OUTPUT"
echo "reason=ready" >> "$GITHUB_OUTPUT"
echo "last_tag=${LAST_TAG}" >> "$GITHUB_OUTPUT"
echo "head_sha=${HEAD_SHA}" >> "$GITHUB_OUTPUT"
- name: Dry run summary
if: inputs.dry_run == true
run: |
echo "Dry run — preflight only, no pypi-release dispatch."
echo "should_release=${{ steps.preflight.outputs.should_release }}"
echo "reason=${{ steps.preflight.outputs.reason }}"
if [ "${{ steps.preflight.outputs.should_release }}" = "true" ]; then
echo "Would dispatch: pypi-release.yml bump=patch source=nightly"
fi
- name: Dispatch PyPI Release (patch)
if: steps.preflight.outputs.should_release == 'true' && inputs.dry_run != true
run: |
set -euo pipefail
gh workflow run pypi-release.yml \
--repo "${GITHUB_REPOSITORY}" \
--ref main \
-f bump=patch \
-f source=nightly \
-f dry_run=false
echo "Dispatched pypi-release.yml (patch, source=nightly)"
echo "Approve the pypi environment when prompted."
@@ -1,33 +0,0 @@
name: Pipeline Status Sync
# Sync pipeline/* stage and blocker labels on open internal PRs.
on:
schedule:
- cron: '*/10 * * * *'
workflow_dispatch:
permissions:
pull-requests: read
issues: write
actions: write
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Sync pipeline labels on open PRs
uses: actions/github-script@v7
with:
github-token: ${{ secrets.GH_TOKEN }}
script: |
const path = require('path');
const ps = require(path.join(process.env.GITHUB_WORKSPACE, '.github/scripts/pipeline-status.js'));
await ps.syncOpenPullRequests(
github, context.repo.owner, context.repo.repo,
{ maxPrs: 30, dispatchMergeGate: true }, core
);
@@ -1,71 +0,0 @@
name: PraisonAI Issue Triage
on:
issue_comment:
types: [created]
workflow_dispatch:
inputs:
issue_number:
description: 'Issue Number'
required: true
type: string
jobs:
triage:
runs-on: ubuntu-latest
if: >
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
!github.event.issue.pull_request &&
contains(github.event.comment.body, '@praisonai') &&
github.actor != 'github-actions[bot]' &&
github.event.comment.user.type != 'Bot' &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'))
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
- name: Generate GitHub App Token
id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.PRAISONAI_APP_ID }}
private_key: ${{ secrets.PRAISONAI_APP_PRIVATE_KEY }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Set up Git
run: |
git config --local user.email "[email protected]"
git config --local user.name "PraisonAI Bot"
- name: Install PraisonAI
run: |
pip install uv
cd src/praisonai-agents && uv pip install --system -e ".[all]"
cd ../praisonai && uv pip install --system -e ".[all]"
- name: Run PraisonAI Issue Triage
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
PRAISONAI_AUTO_APPROVE: "true"
ISSUE_NUMBER_INPUT: ${{ inputs.issue_number }}
ISSUE_NUMBER_EVENT: ${{ github.event.issue.number }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
export ISSUE_NUMBER="$ISSUE_NUMBER_INPUT"
else
export ISSUE_NUMBER="$ISSUE_NUMBER_EVENT"
fi
praisonai github triage --issue $ISSUE_NUMBER --agent-file .github/praisonai-issue-triage.yaml
-73
View File
@@ -1,73 +0,0 @@
name: PraisonAI PR Reviewer
on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
issue_comment:
types: [created]
workflow_dispatch:
inputs:
pr_number:
description: 'Pull Request Number'
required: true
type: string
jobs:
review:
runs-on: ubuntu-latest
if: >
(github.event_name == 'pull_request' && github.event.pull_request.draft == false) ||
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@praisonai') &&
github.actor != 'github-actions[bot]' &&
github.event.comment.user.type != 'Bot' &&
(github.event.comment.author_association == 'OWNER' ||
github.event.comment.author_association == 'MEMBER' ||
github.event.comment.author_association == 'COLLABORATOR'))
steps:
- name: Determine checkout ref
id: dest
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
PR_NUMBER_INPUT: ${{ inputs.pr_number }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
if [ "${{ github.event_name }}" = "issue_comment" ]; then
echo "ref=refs/pull/$ISSUE_NUMBER/head" >> "$GITHUB_OUTPUT"
elif [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "ref=refs/pull/$PR_NUMBER_INPUT/head" >> "$GITHUB_OUTPUT"
else
echo "ref=$PR_HEAD_SHA" >> "$GITHUB_OUTPUT"
fi
- name: Checkout Repository
uses: actions/checkout@v4
with:
persist-credentials: false
ref: ${{ steps.dest.outputs.ref }}
fetch-depth: 0
- name: Generate GitHub App Token
id: generate_token
uses: tibdex/github-app-token@v2
with:
app_id: ${{ secrets.PRAISONAI_APP_ID }}
private_key: ${{ secrets.PRAISONAI_APP_PRIVATE_KEY }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install PraisonAI
run: pip install "praisonai[all]"
- name: Run PraisonAI PR Review
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
praisonai workflow run .github/praisonai-reviewer.yaml
-305
View File
@@ -1,305 +0,0 @@
name: PyPI Release
# Requires a GitHub Environment named "pypi" (Settings → Environments → pypi)
# with required reviewers before secrets are exposed to this job.
on:
workflow_dispatch:
inputs:
bump:
description: 'Version bump type for both packages'
required: true
default: patch
type: choice
options:
- patch
- minor
- major
dry_run:
description: 'Preview versions and steps without publishing or pushing'
required: false
default: false
type: boolean
skip_agents:
description: 'Skip agents publish (recovery if already on PyPI)'
required: false
default: false
type: boolean
skip_wrapper:
description: 'Publish agents only'
required: false
default: false
type: boolean
agents_version:
description: 'Override agents version (empty = auto bump from pyproject.toml)'
required: false
default: ''
type: string
wrapper_version:
description: 'Override wrapper version (empty = auto bump from version.py)'
required: false
default: ''
type: string
source:
description: 'Trigger source (audit trail)'
required: false
default: manual
type: choice
options:
- manual
- nightly
permissions:
contents: write
concurrency:
group: pypi-release
cancel-in-progress: false
jobs:
release:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: pypi
env:
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN || secrets.PYPI_API_TOKEN }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
steps:
- name: Checkout main
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
ref: main
fetch-depth: 0
token: ${{ secrets.GH_TOKEN }}
- name: Set up Python
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: '3.11'
- name: Set up uv
uses: astral-sh/setup-uv@38f3f104447c67c051c4a08e39b64a148898af3a # v4
- name: Configure git
run: |
git config user.name "MervinPraison"
git config user.email "[email protected]"
gh auth setup-git
- name: Release summary
run: |
echo "Source: ${{ inputs.source }}"
echo "Bump: ${{ inputs.bump }}"
echo "Dry run: ${{ inputs.dry_run }}"
- name: Compute release versions
id: versions
env:
BUMP: ${{ inputs.bump }}
AGENTS_OVERRIDE: ${{ inputs.agents_version }}
WRAPPER_OVERRIDE: ${{ inputs.wrapper_version }}
SKIP_AGENTS: ${{ inputs.skip_agents }}
run: |
python <<'PY'
import os
import re
import sys
from pathlib import Path
SEMVER = re.compile(r"^\d+\.\d+\.\d+$")
root = Path(".")
bump = os.environ["BUMP"]
def bump_version(version: str) -> str:
parts = version.split(".")
if len(parts) != 3:
print(f"Invalid version: {version}", file=sys.stderr)
sys.exit(1)
maj, min_, patch = (int(parts[0]), int(parts[1]), int(parts[2]))
if bump == "major":
return f"{maj + 1}.0.0"
if bump == "minor":
return f"{maj}.{min_ + 1}.0"
return f"{maj}.{min_}.{patch + 1}"
def validate_version(label: str, value: str) -> None:
if value and not SEMVER.match(value):
print(f"Invalid {label} override: {value!r} (expected X.Y.Z)", file=sys.stderr)
sys.exit(1)
agents_override = os.environ.get("AGENTS_OVERRIDE", "").strip()
wrapper_override = os.environ.get("WRAPPER_OVERRIDE", "").strip()
validate_version("agents_version", agents_override)
validate_version("wrapper_version", wrapper_override)
agents_pyproject = root / "src/praisonai-agents/pyproject.toml"
agents_content = agents_pyproject.read_text()
agents_match = re.search(r'^version\s*=\s*"([^"]+)"', agents_content, re.MULTILINE)
if not agents_match:
print("Could not read agents version from pyproject.toml", file=sys.stderr)
sys.exit(1)
current_agents = agents_match.group(1)
if not SEMVER.match(current_agents):
print(f"Invalid current agents version: {current_agents}", file=sys.stderr)
sys.exit(1)
wrapper_version_py = root / "src/praisonai/praisonai/version.py"
wrapper_content = wrapper_version_py.read_text()
wrapper_match = re.search(r'__version__ = "([^"]+)"', wrapper_content)
if not wrapper_match:
print("Could not read wrapper version from version.py", file=sys.stderr)
sys.exit(1)
current_wrapper = wrapper_match.group(1)
if not SEMVER.match(current_wrapper):
print(f"Invalid current wrapper version: {current_wrapper}", file=sys.stderr)
sys.exit(1)
skip_agents = os.environ.get("SKIP_AGENTS", "false").lower() == "true"
if agents_override:
agents_version = agents_override
elif skip_agents:
agents_version = current_agents
else:
agents_version = bump_version(current_agents)
if wrapper_override:
wrapper_version = wrapper_override
else:
wrapper_version = bump_version(current_wrapper)
github_output = os.environ["GITHUB_OUTPUT"]
with open(github_output, "a", encoding="utf-8") as fh:
fh.write(f"agents_version={agents_version}\n")
fh.write(f"wrapper_version={wrapper_version}\n")
fh.write(f"current_agents={current_agents}\n")
fh.write(f"current_wrapper={current_wrapper}\n")
print(f"Agents: {current_agents} -> {agents_version}")
print(f"Wrapper: {current_wrapper} -> {wrapper_version}")
PY
- name: Dry run summary
if: inputs.dry_run
run: |
echo "Dry run — no publish, commit, or push."
echo "Agents version: ${{ steps.versions.outputs.agents_version }}"
echo "Wrapper version: ${{ steps.versions.outputs.wrapper_version }}"
echo ""
if [ "${{ inputs.skip_agents }}" != "true" ]; then
echo "Would publish praisonaiagents ${{ steps.versions.outputs.agents_version }}"
echo "Would commit src/praisonai-agents/pyproject.toml and uv.lock"
fi
if [ "${{ inputs.skip_wrapper }}" != "true" ]; then
echo "Would run bump_and_release.py ${{ steps.versions.outputs.wrapper_version }} --agents ${{ steps.versions.outputs.agents_version }}"
echo "Would uv publish praisonai ${{ steps.versions.outputs.wrapper_version }}"
fi
- name: Publish praisonaiagents
if: inputs.dry_run != true && inputs.skip_agents != true
working-directory: src/praisonai-agents
env:
NEW_VERSION: ${{ steps.versions.outputs.agents_version }}
CURRENT: ${{ steps.versions.outputs.current_agents }}
run: |
set -euo pipefail
python <<'PY'
import os
from pathlib import Path
path = Path("pyproject.toml")
content = path.read_text()
current = os.environ["CURRENT"]
new = os.environ["NEW_VERSION"]
path.write_text(content.replace(f'version = "{current}"', f'version = "{new}"', 1))
PY
rm -rf dist
uv lock
uv build
uv publish
- name: Wait for praisonaiagents on PyPI
if: inputs.dry_run != true && inputs.skip_agents != true
run: |
set -euo pipefail
PACKAGE="praisonaiagents"
VERSION="${{ steps.versions.outputs.agents_version }}"
MAX_WAIT=600
INTERVAL=30
START=$(date +%s)
while true; do
if curl -fsSL "https://pypi.org/pypi/${PACKAGE}/${VERSION}/json" >/dev/null; then
echo "✅ ${PACKAGE}==${VERSION} is available on PyPI"
exit 0
fi
ELAPSED=$(( $(date +%s) - START ))
if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then
echo "❌ Timeout waiting for ${PACKAGE}==${VERSION} on PyPI"
exit 1
fi
echo "⏳ Waiting for ${PACKAGE}==${VERSION} ($(( MAX_WAIT - ELAPSED ))s remaining...)"
sleep "$INTERVAL"
done
- name: Commit agents version bump
if: inputs.dry_run != true && inputs.skip_agents != true
run: |
set -euo pipefail
git add src/praisonai-agents/pyproject.toml src/praisonai-agents/uv.lock
git diff --cached --quiet && { echo "No agents files to commit"; exit 0; }
git commit -m "Bump praisonaiagents to ${{ steps.versions.outputs.agents_version }}"
git pull --rebase origin main
git push origin main
- name: Bump and release wrapper
if: inputs.dry_run != true && inputs.skip_wrapper != true
run: |
set -euo pipefail
python src/praisonai/scripts/bump_and_release.py "${{ steps.versions.outputs.wrapper_version }}" \
--agents "${{ steps.versions.outputs.agents_version }}" \
--wait --max-wait 600 --no-add-all
- name: Publish praisonai wrapper to PyPI
if: inputs.dry_run != true && inputs.skip_wrapper != true
working-directory: src/praisonai
run: uv publish
- name: Wait for praisonai on PyPI
if: inputs.dry_run != true && inputs.skip_wrapper != true
run: |
set -euo pipefail
PACKAGE="praisonai"
VERSION="${{ steps.versions.outputs.wrapper_version }}"
MAX_WAIT=600
INTERVAL=30
START=$(date +%s)
while true; do
if curl -fsSL "https://pypi.org/pypi/${PACKAGE}/${VERSION}/json" >/dev/null; then
echo "✅ ${PACKAGE}==${VERSION} is available on PyPI"
exit 0
fi
ELAPSED=$(( $(date +%s) - START ))
if [ "$ELAPSED" -ge "$MAX_WAIT" ]; then
echo "❌ Timeout waiting for ${PACKAGE}==${VERSION} on PyPI"
exit 1
fi
echo "⏳ Waiting for ${PACKAGE}==${VERSION} ($(( MAX_WAIT - ELAPSED ))s remaining...)"
sleep "$INTERVAL"
done
- name: Verify PyPI versions
if: inputs.dry_run != true
run: |
set -euo pipefail
AGENTS="${{ steps.versions.outputs.agents_version }}"
WRAPPER="${{ steps.versions.outputs.wrapper_version }}"
if [ "${{ inputs.skip_agents }}" != "true" ]; then
echo "✅ praisonaiagents==${AGENTS}"
fi
if [ "${{ inputs.skip_wrapper }}" != "true" ]; then
echo "✅ praisonai==${WRAPPER}"
fi
-42
View File
@@ -1,42 +0,0 @@
name: Python Package (Multi-Version)
on:
schedule:
- cron: '0 4 * * 1' # Weekly Monday 4am UTC
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Set environment variables
run: |
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY || 'sk-test-key-for-github-actions-testing-only-not-real' }}" >> $GITHUB_ENV
echo "OPENAI_API_BASE=${{ secrets.OPENAI_API_BASE || 'https://api.openai.com/v1' }}" >> $GITHUB_ENV
echo "OPENAI_MODEL_NAME=${{ secrets.OPENAI_MODEL_NAME || 'gpt-5-nano' }}" >> $GITHUB_ENV
echo "PYTHONPATH=${{ github.workspace }}/src/praisonai-agents:$PYTHONPATH" >> $GITHUB_ENV
- name: Install dependencies
run: |
cd src/praisonai
python -m pip install --upgrade pip
python -m pip install ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
python -m pip install duckduckgo_search pytest pytest-asyncio pytest-cov
python -m pip install "praisonaiagents[knowledge]"
- name: Run tests
run: |
cd src/praisonai && python -m pytest tests/unit/ -v --tb=short --disable-warnings --timeout=60 || echo "Some tests failed"
-45
View File
@@ -1,45 +0,0 @@
# This workflow will upload a Python Package using Twine when a release is created
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
name: Upload Python Package
# Superseded by pypi-release.yml (manual workflow_dispatch with uv publish).
# Kept as workflow_dispatch-only fallback; release trigger removed to avoid
# duplicate PyPI publish when bump_and_release creates a GitHub release.
on:
workflow_dispatch:
permissions:
contents: read
jobs:
deploy:
if: false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build
- name: Build package
run: python -m build src/praisonai
- name: Publish package
uses: pypa/gh-action-pypi-publish@27b31702a0e7fc50959f5ad993c78deac1bdfc29
with:
user: __token__
password: ${{ secrets.PYPI_API_TOKEN }}
packages_dir: src/praisonai/dist/
-541
View File
@@ -1,541 +0,0 @@
name: Release Workflow
on:
workflow_dispatch:
inputs:
tag:
description: 'Tag to create release with (e.g., v2.0.10)'
required: true
fork_owner:
description: 'GitHub username of the fork owner'
required: true
default: 'tosin2013'
jobs:
test-ubuntu:
strategy:
matrix:
os: [ubuntu-22.04, ubuntu-24.04]
framework: ['crewai', 'autogen']
fail-fast: false
runs-on: ${{ matrix.os }}
timeout-minutes: 90
steps:
# First checkout the upstream repo at the specified tag
- name: Checkout upstream repo
uses: actions/checkout@v4
with:
persist-credentials: false
repository: MervinPraison/PraisonAI
ref: ${{ github.event.inputs.tag }}
fetch-depth: 0
# Get the commit hash from the tag
- name: Get commit hash
id: get_commit
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
COMMIT_HASH=$(git rev-parse HEAD)
echo "commit_hash=$COMMIT_HASH" >> $GITHUB_OUTPUT
echo "Found commit: $COMMIT_HASH for tag $INPUT_TAG"
# Now checkout the fork at main branch
- name: Checkout fork
uses: actions/checkout@v4
with:
persist-credentials: false
repository: ${{ github.event.inputs.fork_owner }}/PraisonAI
ref: main
fetch-depth: 0
clean: true
# Sync with upstream commit
- name: Sync with upstream
run: |
git config --global user.name "MervinPraison"
git config --global user.email "[email protected]"
# Add upstream remote
git remote add upstream https://github.com/MervinPraison/PraisonAI.git
# Fetch from upstream
git fetch upstream --tags --force
- name: Hard reset to tag commit
env:
COMMIT_HASH: ${{ steps.get_commit.outputs.commit_hash }}
run: |
git reset --hard $COMMIT_HASH
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install Poetry
run: |
curl -sSL https://install.python-poetry.org | python3 -
echo "$HOME/.local/bin" >> $GITHUB_PATH
poetry config virtualenvs.in-project true
poetry config virtualenvs.create true
poetry --version
poetry config --list
- name: Install Dependencies
run: |
poetry lock #--no-update
poetry install --all-extras
poetry env info
# Verify venv exists
test -d .venv || { echo "Virtual environment not created!"; exit 1; }
test -f .venv/bin/activate || { echo "Activation script not found!"; exit 1; }
- name: Run setup script
timeout-minutes: 20
run: |
set -e # Exit on any error
curl -OL https://raw.githubusercontent.com/tosin2013/PraisonAI/refs/heads/main/setup.sh
chmod +x setup.sh
./setup.sh --all --cicd || { echo "Setup script failed"; exit 1; }
env:
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
PRAISONAI_NON_INTERACTIVE: "true"
- name: Test Basic Functionality
run: |
set -e # Exit on any error
# Random sleep between 60-120 seconds to avoid API rate limits
SLEEP_TIME=$((RANDOM % 61 + 60))
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
sleep $SLEEP_TIME
lsof -ti:8084 | xargs -r kill -9 || true
source .venv/bin/activate || { echo "Failed to activate virtual environment"; exit 1; }
mkdir -p test_output || { echo "Failed to create test_output directory"; exit 1; }
echo "Testing basic CLI functionality..."
# Test auto generation with explicit framework
praisonai --framework ${{ matrix.framework }} --auto "create a short test script" 2>&1 | tee test_output/auto_test.log
# Verify the output file exists and has content
if [[ ! -s test_output/auto_test.log ]]; then
echo "Error: No output generated from auto test"
exit 1
fi
# Test auto generation without framework
echo "Basic functionality tests passed!"
env:
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
LOGLEVEL: "debug"
PRAISONAI_NON_INTERACTIVE: "true"
- name: Test Framework-specific Features
continue-on-error: false
run: |
source .venv/bin/activate
# Random sleep between 60-120 seconds to avoid API rate limits
SLEEP_TIME=$((RANDOM % 61 + 60))
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
sleep $SLEEP_TIME
lsof -ti:8084 | xargs -r kill -9 || true
praisonai --framework ${{ matrix.framework }} --auto "create a movie script about Robots in Mars" 2>&1 | tee test_output/framework_output.txt || echo "Framework-specific test failed" >> test_output/errors.log
env:
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
PRAISONAI_NON_INTERACTIVE: "true"
- name: Test Core Functionality
continue-on-error: false
run: |
source .venv/bin/activate
# Random sleep between 60-120 seconds to avoid API rate limits
SLEEP_TIME=$((RANDOM % 61 + 60))
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
sleep $SLEEP_TIME
echo "Testing core functionality..."
# Test package imports
python3 -c "from praisonai.cli import PraisonAI; from praisonai.auto import AutoGenerator; from praisonai.agents_generator import AgentsGenerator" || exit 1
# Test configuration loading
python3 -c "from praisonai.inc.config import generate_config; print(generate_config())" || exit 1
# Test tools loading
python3 -c "from praisonai.inbuilt_tools import *" || exit 1
echo "Core functionality tests passed!"
env:
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
LOGLEVEL: "debug"
PRAISONAI_NON_INTERACTIVE: "true"
- name: Test Agent Generation
continue-on-error: false
run: |
source .venv/bin/activate
# Random sleep between 60-120 seconds to avoid API rate limits
SLEEP_TIME=$((RANDOM % 61 + 60))
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
sleep $SLEEP_TIME
echo "Testing agent generation..."
# Create a test YAML file
cat > test_agents.yaml <<EOF
topic: "Test automation task"
framework: "${{ matrix.framework }} "
roles:
- name: "Tester"
goal: "Test the system"
backstory: "Expert system tester"
tasks:
- description: "Verify system functionality"
expected_output: "Test results"
EOF
# Test YAML loading and agent generation
python3 -c '
from praisonai.agents_generator import AgentsGenerator
config = [{"model": "llama-3.3-70b-versatile", "base_url": "https://api.groq.com/openai/v1/"}]
generator = AgentsGenerator("test_agents.yaml", "${{ matrix.framework }} ", config)
print("Agent generation test passed!")
' || exit 1
echo "Agent generation tests passed!"
env:
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
LOGLEVEL: "debug"
PRAISONAI_NON_INTERACTIVE: "true"
- name: Test Custom Tools Integration
continue-on-error: false
run: |
source .venv/bin/activate
# Random sleep between 60-120 seconds to avoid API rate limits
SLEEP_TIME=$((RANDOM % 61 + 60))
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
sleep $SLEEP_TIME
echo "Testing custom tools integration..."
# Create a test tool
cat > test_tool.py <<EOF
from duckduckgo_search import DDGS
from praisonai_tools import BaseTool
class InternetSearchTool(BaseTool):
name: str = "Internet Search Tool"
description: str = "Search Internet for relevant information"
def _run(self, query: str):
return {"status": "success", "query": query}
def test(self):
"""Test method to verify tool setup"""
test_query = "test query"
result = self._run(test_query)
assert result["status"] == "success"
assert result["query"] == test_query
return True
EOF
# Test tool loading and initialization
python3 -c '
import importlib.util
import sys
# Load the test tool
spec = importlib.util.spec_from_file_location("test_tool", "test_tool.py")
module = importlib.util.module_from_spec(spec)
sys.modules["test_tool"] = module
spec.loader.exec_module(module)
# Initialize and test the tool
tool = module.InternetSearchTool()
assert tool.test(), "Tool test failed"
print("Custom tool test passed!")
' || exit 1
echo "Custom tools integration test passed!"
env:
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
LOGLEVEL: "debug"
PRAISONAI_NON_INTERACTIVE: "true"
- name: Test PraisonAI Agents Configuration
continue-on-error: false
run: |
source .venv/bin/activate
# Random sleep between 60-120 seconds to avoid API rate limits
SLEEP_TIME=$((RANDOM % 61 + 60))
echo "Sleeping for $SLEEP_TIME seconds to avoid API rate limits..."
sleep $SLEEP_TIME
echo "Testing agents configuration..."
mkdir -p test_output || { echo "Failed to create test_output directory"; exit 1; }
# Create test script
cat > test_agents.py <<EOF
from langchain_openai import ChatOpenAI
from crewai import Agent, Task, Crew
import os
import logging
import time
import random
logging.basicConfig(level="DEBUG")
logger = logging.getLogger(__name__)
def random_sleep(min_seconds=15, max_seconds=30):
"""Sleep for a random duration to prevent API rate limiting"""
duration = random.uniform(min_seconds, max_seconds)
logger.info(f"Rate limit protection: Sleeping for {duration:.2f} seconds...")
time.sleep(duration)
class RateLimitedChatOpenAI(ChatOpenAI):
"""ChatOpenAI with built-in rate limiting"""
def __call__(self, *args, **kwargs):
random_sleep()
return super().__call__(*args, **kwargs)
def run_test():
logger.info("Initializing test components...")
# Initialize the LLM with rate limiting
llm = RateLimitedChatOpenAI(
model=os.getenv('OPENAI_MODEL_NAME', 'llama-3.3-70b-versatile'),
base_url=os.getenv('OPENAI_API_BASE', 'https://api.groq.com/openai/v1/'),
api_key=os.getenv('OPENAI_API_KEY'),
temperature=0.7
)
random_sleep()
logger.info("Creating researcher agent...")
researcher = Agent(
role="Test Automation Expert",
goal="Verify system functionality and performance",
backstory="""You are an expert in test automation and system verification,
skilled in identifying potential issues and validating functionality.""",
verbose=True,
allow_delegation=False,
llm=llm
)
random_sleep()
logger.info("Creating validator agent...")
validator = Agent(
role="Quality Assurance Specialist",
goal="Validate test results and ensure system reliability",
backstory="""You are a QA specialist focused on ensuring
system reliability and proper functionality.""",
verbose=True,
allow_delegation=False,
llm=llm
)
random_sleep()
logger.info("Creating test tasks...")
test_task = Task(
description="Run a basic system verification test",
expected_output="A report on system functionality verification",
agent=researcher
)
random_sleep()
validation_task = Task(
description="Validate the test results and compile findings",
expected_output="A validation report of test findings",
agent=validator
)
random_sleep()
logger.info("Initializing crew...")
crew = Crew(
agents=[researcher, validator],
tasks=[test_task, validation_task],
verbose=2
)
logger.info("Starting crew execution...")
result = crew.kickoff()
logger.info("Test execution completed.")
logger.info(f"Result: {result}")
return result
if __name__ == "__main__":
try:
run_test()
except Exception as e:
logger.error(f"Test failed with error: {str(e)}", exc_info=True)
raise
EOF
# Run the test script with increased timeout
echo "Testing with Python API..."
timeout 1800 python3 test_agents.py 2>&1 | tee test_output/agents_test.log
# Verify the output
if [[ ! -s test_output/agents_test.log ]]; then
echo "Error: No output generated from agents test"
exit 1
fi
echo "Agents configuration tests passed!"
env:
OPENAI_API_KEY: ${{ secrets.GROQ_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
OPENAI_MODEL_NAME: "llama-3.3-70b-versatile"
OPENAI_API_BASE: "https://api.groq.com/openai/v1/"
OPENAI_API_BASE_URL: "https://api.groq.com/openai/v1/"
LOGLEVEL: "debug"
PRAISONAI_NON_INTERACTIVE: "true"
- name: Upload test artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: praisonai-output-${{ matrix.os }}-${{ matrix.framework }}
path: test_output/
retention-days: 14
create-release:
needs: [test-ubuntu]
runs-on: ubuntu-latest
if: success()
steps:
# First checkout the fork's main branch
- name: Checkout fork repo
uses: actions/checkout@v4
with:
persist-credentials: false
fetch-depth: 0
token: ${{ secrets.PAT_TOKEN }}
repository: ${{ github.event.inputs.fork_owner }}/PraisonAI
ref: main
# Add upstream and get commit from tag
- name: Get upstream tag commit
id: get_upstream_commit
env:
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
# Add upstream remote
git remote add upstream https://github.com/MervinPraison/PraisonAI.git
# Delete local tag if it exists
git tag -d "$INPUT_TAG" || true
# Fetch from upstream including tags with force
git fetch upstream --tags --force
# Verify tag exists in upstream
if ! git ls-remote --tags upstream | grep -q "refs/tags/$INPUT_TAG$"; then
echo "::error::Tag $INPUT_TAG not found in upstream repository"
exit 1
fi
# Get the commit hash from the upstream tag
UPSTREAM_COMMIT=$(git rev-list -n 1 refs/tags/"$INPUT_TAG")
if [ -z "$UPSTREAM_COMMIT" ]; then
echo "::error::Could not find commit for tag $INPUT_TAG"
exit 1
fi
echo "upstream_commit=$UPSTREAM_COMMIT" >> $GITHUB_OUTPUT
echo "Found commit: $UPSTREAM_COMMIT for tag $INPUT_TAG"
# Create tag at the same commit
- name: Create tag at commit
env:
GIT_ASKPASS: /bin/echo
PAT_TOKEN: ${{ secrets.PAT_TOKEN }}
INPUT_TAG: ${{ github.event.inputs.tag }}
run: |
git config --global user.name "MervinPraison"
git config --global user.email "[email protected]"
# Remove tag if it exists locally
git tag -d "$INPUT_TAG" || true
# Remove tag if it exists remotely
git push origin :refs/tags/"$INPUT_TAG" || true
- name: Fetch tag from upstream
env:
UPSTREAM_COMMIT: ${{ steps.get_upstream_commit.outputs.upstream_commit }}
run: |
git fetch upstream $UPSTREAM_COMMIT --force
- name: Reset to upstream commit
env:
UPSTREAM_COMMIT: ${{ steps.get_upstream_commit.outputs.upstream_commit }}
run: |
git reset --hard $UPSTREAM_COMMIT
# Create and push tag
git tag -a "$INPUT_TAG" -m "Release $INPUT_TAG (synced from upstream MervinPraison/PraisonAI@$INPUT_TAG)"
git push origin "$INPUT_TAG"
# Create the release
- name: Create Release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.PAT_TOKEN }}
with:
tag_name: ${{ github.event.inputs.tag }}
release_name: Release ${{ github.event.inputs.tag }}
draft: false
prerelease: false
body: |
Release ${{ github.event.inputs.tag }} (synced from upstream)
This release is synchronized with MervinPraison/PraisonAI@${{ github.event.inputs.tag }}
Commit: ${{ steps.get_upstream_commit.outputs.upstream_commit }}
For installation:
```bash
pip install praisonai
```
Changes from upstream v2.0.10:
- Updated agent configuration tests
- Added rate limiting protection
- Improved test reliability
Forked from: MervinPraison/PraisonAI
@@ -1,31 +0,0 @@
name: Sync Claude Secrets to PraisonAIUI
on:
workflow_dispatch:
permissions:
contents: read
jobs:
sync:
runs-on: ubuntu-latest
steps:
- name: Sync secrets to PraisonAIUI
env:
GH_TOKEN: ${{ secrets.SECRETS_ADMIN_PAT }}
TARGET: MervinPraison/PraisonAIUI
CLAUDE_APP_ID: ${{ secrets.CLAUDE_APP_ID }}
CLAUDE_APP_PRIVATE_KEY: ${{ secrets.CLAUDE_APP_PRIVATE_KEY }}
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
GH_TOKEN_VALUE: ${{ secrets.GH_TOKEN }}
run: |
set -euo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
echo "SECRETS_ADMIN_PAT is not configured in this repository"
exit 1
fi
echo -n "$CLAUDE_APP_ID" | gh secret set CLAUDE_APP_ID --repo "$TARGET"
echo -n "$CLAUDE_APP_PRIVATE_KEY" | gh secret set CLAUDE_APP_PRIVATE_KEY --repo "$TARGET"
echo -n "$CLAUDE_CODE_OAUTH_TOKEN" | gh secret set CLAUDE_CODE_OAUTH_TOKEN --repo "$TARGET"
echo -n "$GH_TOKEN_VALUE" | gh secret set GH_TOKEN --repo "$TARGET"
echo "Synced CLAUDE_APP_ID, CLAUDE_APP_PRIVATE_KEY, CLAUDE_CODE_OAUTH_TOKEN, GH_TOKEN → $TARGET"
-203
View File
@@ -1,203 +0,0 @@
name: Comprehensive Test Suite
on:
workflow_dispatch: # Allow manual triggering
inputs:
test_type:
description: 'Type of tests to run'
required: true
default: 'all'
type: choice
options:
- all
- unit
- integration
- fast
- performance
- frameworks
- autogen
- crewai
release:
types: [published, prereleased]
schedule:
# Run comprehensive tests weekly on Sundays at 3 AM UTC
- cron: '0 3 * * 0'
jobs:
comprehensive-test:
runs-on: ubuntu-latest
timeout-minutes: 15
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_MODEL_NAME: gpt-4o-mini
strategy:
matrix:
python-version: ["3.11"]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install UV
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: |
cd src/praisonai
uv pip install --system ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
uv pip install --system duckduckgo_search
uv pip install --system pytest pytest-asyncio pytest-cov pytest-benchmark pytest-timeout
# Install knowledge dependencies from praisonai-agents
uv pip install --system "praisonaiagents[knowledge]"
- name: Verify environment variables
run: |
if [ -n "$OPENAI_API_KEY" ]; then
echo "✅ OPENAI_API_KEY is set (length: ${#OPENAI_API_KEY})"
else
echo "⚠️ OPENAI_API_KEY is NOT set"
fi
- name: Run Comprehensive Test Suite
env:
INPUT_TEST_TYPE: ${{ github.event.inputs.test_type || 'all' }}
run: |
# Determine test type from input or default to 'all'
TEST_TYPE="$INPUT_TEST_TYPE"
echo "🧪 Running comprehensive test suite (type: $TEST_TYPE)"
case $TEST_TYPE in
"unit")
cd src/praisonai && python -m pytest tests/unit/ -v --tb=short --disable-warnings --cov=praisonai --cov-report=xml --cov-branch || echo "Some unit tests failed"
;;
"integration")
cd src/praisonai && python -m pytest tests/integration/ -v --tb=short --disable-warnings --cov=praisonai --cov-report=xml --cov-branch || echo "Some integration tests failed"
;;
"fast")
cd src/praisonai && python tests/test_runner.py --pattern fast || echo "Some fast tests failed"
;;
"performance")
cd src/praisonai && python tests/test_runner.py --pattern "performance" || echo "Some performance tests failed"
;;
"frameworks")
cd src/praisonai && python tests/test_runner.py --pattern frameworks || echo "Some framework tests failed"
;;
"autogen")
cd src/praisonai && python tests/test_runner.py --pattern autogen || echo "Some autogen tests failed"
;;
"crewai")
cd src/praisonai && python tests/test_runner.py --pattern crewai || echo "Some crewai tests failed"
;;
"all"|*)
cd src/praisonai && python tests/test_runner.py --pattern all || echo "Some tests failed"
;;
esac
- name: Generate Comprehensive Test Report
if: always()
env:
INPUT_TEST_TYPE: ${{ github.event.inputs.test_type || 'all' }}
EVENT_NAME: ${{ github.event_name }}
run: |
echo "# 📋 Comprehensive Test Report" > comprehensive_report.md
echo "" >> comprehensive_report.md
echo "**Python Version:** ${{ matrix.python-version }}" >> comprehensive_report.md
echo "**Test Type:** $INPUT_TEST_TYPE" >> comprehensive_report.md
echo "**Trigger:** $EVENT_NAME" >> comprehensive_report.md
echo "**Status:** 🟢 All Tests Passed" >> comprehensive_report.md
echo "" >> comprehensive_report.md
echo "## 🧪 Test Categories Covered:" >> comprehensive_report.md
echo "" >> comprehensive_report.md
echo "### Unit Tests:" >> comprehensive_report.md
echo "- ✅ Core agent functionality" >> comprehensive_report.md
echo "- ✅ Async operations" >> comprehensive_report.md
echo "- ✅ Tool integrations" >> comprehensive_report.md
echo "- ✅ UI components" >> comprehensive_report.md
echo "" >> comprehensive_report.md
echo "### Integration Tests:" >> comprehensive_report.md
echo "- ✅ MCP (Model Context Protocol)" >> comprehensive_report.md
echo "- ✅ RAG (Retrieval Augmented Generation)" >> comprehensive_report.md
echo "- ✅ Base URL API mapping" >> comprehensive_report.md
echo "- ✅ Multi-agent workflows" >> comprehensive_report.md
echo "- ✅ AutoGen framework integration" >> comprehensive_report.md
echo "- ✅ CrewAI framework integration" >> comprehensive_report.md
echo "- 💬 LLM integrations (OpenAI, Anthropic, etc.)" >> comprehensive_report.md
echo "- 🖥️ UI frameworks (Gradio, Streamlit)" >> comprehensive_report.md
echo "- 📊 Memory and persistence" >> comprehensive_report.md
echo "- 🌐 Multi-modal capabilities" >> comprehensive_report.md
echo "- ✅ AutoGen framework integration" >> comprehensive_report.md
echo "- ✅ CrewAI framework integration" >> comprehensive_report.md
echo "" >> comprehensive_report.md
echo "### Key Features Tested:" >> comprehensive_report.md
echo "- 🤖 Agent creation and configuration" >> comprehensive_report.md
echo "- 📋 Task management and execution" >> comprehensive_report.md
echo "- 🔄 Sync/async workflows" >> comprehensive_report.md
echo "- 🛠️ Custom tools and error handling" >> comprehensive_report.md
echo "- 🧠 Knowledge bases and RAG" >> comprehensive_report.md
echo "- 🔌 MCP server connections" >> comprehensive_report.md
echo "- 💬 LLM integrations (OpenAI, Anthropic, etc.)" >> comprehensive_report.md
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
file: src/praisonai/coverage.xml
flags: comprehensive-tests
name: comprehensive-tests-coverage
fail_ci_if_error: false
verbose: true
- name: Upload Comprehensive Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: comprehensive-test-results-python-${{ matrix.python-version }}
path: |
comprehensive_report.md
src/praisonai/htmlcov/
src/praisonai/coverage.xml
src/praisonai/.coverage
retention-days: 30
test-matrix-summary:
runs-on: ubuntu-latest
needs: comprehensive-test
if: always()
steps:
- name: Generate Matrix Summary
run: |
echo "# 🎯 Test Matrix Summary" > matrix_summary.md
echo "" >> matrix_summary.md
echo "## Python Version Results:" >> matrix_summary.md
echo "- Python 3.10: ${{ needs.comprehensive-test.result }}" >> matrix_summary.md
echo "- Python 3.11: ${{ needs.comprehensive-test.result }}" >> matrix_summary.md
echo "" >> matrix_summary.md
echo "## Overall Status:" >> matrix_summary.md
if [ "${{ needs.comprehensive-test.result }}" == "success" ]; then
echo "✅ **All tests passed across all Python versions!**" >> matrix_summary.md
else
echo "❌ **Some tests failed. Check individual job logs for details.**" >> matrix_summary.md
fi
- name: Upload Matrix Summary
uses: actions/upload-artifact@v4
with:
name: test-matrix-summary
path: matrix_summary.md
retention-days: 30
-74
View File
@@ -1,74 +0,0 @@
name: Core Tests
on:
push:
branches: [ main, develop ]
paths:
- 'src/**'
- 'pyproject.toml'
- '.github/workflows/test-core.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'src/**'
- 'pyproject.toml'
- '.github/workflows/test-core.yml'
workflow_dispatch:
jobs:
test-core:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install UV
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: |
cd src/praisonai
uv pip install --system ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
uv pip install --system duckduckgo_search
uv pip install --system pytest pytest-asyncio pytest-cov pytest-timeout
uv pip install --system "praisonaiagents[knowledge]"
- name: Set environment variables
run: |
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY || 'sk-test-key-for-github-actions-testing-only-not-real' }}" >> $GITHUB_ENV
echo "OPENAI_API_BASE=${{ secrets.OPENAI_API_BASE || 'https://api.openai.com/v1' }}" >> $GITHUB_ENV
echo "OPENAI_MODEL_NAME=${{ secrets.OPENAI_MODEL_NAME || 'gpt-5-nano' }}" >> $GITHUB_ENV
echo "PYTHONPATH=${{ github.workspace }}/src/praisonai-agents:$PYTHONPATH" >> $GITHUB_ENV
- name: Run Unit Tests
run: |
cd src/praisonai && python -m pytest tests/unit/ -v --tb=short --disable-warnings \
--cov=praisonai --cov-report=xml --cov-branch \
--timeout=60 || echo "Some unit tests failed"
continue-on-error: true
- name: Run Integration Tests
run: |
cd src/praisonai && python -m pytest tests/integration/ -v --tb=short --disable-warnings \
--timeout=60 \
--ignore=tests/integration/test_tracker_complex_tasks.py \
--ignore=tests/integration/test_serve_integration.py \
|| echo "Some integration tests failed"
- name: Upload coverage to Codecov
if: always()
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN || '' }}
files: src/praisonai/coverage.xml
flags: core-tests
fail_ci_if_error: false
-174
View File
@@ -1,174 +0,0 @@
name: Extended Tests
on:
schedule:
- cron: '0 2 * * *'
workflow_dispatch:
jobs:
test-examples:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Install UV
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: |
cd src/praisonai
uv pip install --system ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
uv pip install --system duckduckgo_search
# Install knowledge dependencies from praisonai-agents
uv pip install --system "praisonaiagents[knowledge]"
- name: Set environment variables
run: |
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY || 'sk-test-key-for-github-actions-testing-only-not-real' }}" >> $GITHUB_ENV
echo "OPENAI_API_BASE=${{ secrets.OPENAI_API_BASE || 'https://api.openai.com/v1' }}" >> $GITHUB_ENV
echo "OPENAI_MODEL_NAME=${{ secrets.OPENAI_MODEL_NAME || 'gpt-5-nano' }}" >> $GITHUB_ENV
echo "PYTHONPATH=${{ github.workspace }}/src/praisonai-agents:$PYTHONPATH" >> $GITHUB_ENV
echo "PRAISONAI_TELEMETRY_DISABLED=true" >> $GITHUB_ENV
echo "PRAISONAI_DISABLE_TELEMETRY=true" >> $GITHUB_ENV
echo "DO_NOT_TRACK=true" >> $GITHUB_ENV
- name: Test Key Example Scripts
run: |
echo "🧪 Testing key example scripts from praisonai-agents..."
# Create a timeout function for consistent handling
timeout_run() {
timeout 30s "$@" || echo "⏱️ $1 test completed/timed out"
}
# Test basic agent functionality
timeout_run python ${{ github.workspace }}/src/praisonai-agents/basic-agents.py
# Test async functionality
timeout_run python ${{ github.workspace }}/src/praisonai-agents/async_example.py
# Test knowledge/RAG functionality
timeout_run python ${{ github.workspace }}/src/praisonai-agents/knowledge-agents.py
# Test MCP functionality
timeout_run python ${{ github.workspace }}/src/praisonai-agents/mcp-basic.py
# Test UI functionality
timeout_run python ${{ github.workspace }}/src/praisonai-agents/ui.py
echo "✅ Example script testing completed"
continue-on-error: true
performance-test:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: 3.11
- name: Install UV
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: |
cd src/praisonai
uv pip install --system ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
uv pip install --system pytest pytest-benchmark
# Install knowledge dependencies from praisonai-agents
uv pip install --system "praisonaiagents[knowledge]"
- name: Set environment variables
run: |
echo "PRAISONAI_TELEMETRY_DISABLED=true" >> $GITHUB_ENV
echo "PRAISONAI_DISABLE_TELEMETRY=true" >> $GITHUB_ENV
echo "DO_NOT_TRACK=true" >> $GITHUB_ENV
- name: Run Performance Benchmarks
run: |
echo "🏃 Running performance benchmarks..."
python -c "
import time
import sys
import statistics
sys.path.insert(0, 'src/praisonai')
print('🏃 Testing agent creation performance...')
times = []
try:
from praisonaiagents import Agent
for i in range(5):
start_time = time.time()
agent = Agent(name=f'PerfAgent{i}')
times.append(time.time() - start_time)
avg_time = statistics.mean(times)
print(f'✅ Average agent creation time: {avg_time:.3f}s')
print(f'📊 Min: {min(times):.3f}s, Max: {max(times):.3f}s')
except Exception as e:
print(f'❌ Agent creation benchmark failed: {e}')
print('🏃 Testing import performance...')
start_time = time.time()
try:
import praisonaiagents
import_time = time.time() - start_time
print(f'✅ Import completed in {import_time:.3f}s')
except Exception as e:
print(f'❌ Import benchmark failed: {e}')
print('🏃 Testing memory usage...')
try:
import psutil
import os
process = psutil.Process(os.getpid())
memory_mb = process.memory_info().rss / 1024 / 1024
print(f'📊 Memory usage: {memory_mb:.1f} MB')
except ImportError:
print('⚠️ psutil not available for memory testing')
except Exception as e:
print(f'❌ Memory benchmark failed: {e}')
"
continue-on-error: true
- name: Generate Performance Report
run: |
echo "## 📊 Performance Test Results" > performance_report.md
echo "" >> performance_report.md
echo "### Benchmarks Run:" >> performance_report.md
echo "- ⚡ Agent creation speed" >> performance_report.md
echo "- 📦 Import performance" >> performance_report.md
echo "- 💾 Memory usage" >> performance_report.md
echo "- 🧪 Example script execution" >> performance_report.md
echo "" >> performance_report.md
echo "_Performance results are logged in the CI output above._" >> performance_report.md
- name: Upload Performance Report
uses: actions/upload-artifact@v4
with:
name: performance-report
path: performance_report.md
retention-days: 5
-156
View File
@@ -1,156 +0,0 @@
name: Framework Integration Tests
on:
workflow_dispatch: # Allow manual triggering
inputs:
framework:
description: 'Framework to test'
required: true
default: 'all'
type: choice
options:
- all
- autogen
- crewai
schedule:
# Run framework tests daily at 6 AM UTC
- cron: '0 6 * * *'
push:
paths:
- 'tests/integration/autogen/**'
- 'tests/integration/crewai/**'
- '.github/workflows/test-frameworks.yml'
jobs:
framework-tests:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
python-version: [3.11]
framework: [autogen, crewai]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install UV
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: |
cd src/praisonai
uv pip install --system ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
uv pip install --system duckduckgo_search
uv pip install --system pytest pytest-asyncio pytest-cov pytest-timeout
- name: Set environment variables
run: |
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY || 'sk-test-key-for-github-actions-testing-only-not-real' }}" >> $GITHUB_ENV
echo "OPENAI_API_BASE=${{ secrets.OPENAI_API_BASE || 'https://api.openai.com/v1' }}" >> $GITHUB_ENV
echo "OPENAI_MODEL_NAME=${{ secrets.OPENAI_MODEL_NAME || 'gpt-5-nano' }}" >> $GITHUB_ENV
- name: Test ${{ matrix.framework }} Framework
run: |
echo "🧪 Testing ${{ matrix.framework }} framework integration with Python ${{ matrix.python-version }}"
cd src/praisonai && python tests/test_runner.py --pattern ${{ matrix.framework }} --verbose --coverage
continue-on-error: false
- name: Generate Framework Test Report
if: always()
run: |
echo "# 🤖 ${{ matrix.framework }} Framework Test Report" > ${{ matrix.framework }}_report.md
echo "" >> ${{ matrix.framework }}_report.md
echo "**Framework:** ${{ matrix.framework }}" >> ${{ matrix.framework }}_report.md
echo "**Python Version:** ${{ matrix.python-version }}" >> ${{ matrix.framework }}_report.md
echo "**Date:** $(date -u)" >> ${{ matrix.framework }}_report.md
echo "**Trigger:** ${{ github.event_name }}" >> ${{ matrix.framework }}_report.md
echo "" >> ${{ matrix.framework }}_report.md
if [ "${{ matrix.framework }}" == "autogen" ]; then
echo "## AutoGen Integration Tests" >> ${{ matrix.framework }}_report.md
echo "- ✅ AutoGen import verification" >> ${{ matrix.framework }}_report.md
echo "- ✅ Basic agent creation through PraisonAI" >> ${{ matrix.framework }}_report.md
echo "- ✅ Conversation flow testing" >> ${{ matrix.framework }}_report.md
echo "- ✅ Configuration validation" >> ${{ matrix.framework }}_report.md
elif [ "${{ matrix.framework }}" == "crewai" ]; then
echo "## CrewAI Integration Tests" >> ${{ matrix.framework }}_report.md
echo "- ✅ CrewAI import verification" >> ${{ matrix.framework }}_report.md
echo "- ✅ Basic crew creation through PraisonAI" >> ${{ matrix.framework }}_report.md
echo "- ✅ Multi-agent workflow testing" >> ${{ matrix.framework }}_report.md
echo "- ✅ Agent collaboration verification" >> ${{ matrix.framework }}_report.md
echo "- ✅ Configuration validation" >> ${{ matrix.framework }}_report.md
fi
echo "" >> ${{ matrix.framework }}_report.md
echo "## Test Commands" >> ${{ matrix.framework }}_report.md
echo '```bash' >> ${{ matrix.framework }}_report.md
echo "# Run ${{ matrix.framework }} tests locally:" >> ${{ matrix.framework }}_report.md
echo "python tests/test_runner.py --pattern ${{ matrix.framework }} --verbose" >> ${{ matrix.framework }}_report.md
echo '```' >> ${{ matrix.framework }}_report.md
- name: Upload Framework Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: ${{ matrix.framework }}-test-results-python-${{ matrix.python-version }}
path: |
${{ matrix.framework }}_report.md
htmlcov/
coverage.xml
.coverage
retention-days: 14
framework-summary:
runs-on: ubuntu-latest
needs: framework-tests
if: always()
steps:
- name: Generate Framework Summary
run: |
echo "# 🚀 Framework Integration Test Summary" > framework_summary.md
echo "" >> framework_summary.md
echo "## Test Results by Framework and Python Version:" >> framework_summary.md
echo "" >> framework_summary.md
echo "### AutoGen Framework:" >> framework_summary.md
echo "- Python 3.11: ${{ needs.framework-tests.result }}" >> framework_summary.md
echo "" >> framework_summary.md
echo "### CrewAI Framework:" >> framework_summary.md
echo "- Python 3.11: ${{ needs.framework-tests.result }}" >> framework_summary.md
echo "" >> framework_summary.md
echo "## Overall Status:" >> framework_summary.md
if [ "${{ needs.framework-tests.result }}" == "success" ]; then
echo "✅ **All framework integration tests passed!**" >> framework_summary.md
else
echo "❌ **Some framework tests failed. Check individual job logs for details.**" >> framework_summary.md
fi
echo "" >> framework_summary.md
echo "## Frameworks Tested:" >> framework_summary.md
echo "- **AutoGen**: Microsoft's conversational AI framework" >> framework_summary.md
echo "- **CrewAI**: Multi-agent collaboration framework" >> framework_summary.md
echo "" >> framework_summary.md
echo "## Test Coverage:" >> framework_summary.md
echo "- Import verification" >> framework_summary.md
echo "- Agent/crew creation" >> framework_summary.md
echo "- Workflow execution" >> framework_summary.md
echo "- Configuration validation" >> framework_summary.md
echo "- Integration with PraisonAI" >> framework_summary.md
- name: Upload Framework Summary
uses: actions/upload-artifact@v4
with:
name: framework-test-summary
path: framework_summary.md
retention-days: 30
-560
View File
@@ -1,560 +0,0 @@
name: Optimized Test Suite
on:
push:
branches: [ main, develop ]
paths:
- 'src/**'
- 'pyproject.toml'
- '.github/workflows/test-optimized.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'src/**'
- 'pyproject.toml'
- '.github/workflows/test-optimized.yml'
schedule:
# Nightly extended tests at 3 AM UTC
- cron: '0 3 * * *'
workflow_dispatch:
inputs:
tier:
description: 'Test tier to run'
required: true
default: 'main'
type: choice
options:
- smoke
- main
- extended
- nightly
env:
PYTHONPATH: ${{ github.workspace }}/src/praisonai-agents
# Test gating environment variables
PRAISONAI_ALLOW_NETWORK: '0'
PRAISONAI_LIVE_TESTS: '0'
jobs:
# ============================================
# SMOKE TESTS - Fast unit tests, no network
# Target: <= 2 minutes
# ============================================
smoke:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-smoke-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-smoke-
${{ runner.os }}-pip-
- name: Install minimal dependencies
run: |
pip install --upgrade pip
# Install praisonai-agents with all deps (required for tests)
cd src/praisonai-agents
pip install -e ".[all]"
cd ../praisonai
pip install -e ".[chat,code]"
pip install pytest pytest-asyncio pytest-timeout prompt_toolkit
- name: Run Smoke Tests
env:
PRAISONAI_TEST_TIER: smoke
PRAISONAI_ALLOW_NETWORK: '0'
run: |
cd src/praisonai
# Run only CLI tests for smoke - fast and critical path
python -m pytest tests/unit/cli/ \
-m "not slow and not network" \
-q --tb=line \
--timeout=30 \
--ignore=tests/fixtures \
--ignore=tests/unit/cli/test_message_queue.py \
--maxfail=5 \
|| echo "Some smoke tests failed"
- name: Smoke Test Summary
if: always()
run: echo "✅ Smoke tests completed"
# ============================================
# MAIN TESTS - Unit + safe integration
# Target: <= 5 minutes
# ============================================
main:
needs: smoke
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
matrix:
python-version: ['3.11']
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Cache pip packages
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-main-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-main-
${{ runner.os }}-pip-
- name: Install dependencies
run: |
pip install --upgrade pip
# Install praisonai-agents with all deps (required for tests)
cd src/praisonai-agents
pip install -e ".[all]"
cd ../praisonai
pip install -e ".[crewai,autogen]"
pip install pytest pytest-asyncio pytest-timeout pytest-cov pytest-xdist
- name: Run Main Tests
env:
PRAISONAI_TEST_TIER: main
PRAISONAI_ALLOW_NETWORK: '0'
OPENAI_API_KEY: 'test-key'
ANTHROPIC_API_KEY: 'test-key'
GOOGLE_API_KEY: 'test-key'
run: |
cd src/praisonai
python -m pytest tests/unit/ tests/integration/ \
-m "not provider_anthropic and not provider_google and not provider_ollama and not provider_grok_xai and not provider_groq and not provider_cohere" \
-q --tb=line \
--timeout=60 \
--ignore=tests/fixtures \
--ignore=tests/e2e \
--ignore=tests/live \
--ignore=tests/unit/jobs \
--ignore=tests/unit/api \
--cov=praisonai \
--cov-report=xml \
-n 4 \
--maxfail=10 \
|| echo "Some tests failed"
- name: Upload coverage
if: matrix.python-version == '3.11'
uses: codecov/codecov-action@v5
with:
token: ${{ secrets.CODECOV_TOKEN || '' }}
files: src/praisonai/coverage.xml
flags: main-tests
fail_ci_if_error: false
# ============================================
# OPENAI LIVE TESTS - Real OpenAI API calls
# Only runs if OPENAI_API_KEY secret is set
# Safe for forks (skips gracefully if no key)
# ============================================
openai-live:
needs: smoke
runs-on: ubuntu-latest
timeout-minutes: 8
# This job always runs but steps are conditional on secret availability
steps:
- name: Check if OpenAI key is available
id: check-key
run: |
if [ -n "${{ secrets.OPENAI_API_KEY }}" ]; then
echo "has_key=true" >> $GITHUB_OUTPUT
else
echo "has_key=false" >> $GITHUB_OUTPUT
echo "⚠️ OPENAI_API_KEY not set - skipping live tests"
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
if: steps.check-key.outputs.has_key == 'true'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Cache pip packages
if: steps.check-key.outputs.has_key == 'true'
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-openai-${{ hashFiles('**/pyproject.toml') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Install dependencies
if: steps.check-key.outputs.has_key == 'true'
run: |
pip install --upgrade pip
cd src/praisonai-agents
pip install -e ".[all]"
cd ../praisonai
pip install -e ".[openai]"
pip install pytest pytest-asyncio pytest-timeout
- name: Run OpenAI Live Tests
if: steps.check-key.outputs.has_key == 'true'
env:
PRAISONAI_TEST_TIER: extended
PRAISONAI_ALLOW_NETWORK: '1'
PRAISONAI_LIVE_TESTS: '1'
PRAISONAI_TEST_PROVIDERS: 'openai'
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_MODEL_NAME: ${{ secrets.OPENAI_MODEL_NAME || 'gpt-4o-mini' }}
run: |
cd src/praisonai
python -m pytest tests/ \
-m "provider_openai or real" \
-v --tb=short \
--timeout=120 \
--ignore=tests/fixtures \
|| echo "Some OpenAI tests failed"
- name: Skip notice
if: steps.check-key.outputs.has_key == 'false'
run: echo "✅ OpenAI live tests skipped (no API key available - this is expected for forks)"
# ============================================
# EXTENDED PROVIDER TESTS - Nightly only
# Runs all provider-specific tests
# ============================================
extended-anthropic:
if: github.event_name == 'schedule' || github.event.inputs.tier == 'extended' || github.event.inputs.tier == 'nightly'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check if Anthropic key is available
id: check-key
run: |
if [ -n "${{ secrets.ANTHROPIC_API_KEY }}" ]; then
echo "has_key=true" >> $GITHUB_OUTPUT
else
echo "has_key=false" >> $GITHUB_OUTPUT
echo "⚠️ ANTHROPIC_API_KEY not set - skipping tests"
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
if: steps.check-key.outputs.has_key == 'true'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
if: steps.check-key.outputs.has_key == 'true'
run: |
pip install --upgrade pip
cd src/praisonai-agents
pip install -e ".[all]"
cd ../praisonai
pip install -e ".[anthropic]"
pip install pytest pytest-asyncio pytest-timeout
- name: Run Anthropic Tests
if: steps.check-key.outputs.has_key == 'true'
env:
PRAISONAI_ALLOW_NETWORK: '1'
PRAISONAI_LIVE_TESTS: '1'
PRAISONAI_TEST_PROVIDERS: 'anthropic'
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
run: |
cd src/praisonai
python -m pytest tests/ \
-m "provider_anthropic" \
-v --tb=short \
--timeout=120 \
--ignore=tests/fixtures \
|| echo "Anthropic tests completed"
- name: Skip notice
if: steps.check-key.outputs.has_key == 'false'
run: echo "✅ Anthropic tests skipped (no API key available)"
extended-google:
if: github.event_name == 'schedule' || github.event.inputs.tier == 'extended' || github.event.inputs.tier == 'nightly'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check if Google key is available
id: check-key
env:
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
run: |
if [ -n "$GOOGLE_API_KEY" ]; then
echo "has_key=true" >> $GITHUB_OUTPUT
else
echo "has_key=false" >> $GITHUB_OUTPUT
echo "⚠️ GOOGLE_API_KEY not set - skipping tests"
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
if: steps.check-key.outputs.has_key == 'true'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
if: steps.check-key.outputs.has_key == 'true'
run: |
pip install --upgrade pip
cd src/praisonai-agents
pip install -e ".[all]"
cd ../praisonai
pip install -e ".[google]"
pip install pytest pytest-asyncio pytest-timeout
- name: Run Google/Gemini Tests
if: steps.check-key.outputs.has_key == 'true'
env:
PRAISONAI_ALLOW_NETWORK: '1'
PRAISONAI_LIVE_TESTS: '1'
PRAISONAI_TEST_PROVIDERS: 'google'
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
run: |
cd src/praisonai
python -m pytest tests/ \
-m "provider_google" \
-v --tb=short \
--timeout=120 \
--ignore=tests/fixtures \
|| echo "Google tests completed"
- name: Skip notice
if: steps.check-key.outputs.has_key == 'false'
run: echo "✅ Google tests skipped (no API key available)"
extended-groq:
if: github.event_name == 'schedule' || github.event.inputs.tier == 'extended' || github.event.inputs.tier == 'nightly'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check if Groq key is available
id: check-key
env:
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
run: |
if [ -n "$GROQ_API_KEY" ]; then
echo "has_key=true" >> $GITHUB_OUTPUT
else
echo "has_key=false" >> $GITHUB_OUTPUT
echo "⚠️ GROQ_API_KEY not set - skipping tests"
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
if: steps.check-key.outputs.has_key == 'true'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
if: steps.check-key.outputs.has_key == 'true'
run: |
pip install --upgrade pip
cd src/praisonai-agents
pip install -e ".[all]"
cd ../praisonai
pip install -e .
pip install pytest pytest-asyncio pytest-timeout
- name: Run Groq Tests
if: steps.check-key.outputs.has_key == 'true'
env:
PRAISONAI_ALLOW_NETWORK: '1'
PRAISONAI_LIVE_TESTS: '1'
PRAISONAI_TEST_PROVIDERS: 'groq'
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
run: |
cd src/praisonai
python -m pytest tests/ \
-m "provider_groq" \
-v --tb=short \
--timeout=120 \
--ignore=tests/fixtures \
|| echo "Groq tests completed"
- name: Skip notice
if: steps.check-key.outputs.has_key == 'false'
run: echo "✅ Groq tests skipped (no API key available)"
extended-xai:
if: github.event_name == 'schedule' || github.event.inputs.tier == 'extended' || github.event.inputs.tier == 'nightly'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check if xAI key is available
id: check-key
env:
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
run: |
if [ -n "$XAI_API_KEY" ]; then
echo "has_key=true" >> $GITHUB_OUTPUT
else
echo "has_key=false" >> $GITHUB_OUTPUT
echo "⚠️ XAI_API_KEY not set - skipping tests"
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
if: steps.check-key.outputs.has_key == 'true'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
if: steps.check-key.outputs.has_key == 'true'
run: |
pip install --upgrade pip
cd src/praisonai-agents
pip install -e ".[all]"
cd ../praisonai
pip install -e .
pip install pytest pytest-asyncio pytest-timeout
- name: Run xAI/Grok Tests
if: steps.check-key.outputs.has_key == 'true'
env:
PRAISONAI_ALLOW_NETWORK: '1'
PRAISONAI_LIVE_TESTS: '1'
PRAISONAI_TEST_PROVIDERS: 'grok_xai'
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
run: |
cd src/praisonai
python -m pytest tests/ \
-m "provider_grok_xai" \
-v --tb=short \
--timeout=120 \
--ignore=tests/fixtures \
|| echo "xAI tests completed"
- name: Skip notice
if: steps.check-key.outputs.has_key == 'false'
run: echo "✅ xAI tests skipped (no API key available)"
extended-cohere:
if: github.event_name == 'schedule' || github.event.inputs.tier == 'extended' || github.event.inputs.tier == 'nightly'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check if Cohere key is available
id: check-key
env:
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
run: |
if [ -n "$COHERE_API_KEY" ]; then
echo "has_key=true" >> $GITHUB_OUTPUT
else
echo "has_key=false" >> $GITHUB_OUTPUT
echo "⚠️ COHERE_API_KEY not set - skipping tests"
fi
- uses: actions/checkout@v4
with:
persist-credentials: false
if: steps.check-key.outputs.has_key == 'true'
- name: Set up Python 3.11
if: steps.check-key.outputs.has_key == 'true'
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
if: steps.check-key.outputs.has_key == 'true'
run: |
pip install --upgrade pip
cd src/praisonai-agents
pip install -e ".[all]"
cd ../praisonai
pip install -e ".[cohere]"
pip install pytest pytest-asyncio pytest-timeout
- name: Run Cohere Tests
if: steps.check-key.outputs.has_key == 'true'
env:
PRAISONAI_ALLOW_NETWORK: '1'
PRAISONAI_LIVE_TESTS: '1'
PRAISONAI_TEST_PROVIDERS: 'cohere'
COHERE_API_KEY: ${{ secrets.COHERE_API_KEY }}
run: |
cd src/praisonai
python -m pytest tests/ \
-m "provider_cohere" \
-v --tb=short \
--timeout=120 \
--ignore=tests/fixtures \
|| echo "Cohere tests completed"
- name: Skip notice
if: steps.check-key.outputs.has_key == 'false'
run: echo "✅ Cohere tests skipped (no API key available)"
# ============================================
# TEST SUMMARY
# ============================================
test-summary:
needs: [smoke, main]
if: always()
runs-on: ubuntu-latest
steps:
- name: Test Summary
run: |
echo "# 📊 Test Suite Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Job | Status |" >> $GITHUB_STEP_SUMMARY
echo "|-----|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Smoke | ${{ needs.smoke.result }} |" >> $GITHUB_STEP_SUMMARY
echo "| Main | ${{ needs.main.result }} |" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "## Test Tiers" >> $GITHUB_STEP_SUMMARY
echo "- **smoke**: Fast unit tests, no network (≤2 min)" >> $GITHUB_STEP_SUMMARY
echo "- **main**: Unit + integration, no network (≤5 min)" >> $GITHUB_STEP_SUMMARY
echo "- **openai-live**: OpenAI API tests (when key available)" >> $GITHUB_STEP_SUMMARY
echo "- **extended-***: Provider-specific tests (nightly)" >> $GITHUB_STEP_SUMMARY
-178
View File
@@ -1,178 +0,0 @@
name: Real End-to-End Tests
# ⚠️ WARNING: This workflow makes real API calls and incurs costs!
# Only runs when manually triggered to prevent accidental charges
on:
workflow_dispatch: # Manual trigger only
inputs:
framework:
description: 'Framework to test (⚠️ Will incur API costs!)'
required: true
default: 'none'
type: choice
options:
- none
- autogen
- crewai
- all
confirm_costs:
description: 'I understand this will make real API calls and may incur costs'
required: true
type: boolean
default: false
jobs:
real-tests:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.confirm_costs == 'true' && github.event.inputs.framework != 'none' }}
timeout-minutes: 15
strategy:
matrix:
python-version: [3.11] # Single version to minimize costs
steps:
- name: 🚨 Cost Warning
env:
INPUT_FRAMEWORK: ${{ github.event.inputs.framework }}
INPUT_CONFIRM_COSTS: ${{ github.event.inputs.confirm_costs }}
run: |
echo "⚠️ WARNING: This workflow will make real API calls!"
echo "💰 This may incur charges on your API accounts"
echo "🎯 Framework: $INPUT_FRAMEWORK"
echo "✅ Cost confirmation: $INPUT_CONFIRM_COSTS"
- name: Checkout code
uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install UV
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> $GITHUB_PATH
- name: Install dependencies
run: |
cd src/praisonai
uv pip install --system ."[ui,gradio,api,agentops,google,openai,anthropic,cohere,chat,code,realtime,call,crewai,autogen]"
uv pip install --system pytest pytest-asyncio pytest-cov
# Install knowledge dependencies from praisonai-agents
uv pip install --system "praisonaiagents[knowledge]"
- name: Set environment variables
run: |
echo "OPENAI_API_KEY=${{ secrets.OPENAI_API_KEY }}" >> $GITHUB_ENV
echo "ANTHROPIC_API_KEY=${{ secrets.ANTHROPIC_API_KEY }}" >> $GITHUB_ENV
echo "GOOGLE_API_KEY=${{ secrets.GOOGLE_API_KEY }}" >> $GITHUB_ENV
- name: Verify API Keys
run: |
if [ -z "${{ secrets.OPENAI_API_KEY }}" ]; then
echo "❌ OPENAI_API_KEY not set in secrets"
echo "🔧 Add your API key to repository secrets"
exit 1
fi
echo "✅ API keys configured"
- name: Run Real AutoGen Tests
if: ${{ github.event.inputs.framework == 'autogen' || github.event.inputs.framework == 'all' }}
run: |
echo "🤖 Running REAL AutoGen tests (⚠️ API costs may apply)"
cd src/praisonai && python -m pytest tests/e2e/autogen/ -v -m real --tb=short
continue-on-error: false
- name: Run Real CrewAI Tests
if: ${{ github.event.inputs.framework == 'crewai' || github.event.inputs.framework == 'all' }}
run: |
echo "⛵ Running REAL CrewAI tests (⚠️ API costs may apply)"
cd src/praisonai && python -m pytest tests/e2e/crewai/ -v -m real --tb=short
continue-on-error: false
- name: Generate Real Test Report
if: always()
env:
INPUT_FRAMEWORK: ${{ github.event.inputs.framework }}
run: |
echo "# 🔥 Real End-to-End Test Report" > real_test_report.md
echo "" >> real_test_report.md
echo "⚠️ **WARNING: This report represents tests that made real API calls**" >> real_test_report.md
echo "" >> real_test_report.md
echo "**Framework Tested:** $INPUT_FRAMEWORK" >> real_test_report.md
echo "**Python Version:** ${{ matrix.python-version }}" >> real_test_report.md
echo "**Date:** $(date -u)" >> real_test_report.md
echo "**Triggered by:** $GITHUB_ACTOR" >> real_test_report.md
echo "" >> real_test_report.md
echo "## 🧪 Test Results" >> real_test_report.md
echo "" >> real_test_report.md
if [ "$INPUT_FRAMEWORK" == "autogen" ] || [ "$INPUT_FRAMEWORK" == "all" ]; then
echo "### AutoGen Real Tests:" >> real_test_report.md
echo "- Environment verification" >> real_test_report.md
echo "- Agent creation with real API calls" >> real_test_report.md
echo "- Configuration validation" >> real_test_report.md
echo "" >> real_test_report.md
fi
if [ "$INPUT_FRAMEWORK" == "crewai" ] || [ "$INPUT_FRAMEWORK" == "all" ]; then
echo "### CrewAI Real Tests:" >> real_test_report.md
echo "- Environment verification" >> real_test_report.md
echo "- Crew creation with real API calls" >> real_test_report.md
echo "- Multi-agent setup validation" >> real_test_report.md
echo "" >> real_test_report.md
fi
echo "## 💰 Cost Considerations" >> real_test_report.md
echo "- These tests made actual API calls to LLM providers" >> real_test_report.md
echo "- Costs depend on your API pricing tier" >> real_test_report.md
echo "- Tests are designed to be minimal to reduce costs" >> real_test_report.md
echo "- Check your API provider dashboard for actual usage" >> real_test_report.md
echo "## 📋 Next Steps" >> real_test_report.md
echo "- Review test results for any failures" >> real_test_report.md
echo "- Check API usage in your provider dashboard" >> real_test_report.md
echo "- Use mock tests (tests/integration/) for routine testing" >> real_test_report.md
- name: Upload Real Test Results
uses: actions/upload-artifact@v4
if: always()
with:
name: real-test-results-${{ github.event.inputs.framework }}-python-${{ matrix.python-version }}
path: |
real_test_report.md
retention-days: 30
# Safety job that runs when costs not confirmed
safety-check:
runs-on: ubuntu-latest
if: ${{ github.event.inputs.confirm_costs != 'true' || github.event.inputs.framework == 'none' }}
steps:
- name: 🛡️ Safety Check Failed
env:
INPUT_FRAMEWORK: ${{ github.event.inputs.framework }}
INPUT_CONFIRM_COSTS: ${{ github.event.inputs.confirm_costs }}
run: |
echo "🚨 Real tests not executed due to safety checks:"
echo ""
echo "✅ Costs confirmed: $INPUT_CONFIRM_COSTS"
echo "✅ Framework selected: $INPUT_FRAMEWORK"
echo ""
echo "To run real tests:"
echo "1. Select a framework (autogen, crewai, or all)"
echo "2. Check 'I understand this will make real API calls and may incur costs'"
echo "3. Ensure API keys are set in repository secrets"
echo ""
echo "💡 For cost-free testing, use mock tests instead:"
echo " - Run 'python -m pytest tests/integration/' locally"
echo " - Or trigger other workflows that use mock tests"
exit 1
-218
View File
@@ -1,218 +0,0 @@
name: Windows Tests
on:
push:
branches: [main, develop]
paths:
- 'src/**'
- 'pyproject.toml'
- '.github/workflows/test-windows.yml'
pull_request:
branches: [main, develop]
paths:
- 'src/**'
- 'pyproject.toml'
- '.github/workflows/test-windows.yml'
workflow_dispatch:
jobs:
test-windows:
runs-on: windows-latest
timeout-minutes: 5
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_MODEL_NAME: gpt-4o-mini
PYTHONUTF8: 1
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python 3.11
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install praisonaiagents
run: |
python -m pip install --upgrade pip
cd src/praisonai-agents
pip install -e .
shell: bash
- name: Install praisonai wrapper
run: |
cd src/praisonai
pip install -e .
shell: bash
# ==================== CORE IMPORTS ====================
- name: Test core imports
run: |
python -c "from praisonaiagents import Agent, Agents, Workflow; print('Core imports OK')"
python -c "from praisonaiagents import tool, Tools; print('Tool imports OK')"
python -c "from praisonaiagents import Task; print('Task imports OK')"
# ==================== AGENT FEATURES ====================
- name: Test Agent instantiation
run: |
python -c "
from praisonaiagents import Agent
a = Agent(name='Test', instructions='You are helpful')
print('Agent created:', a.name)
a2 = Agent(name='ToolAgent', instructions='Use tools', tools=[])
print('Agent with tools:', a2.name)
"
- name: Test Agents orchestration
run: |
python -c "
from praisonaiagents import Agent, Agents, Task
a1 = Agent(name='Agent1', instructions='First agent')
a2 = Agent(name='Agent2', instructions='Second agent')
t = Task(description='Do something', agent=a1)
agents = Agents(agents=[a1, a2], tasks=[t])
print('Agents:', len(agents.agents), 'Tasks:', len(agents.tasks))
"
# ==================== WORKFLOW FEATURES ====================
- name: Test Workflow
run: |
python -c "
from praisonaiagents import Workflow
w = Workflow(name='test')
print('Workflow:', w.name)
"
# ==================== MEMORY FEATURES ====================
- name: Test Memory classes
run: |
python -c "
from praisonaiagents.memory import FileMemory
from praisonaiagents.config import MemoryConfig
cfg = MemoryConfig(backend='file', user_id='test_user')
print('MemoryConfig backend:', cfg.backend)
mem = FileMemory(user_id='test_user', base_path='.praison/test_memory')
print('FileMemory user:', mem.user_id)
"
# ==================== TOOL DECORATOR ====================
- name: Test tool decorator
run: |
python -c "
from praisonaiagents import tool
@tool
def my_tool(query: str) -> str:
'''A test tool'''
return f'Echo: {query}'
result = my_tool('hello')
print('Tool result:', result)
"
# ==================== CONFIG CLASSES ====================
- name: Test config classes
run: |
python -c "
from praisonaiagents.config import OutputConfig, ExecutionConfig, PlanningConfig
out_cfg = OutputConfig(verbose=True, stream=True)
print('OutputConfig verbose:', out_cfg.verbose)
exec_cfg = ExecutionConfig(max_iter=50, max_retry_limit=5)
print('ExecutionConfig max_iter:', exec_cfg.max_iter)
plan_cfg = PlanningConfig(reasoning=True, auto_approve=False)
print('PlanningConfig reasoning:', plan_cfg.reasoning)
"
# ==================== HANDOFF ====================
- name: Test Handoff
run: |
python -c "
from praisonaiagents import Handoff, handoff, Agent
a1 = Agent(name='Agent1', instructions='First')
a2 = Agent(name='Agent2', instructions='Second')
# Using Handoff class directly
h = Handoff(agent=a2)
print('Handoff tool name:', h.tool_name)
# Using handoff function
h2 = handoff(a1, tool_name_override='custom_handoff')
print('Custom handoff name:', h2.tool_name)
"
# ==================== ASYNC FEATURES ====================
- name: Test async imports
run: |
python -c "
import asyncio
from praisonaiagents import Agent
async def test_async():
agent = Agent(name='AsyncAgent', instructions='Test')
assert hasattr(agent, 'arun'), 'Missing arun'
assert hasattr(agent, 'astart'), 'Missing astart'
print('Async methods available')
asyncio.run(test_async())
"
# ==================== CLI COMMANDS ====================
- name: Test CLI help
run: praisonai --help
- name: Test CLI session list
run: praisonai session list
continue-on-error: true
- name: Test CLI queue list
run: praisonai queue list
continue-on-error: true
# ==================== PACKAGING TESTS ====================
- name: Test packaging/daemon entry points
run: |
echo "Testing both entry point methods..."
python -m praisonai --version
praisonai --version
- name: Test doctor packaging command
run: |
echo "Testing doctor packaging diagnostics..."
praisonai doctor packaging --json
# ==================== REAL API TESTS ====================
- name: Real API test - Agent run
if: env.OPENAI_API_KEY != ''
run: |
python -c "
from praisonaiagents import Agent
agent = Agent(name='TestAgent', instructions='Reply with exactly: WINDOWS_OK', llm='gpt-4o-mini')
result = agent.run('Say WINDOWS_OK')
print('Result:', result)
print('Real API test passed')
"
- name: Real API test - Multi-agent
if: env.OPENAI_API_KEY != ''
run: |
python -c "
from praisonaiagents import Agent, Agents, Task
researcher = Agent(name='Researcher', instructions='You research topics', llm='gpt-4o-mini')
writer = Agent(name='Writer', instructions='You write content', llm='gpt-4o-mini')
task = Task(description='Say hello world', agent=researcher)
agents = Agents(agents=[researcher, writer], tasks=[task])
result = agents.start()
print('Multi-agent result type:', type(result))
print('Multi-agent test passed')
"
-57
View File
@@ -1,57 +0,0 @@
name: Update API Reference
on:
push:
branches: [ "main" ]
paths:
- 'src/praisonai-agents/praisonaiagents/**/*.py'
- 'src/praisonai/praisonai/**/*.py'
- 'src/praisonai-ts/src/**/*.ts'
- '.github/workflows/update-api-md.yml'
permissions:
contents: write
jobs:
update-api-md:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
persist-credentials: true
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Generate api.md
run: |
cd $GITHUB_WORKSPACE
python3 src/praisonai/scripts/generate_api_md.py
- name: Check for changes
id: check_changes
run: |
if git diff --quiet api.md; then
echo "changes=false" >> $GITHUB_OUTPUT
echo "No changes to api.md"
else
echo "changes=true" >> $GITHUB_OUTPUT
echo "api.md has been updated"
git diff api.md | head -50
fi
- name: Commit and push changes
if: steps.check_changes.outputs.changes == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "docs: auto-update api.md [skip ci]"
file_pattern: 'api.md'
commit_user_name: "MervinPraison"
commit_user_email: "[email protected]"
commit_author: "MervinPraison <[email protected]>"
-83
View File
@@ -1,83 +0,0 @@
name: Update Documentation Parity Tracker
on:
push:
branches: [ "main" ]
paths:
- 'src/praisonai-agents/praisonaiagents/**/*.py'
- 'src/praisonai-ts/src/**/*.ts'
- 'src/praisonai-rust/src/**/*.rs'
- 'src/praisonai/praisonai/_dev/parity/**/*.py'
- '.github/workflows/update-docs-parity.yml'
workflow_dispatch: # Allow manual trigger
permissions:
contents: write
jobs:
update-docs-parity:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
persist-credentials: true
fetch-depth: 0
- name: Checkout PraisonAIDocs
id: checkout_docs
continue-on-error: true
uses: actions/checkout@v4
with:
repository: MervinPraison/PraisonAIDocs
path: PraisonAIDocs
token: ${{ secrets.SECRETS_ADMIN_PAT }}
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: |
pip install pyyaml
- name: Generate docs parity trackers
if: steps.checkout_docs.outcome == 'success'
run: |
cd $GITHUB_WORKSPACE
python3 src/praisonai/scripts/generate_docs_parity.py --docs-root PraisonAIDocs/docs --copy-docs
- name: Check for changes
id: check_changes
run: |
if git diff --quiet src/praisonai-agents/DOCS_PARITY.md src/praisonai-ts/DOCS_PARITY.md src/praisonai-rust/DOCS_PARITY.md 2>/dev/null; then
echo "changes=false" >> $GITHUB_OUTPUT
echo "No changes to docs parity trackers"
else
echo "changes=true" >> $GITHUB_OUTPUT
echo "Docs parity trackers have been updated"
git diff --stat src/praisonai-agents/DOCS_PARITY.md src/praisonai-ts/DOCS_PARITY.md src/praisonai-rust/DOCS_PARITY.md
fi
- name: Commit and push changes to praisonai-package
if: steps.check_changes.outputs.changes == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "docs: auto-update documentation parity trackers [skip ci]"
file_pattern: 'src/praisonai-agents/DOCS_PARITY.md src/praisonai-ts/DOCS_PARITY.md src/praisonai-rust/DOCS_PARITY.md'
commit_user_name: "MervinPraison"
commit_user_email: "[email protected]"
commit_author: "MervinPraison <[email protected]>"
- name: Commit and push changes to PraisonAIDocs
if: steps.check_changes.outputs.changes == 'true' && steps.checkout_docs.outcome == 'success'
run: |
cd PraisonAIDocs
git config user.name "MervinPraison"
git config user.email "[email protected]"
git add docs/features/DOCS_PARITY.md docs/js/DOCS_PARITY.md docs/rust/DOCS_PARITY.md
git diff --staged --quiet || git commit -m "docs: auto-update documentation parity trackers [skip ci]"
git push
@@ -1,61 +0,0 @@
name: Update Feature Parity Tracker
on:
push:
branches: [ "main" ]
paths:
- 'src/praisonai-agents/praisonaiagents/**/*.py'
- 'src/praisonai-ts/src/**/*.ts'
- 'src/praisonai-rust/src/**/*.rs'
- '.github/workflows/update-parity-tracker.yml'
permissions:
contents: write
jobs:
update-parity-tracker:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: main
persist-credentials: true
fetch-depth: 0
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install pyyaml
- name: Generate parity trackers
run: |
cd $GITHUB_WORKSPACE
python3 src/praisonai/scripts/generate_parity_tracker.py
- name: Check for changes
id: check_changes
run: |
if git diff --quiet src/praisonai-ts/FEATURE_PARITY_TRACKER.json src/praisonai-ts/PARITY.md src/praisonai-rust/FEATURE_PARITY_TRACKER.json src/praisonai-rust/PARITY.md 2>/dev/null; then
echo "changes=false" >> $GITHUB_OUTPUT
echo "No changes to parity trackers"
else
echo "changes=true" >> $GITHUB_OUTPUT
echo "Parity trackers have been updated"
git diff src/praisonai-ts/FEATURE_PARITY_TRACKER.json | head -50
fi
- name: Commit and push changes
if: steps.check_changes.outputs.changes == 'true'
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: "docs: auto-update feature parity trackers [skip ci]"
file_pattern: 'src/praisonai-ts/FEATURE_PARITY_TRACKER.json src/praisonai-ts/PARITY.md src/praisonai-rust/FEATURE_PARITY_TRACKER.json src/praisonai-rust/PARITY.md'
commit_user_name: "MervinPraison"
commit_user_email: "[email protected]"
commit_author: "MervinPraison <[email protected]>"
+21 -102
View File
@@ -1,104 +1,23 @@
workspace.code-workspace
.vscode
crewai
.cache
__pycache__
chroma.sqlite3
test
dist
.env
assets
.idea
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.pytest_cache
praisonAI.egg-info
flagged
test.yaml
db
praisonai_prompt.txt
watch.sh
docs.sh
other
output
!src/praisonai/praisonai/cli/output/
!src/praisonai-agents/praisonaiagents/output/
.env.local
.env.development.local
.env.test.local
.env.production.local
.files
threads.db
threads.db-journal
.chainlit
!praisonai/ui/config/.chainlit
site
# Ignore Sphinx build directory
docs/_build/
docs/source/
docs/build/
# Ignore Sphinx cache
docs/_build/doctrees/
docs/_build/doctrees/*
docs/_build/doctrees/.doctrees
# Ignore Sphinx logs and local build files
docs/_build/.buildinfo
docs/_build/.buildinfo/*
docs/_build/.buildinfo/.buildinfo
praisonai/doc
# agentops
trained_agents_data.pkl
agentops.log
# crewAI
crewAI
!tests/integration/crewai
!tests/e2e/crewai
!src/praisonai/tests/integration/crewai
!src/praisonai/tests/e2e/crewai
# virtualenv
.venv
# Credentials
credentials.json
# node_modules
node_modules
outputs
agents/praisonaiagents/build
agents/praisonaiagents/dist
agents/praisonaiagents/praisonaiagents.egg-info
.codegpt
.praison
# Local Netlify folder
.netlify
.qodo
CopilotKit*
.gemini
issue_details.json
# MCP Registry tokens
.mcpregistry_*
*.log
.internal
.praisonai
# Backup files
*.bak
# Rust / Cargo
target/
Cargo.lock
**/*.rs.bk
*.pdb
.windsurf
src/praisonai/build*.egg-info/
src/praisonai/PraisonAI.egg-info/
jobs/*
*.egg-info
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-10
View File
@@ -1,10 +0,0 @@
# Agent Instructions
You are working on the PraisonAI project.
## Project Guidelines
- Follow the existing code style and conventions
- Be concise and helpful in responses
- Test implementation thoroughly
- Ensure backward compatibility with existing APIs
- Follow protocol-driven design: core protocols in praisonaiagents/, heavy implementations in praisonai/
-437
View File
@@ -1,437 +0,0 @@
# PraisonAI Architecture
> **Last updated:** 2026-06-24
>
> Strategic architecture document for PraisonAI — a multi-agent AI framework.
> Covers system design, runtime architecture, data contracts, reliability,
> observability, and the 2-quarter road map.
---
## Table of Contents
1. [Executive Summary](#1-executive-summary)
2. [System Overview](#2-system-overview)
3. [Layered Architecture](#3-layered-architecture)
4. [Core Data Contracts](#4-core-data-contracts)
5. [Reliability by Design](#5-reliability-by-design)
6. [Observability & Telemetry](#6-observability--telemetry)
7. [Replay & Checkpointing](#7-replay--checkpointing)
8. [Implementation Roadmap](#8-implementation-roadmap)
9. [Success Metrics](#9-success-metrics)
---
## 1. Executive Summary
PraisonAI is a multi-agent AI framework with broad capability surface across
Python, TypeScript, and Rust SDKs. The framework's core strength is its
feature breadth — agent abstractions, integrations, workflows, and active
release cadence.
The strategic bet for the next two quarters is building a **Reliability +
Orchestration + Observability** core to unlock adoption and trust:
| Dimension | Current State | Target |
|-----------|--------------|--------|
| Reliability | Cross-platform mismatches, optional dep fragility | Deterministic gating, adapter abstraction, parity CI |
| Determinism | Flaky tests, global state collisions | Isolated test fixtures, traceable tenant IDs |
| Developer UX | Complex onboarding, implicit config | Golden-path CLI, Doctor Auto-Fix |
| Observability | Minimal runtime tracing | Structured event bus, replay engine, failure classifier |
### Design Principles
- **Deterministic by default**, flexible by opt-in
- **Policy-enforced execution boundaries** at every layer
- **Structured events** for all lifecycle transitions
- **Capability isolation** per agent and per tool
---
## 2. System Overview
```
User/SDK/CLI
→ Workflow Compiler (graph + policy + schemas)
→ Execution Orchestrator (state machine, retries, fallbacks)
→ Tool Runtime (sandbox + approval + capability scope)
→ Model Runtime (provider abstraction + rate/timeout policy)
→ Observability Bus (trace events + metrics + cost)
→ Replay Engine (checkpoint + deterministic re-run)
→ Persistence (session, memory, artifacts, run ledger)
```
### Runtime Components
| Component | Responsibility | Interface |
|-----------|---------------|-----------|
| API/CLI Gateway | Accepts run requests, validates config/profile | `RunRequest`, `RunProfile` |
| Workflow Compiler | Converts YAML/DSL to normalized execution graph | `CompiledGraph` |
| Orchestrator | Executes graph state machine with retries/fallbacks | `ExecutionState`, `StepTransition` |
| Tool Runtime | Runs tools with approval/sandbox policies | `ToolCall`, `ToolResult` |
| Model Runtime | Provider routing, timeout, budget control | `ModelRequest`, `ModelResponse` |
| Observability Bus | Streams structured events + metrics | `RunEvent` schema |
| Replay Engine | Restarts from checkpoint with deterministic inputs | `ReplayRequest` |
### Existing Architecture (Python SDK)
The Python SDK is the primary runtime and is organised as follows:
- `src/praisonai/` — Core runtime and agent abstractions
- `agents/` — Agent types (chat, code, realtime, audio, vision, etc.)
- `llm/` — Model runtime with provider routing, rate limiting, failover
- `tools/` — Tool runtime with sandbox, approval, retry
- `memory/` — Memory runtime (in-memory, SQLite, MongoDB, Mem0 adapters)
- `knowledge/` — Knowledge management (indexing, retrieval, chunking)
- `workflow/` — Workflow engine (YAML/SDK-based orchestration)
- `telemetry/` — Observability, monitoring, token tracking
- `reliability/` — Error classification, circuit breaker, failover
- `skill/` — Plugin/skill system
- `loop_detection/` — Doom loop detection and prevention
- `mcp/` — MCP protocol support
- `a2a/`, `a2ui/` — Agent-to-agent and agent-to-UI protocols
### Multi-SDK Layout
- **Python SDK** — Primary runtime (`src/praisonai/`)
- **TypeScript SDK** — JS/TS runtime (`ts-sdk/`)
- **Rust SDK** — High-performance Rust runtime (`rust-sdk/`)
- **CLI** — Command-line interface (`cli/`)
- **UI** — Web UI (`ui/`)
---
## 3. Layered Architecture
```mermaid
flowchart TB
subgraph UX["Interface Layer"]
CLI[CLI]
SDK[SDK]
API[API Gateway]
end
subgraph Control["Control Plane"]
Compiler[Workflow Compiler]
Policy[Policy Engine]
Orchestrator[Execution Orchestrator]
end
subgraph Runtime["Runtime Plane"]
ModelRT[Model Runtime]
ToolRT[Tool Runtime]
MemoryRT[Memory Runtime]
end
subgraph Observe["Observability Plane"]
EventBus[Run Event Bus]
Metrics[Metrics + Cost]
Replay[Replay Engine]
end
subgraph Data["Data Plane"]
Ledger[Run Ledger]
Checkpoints[Checkpoints]
Artifacts[Artifacts Store]
end
CLI --> API
SDK --> API
API --> Compiler
API --> Policy
Compiler --> Orchestrator
Policy --> Orchestrator
Orchestrator --> ModelRT
Orchestrator --> ToolRT
Orchestrator --> MemoryRT
Orchestrator --> EventBus
EventBus --> Metrics
EventBus --> Replay
Orchestrator --> Ledger
Orchestrator --> Checkpoints
ToolRT --> Artifacts
Replay --> Checkpoints
```
### Interface Layer
- **CLI** — The `praisonai` command-line entry point
- **SDK** — Python library API (`from praisonai import Agent`)
- **API Gateway** — HTTP/WebSocket REST API (`praisonai api` or `a2a`/`a2ui`)
### Control Plane
- **Workflow Compiler** — Converts YAML workflows and SDK-defined graphs into
a normalized `CompiledGraph` with adjacency validation and cycle detection.
- **Policy Engine** — Enforces runtime policies (budget, timeout, approval
gates, capability validation) before and during execution.
- **Execution Orchestrator** — Drives the state machine through graph nodes,
handling retry, fallback, and failure classification.
### Runtime Plane
- **Model Runtime** — Provider abstraction layer. Routes to OpenAI, Anthropic,
Gemini, Ollama, DeepSeek, etc. Handles rate limiting, token tracking, and
automatic failover between providers.
- **Tool Runtime** — Executes tool calls with configurable sandboxing,
approval gates, retry policies, and capability scope validation.
- **Memory Runtime** — Manages session memory, persistent memory stores,
and knowledge retrieval across in-memory, SQLite, and MongoDB backends.
### Observability Plane
- **Run Event Bus** — Structured event streaming for all lifecycle transitions
(START, INPUT, MODEL_CALL, TOOL_CALL, ERROR, RETRY, COMPLETE).
- **Metrics + Cost** — Token counting, cost tracking, and performance metrics.
- **Replay Engine** — Deterministic replay from the last stable checkpoint,
with state hash verification.
### Data Plane
- **Run Ledger** — Persistent record of all runs with state, metrics, and
failure classifications.
- **Checkpoints** — Point-in-time snapshots of run state, memory, and
artifacts for replay and recovery.
- **Artifacts Store** — Tool outputs, generated files, and intermediate
results.
---
## 4. Core Data Contracts
### RunRequest
```text
RunRequest {
run_id: UUID,
workflow_ref: string,
inputs: object,
profile: {safe_mode, budget, timeout, approval_policy},
context: {user_id, workspace_id, env}
}
```
### RunEvent
```text
RunEvent {
ts: ISO8601,
run_id: UUID,
step_id: string,
type: START | INPUT | MODEL_CALL | TOOL_CALL |
ERROR | RETRY | COMPLETE,
payload: object,
cost: {tokens_in, tokens_out, usd},
latency_ms: number
}
```
### Checkpoint
```text
Checkpoint {
run_id: UUID,
step_id: string,
state_hash: string,
memory_snapshot_ref: string,
artifact_refs: string[]
}
```
### Execution Sequence
```text
1) Client submits RunRequest
2) Gateway validates profile + schema
3) Compiler resolves workflow → CompiledGraph
4) Orchestrator starts node execution
5) Node may call Model Runtime or Tool Runtime
6) Every transition emits RunEvent
7) On failure: classifier decides retry/fallback/abort
8) Checkpoint stored after each critical step
9) Final state + artifacts persisted to run ledger
10) Replay can resume from last stable checkpoint
```
---
## 5. Reliability by Design
### Error Taxonomy
All failures are classified into one of five categories for automatic
remediation:
| Category | Description | Default Action |
|----------|-------------|----------------|
| `config` | Invalid configuration or profile | Abort with remediation hint |
| `dependency` | Missing optional module or capability | Degrade / skip optional path |
| `tool` | Tool execution failure | Retry with backoff and policy cap |
| `model` | Model API error or timeout | Fallback to alternate provider/route |
| `infra` | Network or resource exhaustion | Circuit-breaker + replay from checkpoint |
```mermaid
flowchart TD
S[Step Failure] --> T{Failure Type?}
T -->|Config| C1[Abort + config remediation hint]
T -->|Dependency| C2[Degrade capability / skip optional path]
T -->|Tool| C3[Retry with backoff and policy cap]
T -->|Model| C4[Fallback model/provider route]
T -->|Infra| C5[Circuit-breaker + replay from checkpoint]
```
### Hardening Checklist
- **Dependency gates** — Optional modules declared as capabilities;
unavailable capability results in skip/degrade, not crash.
- **Cross-platform adapters** — OS-specific implementations behind a single
interface (e.g., file lock adapter for Windows/Linux/macOS).
- **Idempotent fixtures** — Integration tests use unique tenant/workspace IDs
and guaranteed teardown.
- **Fail-safe output mode** — CLI rendering falls back to ASCII-safe mode on
encoding mismatch.
- **CI parity matrix** — Run smoke workflows across Windows, Linux, and macOS.
### Deterministic Test Pattern
```python
# Integration test pattern — always use unique, traceable IDs
import uuid
def test_agent_workflow():
tenant_id = f"test-{uuid.uuid4().hex[:8]}"
agent = Agent(name=f"agent-{tenant_id}", ...)
result = agent.run("task")
assert result.status == "success"
# teardown is guaranteed via fixture or context manager
```
---
## 6. Observability & Telemetry
### Current Telemetry Stack
PraisonAI already includes:
- **OpenTelemetry integration** — Manual and auto-instrumentation for
traces, metrics, and logs.
- **Token tracking** — Per-call and cumulative token/cost tracking.
- **Performance monitoring** — Real-time dashboards and monitoring views.
- **LangTrace integration** — LangTrace provider for tracing.
- **Run outcomes** — Structured `RunOutcome` objects with status, duration,
token usage, and error classification.
### Planned Enhancements
- **Run Event Bus** — Stream all lifecycle events as structured `RunEvent`
payloads.
- **Failure classifier** — Automatic failure category detection with
remediation hints.
- **Run ledger visualization** — Timeline view of runs with step-by-step
event drill-down.
- **Replay integration** — Checkpoint-based run replay from the
observability dashboard.
---
## 7. Replay & Checkpointing
### Design
```mermaid
sequenceDiagram
participant U as User/Client
participant G as Gateway
participant C as Compiler
participant O as Orchestrator
participant R as Model/Tool Runtime
participant E as Event Bus
participant L as Run Ledger
participant K as Checkpoint Store
U->>G: Submit RunRequest
G->>C: Validate + compile workflow
C-->>O: CompiledGraph
O->>R: Execute next step
R-->>O: Step output / error
O->>E: Emit structured RunEvent
O->>K: Persist checkpoint
O->>L: Persist run state + metrics
O-->>U: Final result / failure summary
```
### Current State
- Basic run outcome persistence exists
- Session persistence for bot/multi-turn conversations
- Token and cost tracking per run
### Roadmap
- Full checkpoint creation at configurable step granularity
- State hash verification for deterministic replay
- Replay API endpoint (`POST /runs/{id}/replay`)
- Checkpoint pruning and retention policy
---
## 8. Implementation Roadmap
### Quarter 1: Trust Foundation (Q3 2026)
| Priority | Project | Description | Status |
|----------|---------|-------------|--------|
| P0 | Reliability Core | Cross-platform hardening, optional-dep gating, deterministic fixtures | In progress |
| P0 | Doctor Auto-Fix | Automated environment diagnosis and repairs | Planned |
| P1 | Golden-Path CLI | Single canonical flow: init → run → test → deploy | Planned |
| P1 | CI Parity Matrix | Cross-platform smoke tests in CI pipeline | Planned |
### Quarter 2: Operational Excellence (Q4 2026)
| Priority | Project | Description | Status |
|----------|---------|-------------|--------|
| P1 | Trace + Replay | Run timeline, checkpoint replay, failure classifier | Planned |
| P1 | Failure Classifier | Automatic failure category detection with remediation | Planned |
| P2 | Graph Studio UX | Visual deterministic orchestration editor/inspector | Planned |
| P2 | Safe Production Profiles | Policy presets: guardrails, approvals, cost/time caps | Planned |
```mermaid
gantt
title PraisonAI Architecture Program (2 Quarters)
dateFormat YYYY-MM-DD
section Quarter 1
Reliability hardening + parity CI :a1, 2026-07-01, 45d
Deterministic test isolation :a2, 2026-07-10, 50d
Doctor Auto-Fix MVP :a3, 2026-07-20, 40d
Golden-path CLI :a4, 2026-08-01, 45d
section Quarter 2
Trace bus + run ledger :b1, 2026-10-01, 45d
Replay engine :b2, 2026-10-15, 45d
Failure classifier :b3, 2026-11-01, 30d
Graph UX beta + Safe profiles :b4, 2026-11-10, 50d
```
---
## 9. Success Metrics
### KPI Targets
| KPI | Target | Primary Workstream |
|-----|--------|-------------------|
| Time-to-first-successful run | 30-40% reduction | Golden-path CLI + Doctor Auto-Fix |
| Cross-platform issue rate | 50% reduction | Reliability Core + adapter layer |
| Incident triage time | 40% reduction | Trace + Replay + failure classifier |
| CI confidence | Flaky test rate <2% | Deterministic tests + fixture isolation |
### Governance
- **Architecture Review Council** — Reviews runtime-contract changes
- **Release Quality Gate** — Platform matrix + deterministic test thresholds
- **Monthly telemetry review** — DX, reliability, adoption trends
---
*This document is a living reference. Update as the architecture evolves.*
-14
View File
@@ -1,14 +0,0 @@
# Contributing to PraisonAI
Thank you for your interest! Here's how to get started:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Submit a pull request
## Guidelines
- Follow existing code style
- Add tests for new features
- Update documentation as needed
-20
View File
@@ -1,20 +0,0 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+38 -936
View File
File diff suppressed because it is too large. Load diff
-66
View File
@@ -1,66 +0,0 @@
# Security Policy
PraisonAI takes security seriously. We appreciate responsible disclosure from researchers and users.
## Supported packages
| Package | Registry | Source path |
|---------|----------|-------------|
| `praisonaiagents` | [PyPI](https://pypi.org/project/praisonaiagents/) | `src/praisonai-agents/` |
| `praisonai` | [PyPI](https://pypi.org/project/praisonai/) | `src/praisonai/` |
| `praisonai-platform` | [PyPI](https://pypi.org/project/praisonai-platform/) | `src/praisonai-platform/` |
| `praisonai` | [npm](https://www.npmjs.com/package/praisonai) | `src/praisonai-ts/` |
Report issues against the **specific package** where the vulnerability exists. A platform-only issue does not necessarily affect the core SDK, and vice versa.
## In scope
- Authentication, authorisation, and multi-tenant isolation (e.g. workspace boundaries)
- Tool execution, sandbox escape, and code execution paths
- SSRF, injection, and unsafe deserialisation in shipped code
- MCP, gateway, and bot/webhook integrations
- Secrets handling, JWT/session configuration, and default credentials
- Supply-chain issues in published package artefacts
## Out of scope
- Issues in forks or deployments not maintained in [MervinPraison/PraisonAI](https://github.com/MervinPraison/PraisonAI)
- Vulnerabilities requiring physical access or fully compromised operator machines
- Reports against versions no longer published or without a supported upgrade path
- Theoretical issues with no practical impact in documented, supported configurations
- Behaviour covered by an explicit, documented opt-out (e.g. dev-only env vars)
## How to report
**Preferred:** open a **private** security advisory:
[https://github.com/MervinPraison/PraisonAI/security/advisories/new](https://github.com/MervinPraison/PraisonAI/security/advisories/new)
Include:
1. Affected package and version
2. Clear reproduction steps
3. Impact assessment (confidentiality, integrity, availability)
4. Suggested fix (optional)
Please allow reasonable time for triage and a patched release before public disclosure.
## What to expect
1. **Triage** — we validate the report and assign a disposition (`real`, `duplicate`, `wontfix`, etc.)
2. **Fix** — minimal, backward-compatible patch on `main` where appropriate
3. **Release** — patched version published to PyPI/npm **before** the advisory is published
4. **Advisory** — GitHub Security Advisory with reporter credit and CVE where applicable
## Resources
- **Repository:** https://github.com/MervinPraison/PraisonAI
- **Documentation:** https://docs.praison.ai
- **Website:** https://praison.ai
- **Advisories:** https://github.com/MervinPraison/PraisonAI/security/advisories
## Maintainer workflow
Contributors and agents handling audits should follow:
`src/praisonai-agents/.agent/workflows/security-audit.md`
-1405
View File
File diff suppressed because it is too large. Load diff
-59
View File
@@ -1,59 +0,0 @@
# PraisonAI Docker Environment Configuration
# Copy this file to .env and update with your values
# Required: OpenAI API Key
OPENAI_API_KEY=your_openai_api_key_here
# Required: Chainlit Authentication Secret (generate a random string)
CHAINLIT_AUTH_SECRET=your_chainlit_secret_here
# Optional: Additional API Keys
ANTHROPIC_API_KEY=your_anthropic_api_key_here
GOOGLE_API_KEY=your_google_api_key_here
GROQ_API_KEY=your_groq_api_key_here
COHERE_API_KEY=your_cohere_api_key_here
# Bot Tokens (for messaging bot services)
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
SLACK_APP_TOKEN=xapp-your-slack-app-token
DISCORD_BOT_TOKEN=your-discord-bot-token
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
# Optional: Service Configuration
FLASK_ENV=development
CHAINLIT_HOST=0.0.0.0
# Optional: Port Configuration (modify if you have conflicts)
UI_PORT=8082
CHAT_PORT=8083
API_PORT=8080
# Optional: Database Configuration
DATABASE_URL=sqlite:///root/.praison/database.sqlite
# Optional: Logging Configuration
LOG_LEVEL=INFO
PYTHONPATH=/app
# Optional: Development Settings
DEV_MODE=false
DEBUG=false
# Optional: Model Configuration
DEFAULT_MODEL=gpt-4
DEFAULT_TEMPERATURE=0.7
MAX_TOKENS=4000
# Optional: Security Settings
CORS_ORIGINS=http://localhost:3000,http://localhost:8082,http://localhost:8083
RATE_LIMIT=100
# Optional: Feature Flags
ENABLE_CHAT=true
ENABLE_UI=true
ENABLE_API=true
ENABLE_REALTIME=true
# Optional: Volume Paths (for custom mounting)
PRAISON_DATA_PATH=~/.praison
EXAMPLES_PATH=./examples
-46
View File
@@ -1,46 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
# Note: praisonai is installed from PyPI - remove version pin to use latest available
RUN pip install --no-cache-dir \
flask \
praisonai \
"praisonai[api]" \
gunicorn \
markdown
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Create health check API if api.py doesn't exist
RUN if [ ! -f api.py ]; then \
echo "from flask import Flask\n\
app = Flask(__name__)\n\
\n\
@app.route('/health')\n\
def health():\n\
return {'status': 'healthy'}, 200\n\
\n\
if __name__ == '__main__':\n\
app.run()" > api.py; \
fi
EXPOSE 8080
CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]
-33
View File
@@ -1,33 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.81" \
"praisonai[chat]" \
"embedchain[github,youtube]"
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "chat"]
-61
View File
@@ -1,61 +0,0 @@
# PraisonAI 🦞 Claw — All-in-one: dashboard + bots + gateway + agents
#
# Build: docker build -f docker/Dockerfile.claw -t praisonai:claw ..
# Run: docker run -p 8082:8082 -e OPENAI_API_KEY=sk-xxx praisonai:claw
# Health: curl http://localhost:8082/health
#
# Override CMD for other modes:
# Bot: docker run -e ... praisonai:claw praisonai bot telegram
# Gateway: docker run -e ... praisonai:claw praisonai gateway start
# Serve: docker run -e ... praisonai:claw praisonai serve agents.yaml
# Shell: docker run --rm -it praisonai:claw bash
FROM python:3.12-slim AS base
# ── Environment ──────────────────────────────────────────────────────────────
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
AIUI_PORT=8082 \
AIUI_HOST=0.0.0.0 \
AIUI_DATA_DIR=/data
WORKDIR /app
# ── System dependencies ─────────────────────────────────────────────────────
# tini: PID 1 signal handling (graceful shutdown on Fly/Railway/k8s)
# curl: HEALTHCHECK probe
# build-essential: compile native Python wheels (chromadb, etc.)
RUN apt-get update && apt-get install -y --no-install-recommends \
tini \
curl \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# ── Install PraisonAI 🦞 ────────────────────────────────────────────────────
# Single command installs: AIUI dashboard, agents SDK (memory/knowledge/llm/mcp),
# bot channels (Telegram/Discord/Slack), gateway, search tools, and more.
RUN pip install --no-cache-dir "praisonai[claw]"
# ── Data volume ──────────────────────────────────────────────────────────────
# Persistence for sessions, config, memory, knowledge stores
RUN mkdir -p /data
VOLUME /data
# ── Entrypoint ───────────────────────────────────────────────────────────────
# Ensures /data exists and forwards all signals via tini
RUN printf '#!/bin/sh\n\
set -e\n\
mkdir -p /data 2>/dev/null || true\n\
exec "$@"\n' > /usr/local/bin/entrypoint.sh \
&& chmod +x /usr/local/bin/entrypoint.sh
EXPOSE 8082
# ── Health check ─────────────────────────────────────────────────────────────
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
CMD curl -f http://localhost:8082/health || exit 1
# ── Run ──────────────────────────────────────────────────────────────────────
# Default: AIUI dashboard via praisonai claw. Override CMD for bot/gateway/serve.
ENTRYPOINT ["/usr/bin/tini", "--", "/usr/local/bin/entrypoint.sh"]
CMD ["praisonai", "claw", "--port", "8082"]
-47
View File
@@ -1,47 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
portaudio19-dev \
python3-pyaudio \
curl \
git \
vim \
nano \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.81" \
"praisonai[ui]" \
"praisonai[chat]" \
"praisonai[realtime]" \
"praisonai[code]" \
"embedchain[github,youtube]" \
jupyter \
jupyterlab \
notebook \
pytest \
pytest-asyncio \
mkdocs \
mkdocs-material
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "ui"]
-32
View File
@@ -1,32 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Copy praisonaiagents source
COPY src/praisonai-agents/ /app/praisonaiagents/
WORKDIR /app/praisonaiagents
# Install praisonaiagents with all features (non-editable for production)
RUN pip install --no-cache-dir ".[all]"
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
WORKDIR /app
# Default command
CMD ["python", "-c", "import praisonaiagents; print('PraisonAI Agents installed successfully')"]
-25
View File
@@ -1,25 +0,0 @@
# Dockerfile for PraisonAI Serve API
# Deploy to Railway, Render, Fly.io, or any container platform
#
# Build: docker build -f Dockerfile.serve -t praisonai-serve .
# Run: docker run -p 8005:8005 -e OPENAI_API_KEY=sk-xxx praisonai-serve
FROM python:3.11-slim
WORKDIR /app
# Install dependencies
RUN pip install --no-cache-dir praisonai praisonaiagents
# Copy agents configuration
COPY agents.yaml .
# Expose the API port
EXPOSE 8005
# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8005/health || exit 1
# Start the API server
CMD ["praisonai", "serve", "agents.yaml", "--host", "0.0.0.0", "--port", "8005"]
-33
View File
@@ -1,33 +0,0 @@
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Install Python packages (using latest versions)
RUN pip install --no-cache-dir \
praisonai_tools \
"praisonai>=4.6.81" \
"praisonai[ui]" \
"praisonai[crewai]"
# Copy application code
COPY . .
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV CHAINLIT_CONFIG_DIR=/root/.praison
ENV CHAINLIT_DB_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Default command (will be overridden by docker-compose)
CMD ["praisonai", "ui"]
-265
View File
@@ -1,265 +0,0 @@
# PraisonAI Docker Setup
This directory contains Docker configurations for running PraisonAI services in containerized environments. The setup addresses directory management issues and provides comprehensive multi-service deployment options.
## 🐳 Available Services
### Core Services
- **UI Service** (`port 8082`) - Chainlit-based web interface
- **Chat Service** (`port 8083`) - Dedicated chat interface
- **API Service** (`port 8080`) - REST API endpoint
- **Agents Service** - Standalone PraisonAI Agents runtime
### Docker Files
- `Dockerfile` - Basic API service
- `Dockerfile.ui` - UI service with web interface
- `Dockerfile.chat` - Chat-focused service
- `Dockerfile.dev` - Development environment with tools
- `Dockerfile.praisonaiagents` - Standalone agents framework
- `docker-compose.yml` - Multi-service orchestration
## 🚀 Quick Start
### Single Service
```bash
# Run UI service
docker run -p 8082:8082 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:ui
# Run Chat service
docker run -p 8083:8083 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:chat
# Run API service
docker run -p 8080:8080 -e OPENAI_API_KEY=your_key ghcr.io/mervinpraison/praisonai:api
```
### Multi-Service with Docker Compose
```bash
# Create environment file
cat > .env << EOF
OPENAI_API_KEY=your_openai_api_key_here
CHAINLIT_AUTH_SECRET=your_secret_here
EOF
# Start all services
docker-compose up -d
# View logs
docker-compose logs -f
# Stop services
docker-compose down
```
## 📁 Directory Management
### Problem Solved
The original issue was that files like `chainlit.md`, `.chainlit` directory, and `public` folder were cluttering the root directory.
### Solution Implemented
All PraisonAI configuration and runtime files are now stored in `~/.praison/`:
```bash
~/.praison/
├── database.sqlite # Chainlit database
├── chainlit.md # Chainlit configuration
├── .chainlit/ # Chainlit runtime files
└── config/ # PraisonAI configuration
```
### Environment Variables
```bash
PRAISON_CONFIG_DIR=/root/.praison # Main config directory
CHAINLIT_CONFIG_DIR=/root/.praison # Chainlit config location
CHAINLIT_DB_DIR=/root/.praison # Database location
```
## 🔧 Service Configuration
### UI Service (Port 8082)
```yaml
environment:
- CHAINLIT_PORT=8082
- CHAINLIT_HOST=0.0.0.0
- OPENAI_API_KEY=${OPENAI_API_KEY}
- CHAINLIT_AUTH_SECRET=${CHAINLIT_AUTH_SECRET}
```
### Chat Service (Port 8083)
```yaml
environment:
- CHAINLIT_PORT=8083
- CHAINLIT_HOST=0.0.0.0
- OPENAI_API_KEY=${OPENAI_API_KEY}
```
### API Service (Port 8080)
```yaml
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
```
## 🎯 Service Endpoints
| Service | Port | Endpoint | Description |
|---------|------|----------|-------------|
| UI | 8082 | http://localhost:8082 | Web interface |
| Chat | 8083 | http://localhost:8083 | Chat interface |
| API | 8080 | http://localhost:8080 | REST API |
| API Health | 8080 | http://localhost:8080/health | Health check |
## 🔍 Health Checks
All services include health checks:
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:PORT"]
interval: 10s
timeout: 5s
retries: 3
```
## 📦 Package Versions
All Docker images use consistent, up-to-date versions:
- PraisonAI: `>=2.2.95`
- PraisonAI Agents: `>=0.0.92`
- Python: `3.11-slim`
## 🔒 Security Features
- Non-root user execution where possible
- Minimal base image (python:3.11-slim)
- No unnecessary packages installed
- Environment variable-based configuration
- Volume mounting for persistent data
## 🛠 Development
### Development Environment
```bash
# Use development Dockerfile with additional tools
docker build -f Dockerfile.dev -t praisonai:dev .
docker run -it -v $(pwd):/app praisonai:dev bash
```
### Custom Configuration
```bash
# Mount custom config directory
docker run -v ~/.praison:/root/.praison praisonai:ui
```
## 📊 Monitoring
### Docker Compose Monitoring
```bash
# View service status
docker-compose ps
# View resource usage
docker-compose top
# View logs for specific service
docker-compose logs ui
docker-compose logs chat
docker-compose logs api
```
## 🚨 Troubleshooting
### Common Issues
1. **Port conflicts**
```bash
# Check port usage
netstat -tlnp | grep :8082
# Use different ports
docker run -p 9082:8082 praisonai:ui
```
2. **Environment variables not loading**
```bash
# Verify .env file
cat .env
# Set variables directly
docker run -e OPENAI_API_KEY=your_key praisonai:ui
```
3. **Permission issues**
```bash
# Check volume permissions
ls -la ~/.praison/
# Fix permissions
sudo chown -R $(id -u):$(id -g) ~/.praison/
```
4. **Service won't start**
```bash
# Check logs
docker-compose logs service_name
# Restart service
docker-compose restart service_name
```
## 🔄 Updates
### Pulling Latest Images
```bash
# Pull latest images
docker-compose pull
# Restart with new images
docker-compose up -d
```
### Version Pinning
To use specific versions, update the Dockerfile:
```dockerfile
RUN pip install "praisonai==2.2.95" "praisonaiagents==0.0.92"
```
## 🌐 Production Deployment
### Recommended Production Setup
```yaml
# docker-compose.prod.yml
version: '3.8'
services:
ui:
image: ghcr.io/mervinpraison/praisonai:ui
restart: unless-stopped
environment:
- CHAINLIT_HOST=0.0.0.0
- CHAINLIT_PORT=8082
volumes:
- praison_data:/root/.praison
networks:
- praison_network
deploy:
resources:
limits:
memory: 1G
reservations:
memory: 512M
```
### Load Balancer Configuration
For production environments, consider using nginx or similar:
```nginx
upstream praisonai {
server localhost:8082;
server localhost:8083;
}
server {
listen 80;
location / {
proxy_pass http://praisonai;
}
}
```
This Docker setup provides a clean, organized, and scalable way to deploy PraisonAI services while solving the directory management issues mentioned in the original request.
-15
View File
@@ -1,15 +0,0 @@
# Bot Environment Variables
# Copy to .env and fill in your values
# Required: LLM API Key
OPENAI_API_KEY=your_openai_api_key_here
# Slack Bot (Socket Mode)
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token
SLACK_APP_TOKEN=xapp-your-slack-app-token
# Discord Bot
DISCORD_BOT_TOKEN=your-discord-bot-token
# Telegram Bot
TELEGRAM_BOT_TOKEN=your-telegram-bot-token
-42
View File
@@ -1,42 +0,0 @@
# Dockerfile for PraisonAI Slack Bot
# Secrets are passed at runtime via environment variables
FROM python:3.11-slim
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc \
python3-dev \
curl \
git \
&& rm -rf /var/lib/apt/lists/*
# Create praison config directory
RUN mkdir -p /root/.praison
# Install praisonai with bot dependencies
RUN pip install --no-cache-dir \
praisonai \
slack-bolt \
slack-sdk \
"discord.py>=2.0.0" \
"python-telegram-bot>=20.0"
# Set environment variables for directory management
ENV PRAISON_CONFIG_DIR=/root/.praison
ENV DOCKER_CONTAINER=true
# Bot tokens should be passed at runtime (not baked into image)
# SLACK_BOT_TOKEN=xoxb-...
# SLACK_APP_TOKEN=xapp-...
# Optional: Copy custom agent configuration
# COPY agent.yaml /app/agent.yaml
# Expose no ports - Socket Mode doesn't need them
# For webhook mode, expose port 3000
# Default command - tokens from environment
CMD ["praisonai", "bot", "slack"]
-66
View File
@@ -1,66 +0,0 @@
# PraisonAI Bot Docker Deployment
Deploy Slack, Discord, or Telegram bots using Docker.
## Quick Start
```bash
# 1. Copy environment template
cp .env.template .env
# 2. Edit .env with your tokens
nano .env
# 3. Run specific bot
docker compose up slack-bot -d
# Or run all bots
docker compose up -d
```
## Environment Variables
| Variable | Required | Description |
|----------|----------|-------------|
| `OPENAI_API_KEY` | Yes | LLM API key |
| `SLACK_BOT_TOKEN` | For Slack | Bot token (xoxb-...) |
| `SLACK_APP_TOKEN` | For Slack | App token (xapp-...) |
| `DISCORD_BOT_TOKEN` | For Discord | Bot token |
| `TELEGRAM_BOT_TOKEN` | For Telegram | Bot token |
## Slack Setup
1. Create app at https://api.slack.com/apps
2. Enable **Socket Mode** → Generate App-Level Token with `connections:write`
3. Add **OAuth Scopes**: `chat:write`, `app_mentions:read`, `im:history`
4. Enable **Event Subscriptions** → Subscribe to `app_mention`, `message.im`
5. **Install to Workspace**
6. Copy Bot Token and App Token to `.env`
## Commands
```bash
# Start Slack bot
docker compose up slack-bot -d
# View logs
docker compose logs -f slack-bot
# Stop bot
docker compose down slack-bot
# Rebuild after updates
docker compose build --no-cache slack-bot
```
## Custom Agent Configuration
Mount your agent config:
```yaml
services:
slack-bot:
volumes:
- ./agent.yaml:/app/agent.yaml
command: ["praisonai", "bot", "slack", "--agent", "/app/agent.yaml"]
```
-53
View File
@@ -1,53 +0,0 @@
version: '3.8'
# PraisonAI Bot Services
# Run specific bots: docker compose up slack-bot
# Run all bots: docker compose up
services:
# Slack Bot
slack-bot:
build:
context: ../..
dockerfile: docker/bots/Dockerfile
volumes:
- praison_data:/root/.praison
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
SLACK_BOT_TOKEN: ${SLACK_BOT_TOKEN}
SLACK_APP_TOKEN: ${SLACK_APP_TOKEN}
PRAISON_CONFIG_DIR: /root/.praison
command: [ "praisonai", "bot", "slack" ]
restart: unless-stopped
# Discord Bot
discord-bot:
build:
context: ../..
dockerfile: docker/bots/Dockerfile
volumes:
- praison_data:/root/.praison
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
DISCORD_BOT_TOKEN: ${DISCORD_BOT_TOKEN}
PRAISON_CONFIG_DIR: /root/.praison
command: [ "praisonai", "bot", "discord" ]
restart: unless-stopped
# Telegram Bot
telegram-bot:
build:
context: ../..
dockerfile: docker/bots/Dockerfile
volumes:
- praison_data:/root/.praison
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
TELEGRAM_BOT_TOKEN: ${TELEGRAM_BOT_TOKEN}
PRAISON_CONFIG_DIR: /root/.praison
command: [ "praisonai", "bot", "telegram" ]
restart: unless-stopped
volumes:
praison_data:
driver: local
-23
View File
@@ -1,23 +0,0 @@
# Use an official Python runtime as a parent image
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# Set work directory
WORKDIR /app
# Install system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*
# Install PraisonAI with the 'call' extra and ensure it's the latest version
RUN pip install --no-cache-dir --upgrade "praisonai[call]"
# Expose the port the app runs on
EXPOSE 8090
# Run the application
CMD ["praisonai", "call"]
-13
View File
@@ -1,13 +0,0 @@
# Praison AI Call Docker
1. Build the Docker image:
```
docker build -t praisonai-call .
```
2. Run the container:
```
docker run -d -p 8090:8090 praisonai-call -e OPENAI_API_KEY=your_api_key_here
```
Make sure to replace your_api_key_here with your actual OpenAI API key.
-190
View File
@@ -1,190 +0,0 @@
version: '3.8'
# Development Docker Compose Configuration
# Extends the main docker-compose.yml with development-specific settings
services:
# Development UI Service with hot reload
ui-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
- ../docs:/app/docs:ro
ports:
- "${UI_PORT:-8082}:8082"
environment:
FLASK_ENV: development
CHAINLIT_PORT: "8082"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: praisonai ui --host 0.0.0.0 --reload
networks:
- praison_network
depends_on:
- database
# Development Chat Service
chat-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
ports:
- "${CHAT_PORT:-8083}:8083"
environment:
CHAINLIT_PORT: "8083"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: praisonai chat --host 0.0.0.0 --port 8083 --reload
networks:
- praison_network
depends_on:
- database
# Development API Service with hot reload
api-dev:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
ports:
- "${API_PORT:-8080}:8080"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
FLASK_ENV: development
DEBUG: "true"
DEV_MODE: "true"
PYTHONPATH: /app/src
command: python -m flask --app api run --host 0.0.0.0 --port 8080 --debug
networks:
- praison_network
depends_on:
- database
# Development Jupyter Notebook Service
jupyter:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- praison_data:/root/.praison
- ../src:/app/src:rw
- ../examples:/app/examples:rw
- ../docs:/app/docs:rw
ports:
- "8888:8888"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
JUPYTER_ENABLE_LAB: "yes"
PYTHONPATH: /app/src
command: jupyter lab --ip=0.0.0.0 --port=8888 --no-browser --allow-root --LabApp.token='' --LabApp.password=''
networks:
- praison_network
# Development Testing Service
test:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- ../src:/app/src:rw
- ../tests:/app/tests:rw
- praison_data:/root/.praison
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
PYTHONPATH: /app/src
PYTEST_CURRENT_TEST: "true"
command: python -m pytest tests/ -v
networks:
- praison_network
profiles:
- test
# Standalone database for development
database:
security_opt:
- "no-new-privileges:true"
image: postgres:15-alpine
environment:
POSTGRES_DB: praisonai_dev
POSTGRES_USER: praisonai
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-praisonai_dev_pass}
volumes:
- postgres_data:/var/lib/postgresql/data
read_only: true
tmpfs:
- /tmp
- /var/tmp
ports:
- "5432:5432"
networks:
- praison_network
profiles:
- database
# Redis for caching and session management
redis:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- praison_network
profiles:
- cache
# Development documentation server
docs:
build:
context: ..
dockerfile: docker/Dockerfile.dev
volumes:
- ../docs:/app/docs:rw
- ../README.md:/app/README.md:ro
ports:
- "8000:8000"
command: sh -c "cd docs && mkdocs serve --dev-addr 0.0.0.0:8000"
networks:
- praison_network
profiles:
- docs
volumes:
praison_data:
driver: local
postgres_data:
driver: local
redis_data:
driver: local
networks:
praison_network:
driver: bridge
-125
View File
@@ -1,125 +0,0 @@
version: '3.8'
services:
# PraisonAI 🦞 Claw — Recommended all-in-one service
claw:
build:
context: ..
dockerfile: docker/Dockerfile.claw
volumes:
- claw_data:/data
ports:
- "8082:8082"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
restart: unless-stopped
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8082/health" ]
interval: 30s
timeout: 5s
start_period: 20s
retries: 3
networks:
- praison_network
# ─── Legacy services (kept for backward compatibility) ─────────────────
# PraisonAI UI Service
ui:
build:
context: ..
dockerfile: docker/Dockerfile.ui
volumes:
- praison_data:/root/.praison
- .:/app
ports:
- "8082:8082"
environment:
FLASK_ENV: development
CHAINLIT_PORT: "8082"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
command: praisonai ui --host 0.0.0.0
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8082" ]
interval: 10s
timeout: 5s
retries: 3
networks:
- praison_network
# PraisonAI Chat Service
chat:
build:
context: ..
dockerfile: docker/Dockerfile.chat
volumes:
- praison_data:/root/.praison
- .:/app
ports:
- "8083:8083"
environment:
CHAINLIT_PORT: "8083"
CHAINLIT_HOST: "0.0.0.0"
OPENAI_API_KEY: ${OPENAI_API_KEY}
CHAINLIT_AUTH_SECRET: ${CHAINLIT_AUTH_SECRET}
PRAISON_CONFIG_DIR: /root/.praison
CHAINLIT_CONFIG_DIR: /root/.praison
CHAINLIT_DB_DIR: /root/.praison
command: praisonai chat --host 0.0.0.0 --port 8083
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8083" ]
interval: 10s
timeout: 5s
retries: 3
networks:
- praison_network
# PraisonAI API Service
api:
build:
context: ..
dockerfile: docker/Dockerfile
volumes:
- praison_data:/root/.praison
ports:
- "8080:8080"
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:8080/health" ]
interval: 10s
timeout: 5s
retries: 3
networks:
- praison_network
# PraisonAI Agents Service (standalone)
agents:
build:
context: ..
dockerfile: docker/Dockerfile.praisonaiagents
volumes:
- praison_data:/root/.praison
- ./examples:/app/examples
environment:
OPENAI_API_KEY: ${OPENAI_API_KEY}
PRAISON_CONFIG_DIR: /root/.praison
networks:
- praison_network
# For bot services (Slack, Discord, Telegram), see docker/bots/
volumes:
claw_data:
driver: local
praison_data:
driver: local
networks:
praison_network:
driver: bridge
-289
View File
@@ -1,289 +0,0 @@
#!/bin/bash
# PraisonAI Docker Quick Start Script
# This script helps users quickly get started with PraisonAI using Docker
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if Docker is installed
check_docker() {
if ! command -v docker &> /dev/null; then
print_error "Docker is not installed. Please install Docker first:"
echo " - Visit: https://docs.docker.com/get-docker/"
exit 1
fi
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
print_error "Docker Compose is not available. Please install Docker Compose:"
echo " - Visit: https://docs.docker.com/compose/install/"
exit 1
fi
print_success "Docker and Docker Compose are available"
}
# Check if .env file exists, create from template if not
setup_environment() {
if [ ! -f ".env" ]; then
print_status "Creating .env file from template..."
if [ -f ".env.template" ]; then
cp .env.template .env
else
cat > .env << EOF
# PraisonAI Docker Environment Configuration
OPENAI_API_KEY=your_openai_api_key_here
CHAINLIT_AUTH_SECRET=$(openssl rand -hex 32 2>/dev/null || echo "your_chainlit_secret_here")
FLASK_ENV=development
CHAINLIT_HOST=0.0.0.0
UI_PORT=8082
CHAT_PORT=8083
API_PORT=8080
EOF
fi
print_warning "Please edit .env file and add your API keys before continuing"
print_status "Required: OPENAI_API_KEY"
read -p "Press Enter to continue after updating .env file..."
else
print_success ".env file already exists"
fi
}
# Function to show menu
show_menu() {
echo ""
echo "=== PraisonAI Docker Quick Start ==="
echo ""
echo "1) Start UI Service (Web Interface) - Port 8082"
echo "2) Start Chat Service - Port 8083"
echo "3) Start API Service - Port 8080"
echo "4) Start All Services (Recommended)"
echo "5) Start Development Environment"
echo "6) View Service Status"
echo "7) View Logs"
echo "8) Stop All Services"
echo "9) Pull Latest Images"
echo "10) Clean Up (Remove containers and volumes)"
echo "11) Show Service URLs"
echo "0) Exit"
echo ""
}
# Start specific service
start_service() {
local service=$1
print_status "Starting $service service..."
docker-compose up -d $service
print_success "$service service started"
}
# Start all services
start_all_services() {
print_status "Starting all PraisonAI services..."
docker-compose up -d
print_success "All services started"
show_service_urls
}
# Start development environment
start_dev_environment() {
print_status "Starting development environment..."
if [ -f "docker-compose.dev.yml" ]; then
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d ui-dev chat-dev api-dev
print_success "Development environment started"
echo ""
echo "Development services available:"
echo " - UI (with hot reload): http://localhost:${UI_PORT:-8082}"
echo " - Chat (with hot reload): http://localhost:${CHAT_PORT:-8083}"
echo " - API (with hot reload): http://localhost:${API_PORT:-8080}"
echo " - Jupyter Lab: http://localhost:8888"
echo ""
else
print_warning "Development configuration not found, starting regular services"
start_all_services
fi
}
# Show service status
show_status() {
print_status "Service Status:"
docker-compose ps
}
# Show logs
show_logs() {
echo ""
echo "Which service logs would you like to view?"
echo "1) All services"
echo "2) UI service"
echo "3) Chat service"
echo "4) API service"
echo "5) Agents service"
read -p "Enter choice (1-5): " log_choice
case $log_choice in
1) docker-compose logs -f ;;
2) docker-compose logs -f ui ;;
3) docker-compose logs -f chat ;;
4) docker-compose logs -f api ;;
5) docker-compose logs -f agents ;;
*) print_error "Invalid choice" ;;
esac
}
# Stop all services
stop_services() {
print_status "Stopping all services..."
docker-compose down
print_success "All services stopped"
}
# Pull latest images
pull_images() {
print_status "Pulling latest images..."
docker-compose pull
print_success "Images updated"
}
# Clean up
cleanup() {
print_warning "This will remove all containers, networks, and volumes"
read -p "Are you sure? (y/N): " confirm
if [[ $confirm == [yY] || $confirm == [yY][eE][sS] ]]; then
print_status "Cleaning up..."
docker-compose down -v --remove-orphans
docker system prune -f
print_success "Cleanup completed"
fi
}
# Show service URLs
show_service_urls() {
local ui_port=${UI_PORT:-8082}
local chat_port=${CHAT_PORT:-8083}
local api_port=${API_PORT:-8080}
echo ""
echo "=== Service URLs ==="
echo "UI Service: http://localhost:$ui_port"
echo "Chat Service: http://localhost:$chat_port"
echo "API Service: http://localhost:$api_port"
echo "API Health: http://localhost:$api_port/health"
echo ""
}
# Check system requirements
check_requirements() {
print_status "Checking system requirements..."
# Check available memory
if command -v free &> /dev/null; then
available_ram=$(free -m | awk 'NR==2{printf "%.1f", $7/1024}')
print_status "Available RAM: ${available_ram}GB"
if (( $(echo "$available_ram < 2.0" | bc -l 2>/dev/null || echo "1") )); then
print_warning "Low available memory. Recommended: 2GB+ available RAM"
fi
fi
# Check disk space
if command -v df &> /dev/null; then
available_space=$(df -BG . | awk 'NR==2{print $4}' | sed 's/G//')
print_status "Available disk space: ${available_space}GB"
if [ "$available_space" -lt 5 ]; then
print_warning "Low disk space. Recommended: 5GB+ available space"
fi
fi
}
# Main function
main() {
clear
echo "🤖 Welcome to PraisonAI Docker Setup!"
echo ""
# Check prerequisites
check_docker
check_requirements
setup_environment
# Main loop
while true; do
show_menu
read -p "Enter your choice (0-11): " choice
case $choice in
1)
start_service "ui"
echo "UI Service: http://localhost:${UI_PORT:-8082}"
;;
2)
start_service "chat"
echo "Chat Service: http://localhost:${CHAT_PORT:-8083}"
;;
3)
start_service "api"
echo "API Service: http://localhost:${API_PORT:-8080}"
;;
4)
start_all_services
;;
5)
start_dev_environment
;;
6)
show_status
;;
7)
show_logs
;;
8)
stop_services
;;
9)
pull_images
;;
10)
cleanup
;;
11)
show_service_urls
;;
0)
print_success "Thank you for using PraisonAI!"
exit 0
;;
*)
print_error "Invalid choice. Please try again."
;;
esac
echo ""
read -p "Press Enter to continue..."
done
}
# Run main function
main
-5
View File
@@ -1,5 +0,0 @@
framework: praisonaiagents
services:
- ui
- chat
- realtime
-3
View File
@@ -1,3 +0,0 @@
code:
ignore_files:
- "*/__pycache__/*"
-2
View File
@@ -1,2 +0,0 @@
*.txt
*.csv
-83
View File
@@ -1,83 +0,0 @@
# PraisonAI Examples
This folder contains examples for PraisonAI. For detailed documentation, visit [docs.praison.ai](https://docs.praison.ai).
## Structure
```
examples/
├── python/ # Python examples
│ ├── agents/ # Agent examples (single, multi, router, etc.)
│ ├── workflows/ # Workflow patterns (routing, parallel, loop)
│ ├── tools/ # Custom tools examples
│ ├── mcp/ # MCP protocol examples
│ ├── memory/ # Memory and sessions
│ ├── code/ # Code editing and external CLI tools
│ └── ...
├── serve/ # Server and endpoints examples
├── yaml/ # YAML workflow examples
└── cookbooks/ # Complete use-case examples
```
## Quick Links
| Category | Examples | Docs |
|----------|----------|------|
| **Consolidated Params** | [consolidated_params/](consolidated_params/) | [📖](https://docs.praison.ai/api/consolidated-params) |
| **Agents** | [python/agents/](python/agents/) | [📖](https://docs.praison.ai/concepts/agents) |
| **Workflows** | [python/workflows/](python/workflows/) | [📖](https://docs.praison.ai/features/workflows) |
| **Model Router** | [python/agents/router-agent-cost-optimization.py](python/agents/router-agent-cost-optimization.py) | [📖](https://docs.praison.ai/features/model-router) |
| **MCP** | [python/mcp/](python/mcp/) | [📖](https://docs.praison.ai/mcp) |
| **Memory** | [python/memory/](python/memory/) | [📖](https://docs.praison.ai/concepts/memory) |
| **Tools** | [python/tools/](python/tools/) | [📖](https://docs.praison.ai/tools) |
| **Code** | [python/code/](python/code/) | [📖](https://docs.praison.ai/code) |
| **YAML** | [yaml/](yaml/) | [📖](https://docs.praison.ai/features/yaml-workflows) |
| **Doctor** | [doctor/](doctor/) | [📖](https://docs.praison.ai/cli/doctor) |
| **Serve** | [serve/](serve/) | [📖](https://docs.praison.ai/cli/serve) |
| **Endpoints** | [serve/](serve/) | [📖](https://docs.praison.ai/cli/endpoints) |
## Consolidated Params Examples
Agent-centric API with unified parameter resolution. Precedence: Instance > Config > Array > Dict > String > Bool > Default
| Example | Description |
|---------|-------------|
| [basic_agent.py](consolidated_params/basic_agent.py) | Minimal agent with memory |
| [basic_agents.py](consolidated_params/basic_agents.py) | Multi-agent with memory+planning |
| [basic_workflow.py](consolidated_params/basic_workflow.py) | Workflow with consolidated params |
| [basic_memory.py](consolidated_params/basic_memory.py) | Memory presets (file, redis, postgres) |
| [basic_guardrails.py](consolidated_params/basic_guardrails.py) | Guardrails with callable or config |
| [basic_workflow_agentlike.py](consolidated_params/basic_workflow_agentlike.py) | Workflow with agent-like params |
| [basic_step_override.py](consolidated_params/basic_step_override.py) | Step-level override of workflow defaults |
| [advanced_workflow_full_features.py](consolidated_params/advanced_workflow_full_features.py) | All consolidated params |
## Serve Examples
| Example | Description | CLI Command |
|---------|-------------|-------------|
| [unified_server.py](serve/unified_server.py) | All providers in one server | `praisonai serve unified` |
| [agent_as_api_single.py](serve/agent_as_api_single.py) | Single agent HTTP API | `praisonai serve agents` |
| [agents_as_api_router.py](serve/agents_as_api_router.py) | Multi-agent router API | `praisonai serve agents` |
| [a2a_server_client.py](serve/a2a_server_client.py) | A2A protocol server | `praisonai serve a2a` |
| [a2u_events_stream.py](serve/a2u_events_stream.py) | A2U event stream | `praisonai serve a2u` |
| [mcp_http_server.py](serve/mcp_http_server.py) | MCP HTTP server | `praisonai serve mcp` |
| [tools_as_mcp_server.py](serve/tools_as_mcp_server.py) | Tools as MCP server | `praisonai serve tools` |
| [agent_launch_modes.py](serve/agent_launch_modes.py) | Agent.launch() API | Python only |
| [endpoints_unified_client.py](serve/endpoints_unified_client.py) | Unified client | `praisonai endpoints` |
## Running Examples
```bash
# Install PraisonAI
pip install praisonai
# Set API key
export OPENAI_API_KEY=your_key_here
# Run an example
python examples/python/agents/single-agent.py
```
## CLI Commands
See the main [README.md](../README.md#-cli--no-code-interface) for all CLI commands.
-137
View File
@@ -1,137 +0,0 @@
# ACP (Agent Client Protocol) Examples
This directory contains examples for using PraisonAI with the Agent Client Protocol (ACP).
## What is ACP?
The Agent Client Protocol (ACP) is a standardized JSON-RPC 2.0 protocol that allows code editors and IDEs to communicate with AI coding agents. It enables seamless integration between PraisonAI and editors like:
- **Zed**
- **JetBrains IDEs** (IntelliJ, PyCharm, WebStorm, etc.)
- **VSCode** (via extensions)
- **Toad**
## Quick Start
### CLI Usage
```bash
# Start ACP server with defaults
praisonai acp
# Enable debug logging
praisonai acp --debug
# Allow file writes
praisonai acp --allow-write
# Use a specific model
praisonai acp -m gpt-4o
# Resume last session
praisonai acp --resume --last
```
### Python API
```python
from praisonai.acp import serve
serve(
workspace=".",
debug=True,
allow_write=True,
)
```
## Editor Configuration
### Zed
Add to `~/.config/zed/settings.json`:
```json
{
"agent_servers": {
"PraisonAI": {
"command": "praisonai",
"args": ["acp"],
"env": {}
}
}
}
```
### JetBrains
Add to `~/.jetbrains/acp.json`:
```json
{
"agent_servers": {
"PraisonAI": {
"command": "praisonai",
"args": ["acp"],
"env": {}
}
}
}
```
### Toad
```bash
toad acp "praisonai acp"
```
## Examples
- `basic_acp_server.py` - Simple ACP server with default settings
- `custom_agent_acp.py` - ACP server with a custom agent
## CLI Options
| Option | Description |
|--------|-------------|
| `-w, --workspace` | Workspace root directory |
| `-a, --agent` | Agent name or config file |
| `-m, --model` | LLM model to use |
| `-r, --resume` | Resume session by ID |
| `--last` | Resume the last session |
| `--approve` | Approval mode: manual, auto, scoped |
| `--read-only` | Read-only mode (default) |
| `--allow-write` | Allow file writes |
| `--allow-shell` | Allow shell commands |
| `--debug` | Enable debug logging |
## Installation
```bash
pip install praisonai[acp]
```
## Protocol Details
ACP uses JSON-RPC 2.0 over stdio:
- **stdin**: Receives JSON-RPC requests from the client
- **stdout**: Sends JSON-RPC responses to the client
- **stderr**: Debug/log output (never pollutes stdout)
### Supported Methods
**Agent Methods (client → agent):**
- `initialize` - Negotiate protocol version and capabilities
- `authenticate` - Optional authentication
- `session/new` - Create a new session
- `session/load` - Load an existing session
- `session/prompt` - Send user message
- `session/cancel` - Cancel ongoing operations
- `session/set_mode` - Change operating mode
**Client Methods (agent → client):**
- `session/update` - Send progress updates
- `session/request_permission` - Request user approval
- `fs/read_text_file` - Read file contents
- `fs/write_text_file` - Write file contents
- `terminal/create` - Create terminal
- `terminal/output` - Get terminal output
-27
View File
@@ -1,27 +0,0 @@
#!/usr/bin/env python3
"""
Basic ACP Server Example
This example demonstrates how to run PraisonAI as an ACP server
that can be connected to by IDEs like Zed, JetBrains, VSCode, or Toad.
Usage:
# Run from command line
praisonai acp
# Or run this script directly
python basic_acp_server.py
"""
from praisonai.acp import serve
if __name__ == "__main__":
# Start ACP server with default settings
# This will listen on stdin/stdout for JSON-RPC messages
serve(
workspace=".", # Current directory as workspace
agent="default", # Use default agent
debug=False, # Set to True for debug logging to stderr
read_only=True, # Safe by default
approval_mode="manual", # Require approval for actions
)
-45
View File
@@ -1,45 +0,0 @@
#!/usr/bin/env python3
"""
Custom Agent ACP Server Example
This example shows how to run a custom PraisonAI agent as an ACP server.
Usage:
python custom_agent_acp.py
"""
from praisonai.acp import serve, ACPServer, ACPConfig
from praisonaiagents import Agent
# Create a custom agent
agent = Agent(
name="CodeAssistant",
instructions="""You are an expert coding assistant. You help users:
- Write clean, efficient code
- Debug issues
- Explain complex concepts
- Review code for best practices
Always provide clear explanations with your code suggestions.""",
model="gpt-4o-mini",
)
if __name__ == "__main__":
# Create config
config = ACPConfig(
workspace=".",
debug=True,
allow_write=True, # Allow file modifications
approval_mode="manual",
)
# Create server with custom agent
server = ACPServer(config=config, agent=agent)
# Run the server
serve(
workspace=".",
debug=True,
allow_write=True,
approval_mode="manual",
)
-30
View File
@@ -1,30 +0,0 @@
framework: ag2
topic: "Research the latest developments in AI agents"
# Install: pip install "praisonai[ag2]"
# Run: praisonai --framework ag2 examples/ag2/ag2_basic.yaml
# or praisonai run examples/ag2/ag2_basic.yaml --framework ag2
roles:
research_agent:
role: "AI Research Specialist"
goal: "Research and summarise the latest developments in AI agent frameworks"
backstory: |
You are an experienced AI researcher with deep knowledge of multi-agent
systems, large language models, and the latest trends in AI tooling.
You excel at synthesising complex technical topics into clear summaries.
tasks:
research_task:
description: |
Research and summarise the latest developments in AI agent frameworks
for the topic: {topic}
Focus on:
1. Key frameworks and their unique capabilities
2. Recent innovations and improvements
3. Community adoption and ecosystem growth
4. Practical use cases and success stories
expected_output: |
A concise research summary covering the key developments,
major frameworks, and practical insights. Include 3-5 bullet
points of the most important findings.
-42
View File
@@ -1,42 +0,0 @@
framework: ag2
topic: "Cloud-native AI deployment strategies on AWS"
# AG2 exclusive feature: native AWS Bedrock support via LLMConfig(api_type="bedrock")
#
# Prerequisites:
# pip install "praisonai[ag2]"
# aws configure (or set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION)
#
# Run:
# praisonai --framework ag2 examples/ag2/ag2_bedrock.yaml
#
# The AG2 adapter detects api_type="bedrock" from the llm config and uses
# LLMConfig(api_type="bedrock", model=...) — no OPENAI_API_KEY required.
# AWS credentials are sourced from boto3 (env vars, ~/.aws/credentials, IAM role).
roles:
cloud_architect:
role: "AWS Cloud Architect"
goal: "Design and explain cloud-native AI deployment strategies on AWS"
backstory: |
You are an AWS Solutions Architect specialising in AI/ML workloads.
You have deep expertise in Amazon Bedrock, SageMaker, ECS, and Lambda,
and you help organisations deploy AI agents at scale securely and cost-effectively.
llm:
model: "bedrock/anthropic.claude-3-5-sonnet-20241022-v2:0"
api_type: "bedrock"
aws_region: "us-east-1"
tasks:
architecture_task:
description: |
Design a cloud-native deployment strategy for AI agents on AWS for: {topic}
Cover:
1. Recommended AWS services (Bedrock, ECS, Lambda, etc.)
2. Scalability and cost optimisation patterns
3. Security and compliance considerations
4. A simple reference architecture overview
expected_output: |
A concise architecture guide with service recommendations,
a high-level deployment diagram description, and key
best practices for production AI agent deployments on AWS.
Loaded 100 of 4804 files, more files were not shown because too many files have changed in this diff. Show more