Intelligent multi-agent system for automated blog content generation for acrossceylon.com - Sri Lanka's premier cycling tour operator.
Automatically generates high-quality, SEO-optimized blog articles (2,500-3,500 words) about cycling tourism in Sri Lanka using a sophisticated multi-agent architecture:
- β Smart Topic Discovery - Analyzes published content to identify gaps
- β AI-Powered Research - Google Keyword Planner API integration
- β Professional Writing - Claude 3 Sonnet generates complete articles
- β Quality Validation - 5-check automated QA system with scoring
- β Image Sourcing - Cloudinary (priority) + Pexels (fallback)
- β Email Approval - Quality report with approve/decline workflow
- β Sanity CMS Integration - Automatic publishing with approval
The system uses 5 specialized AI agents coordinated by a Manager Agent:
| Agent | Purpose | Technologies |
|---|---|---|
| Manager | Orchestrates workflow, manages state, sends emails | DynamoDB, SES, CloudWatch |
| Topic Discovery | Finds content gaps, selects topics | Python, DynamoDB |
| Research | Keyword research, content strategy | Google Ads API, Claude |
| SEO Writer | Generates complete articles | Claude 3 Sonnet, Cloudinary, Pexels |
| Content Checker | Quality validation (5 checks, 0-100 score) | Python (no AI) |
Manager Agent
β
Topic Discovery β Research β SEO Writer β Content Checker
β
[Score 0-100]
β
βββββββββββββββββββΌββββββββββββββββββ
β β β
APPROVED NEEDS_REVISION REJECTED
(β₯85%) (70-84%) (<70%)
β β
[Send Email] [Send Email] [No Email]
β β
[Approve/Decline from Email]
β
[Publish to Sanity CMS]
Every article receives a quality score (0-100) based on 5 automated checks:
- Verifies facts against research report
- Requires 70% coverage minimum
- Critical failure if <70%
- Keyword density 1-3%
- Meta title 50-60 chars
- Meta description 140-160 chars
- Internal links 2-5
- Images 3-5
- Covers must-include items
- Requires 80% coverage minimum
- Critical failure if <80%
- Jaccard similarity with recent 10 articles
- Must be <20% similar
- Critical failure if too similar
- Word count 2,500-3,500
- Flesch readability >60
Validation Outcomes:
- APPROVED (β₯85%): Article approved, email sent
- NEEDS_REVISION (70-84%): Minor issues, email sent with warnings
- REJECTED (<70%): Quality too low, no email sent
- AWS Account with Bedrock access (Claude 3 Sonnet model:
anthropic.claude-3-sonnet-20240229-v1:0) - Google Cloud Console project (Keyword Planner API)
- Sanity CMS account
- Cloudinary account (optional, for custom images)
- Pexels API key
- SES verified sender email
git clone https://github.com/YOUR_USERNAME/blog-automation.git
cd blog-automation# Sanity CMS credentials
aws secretsmanager create-secret \
--name blog-sanity-credentials \
--secret-string '{"project_id":"xxx","dataset":"production","token":"xxx"}' \
--region us-east-1
# Cloudinary credentials (optional)
aws secretsmanager create-secret \
--name blog-cloudinary-credentials \
--secret-string '{"cloud_name":"xxx","api_key":"xxx","api_secret":"xxx"}' \
--region us-east-1
# Pexels API key
aws secretsmanager create-secret \
--name blog-pexels-credentials \
--secret-string '{"api_key":"xxx"}' \
--region us-east-1
# Google Keyword Planner credentials
aws secretsmanager create-secret \
--name blog-google-keywords-credentials \
--secret-string '{"client_id":"xxx","client_secret":"xxx","refresh_token":"xxx","developer_token":"xxx","customer_id":"xxx"}' \
--region us-east-1export AWS_PROFILE=blog-automation
export AWS_REGION=us-east-1
chmod +x deploy.sh
./deploy.shPush to main branch or trigger manually via GitHub Actions UI. The workflow will:
- Package both single-agent and multi-agent systems
- Deploy all 5 Lambda functions
- Configure retry settings (MaximumRetryAttempts=0)
- Verify deployment
Required GitHub Secrets:
Go to your repository β Settings β Secrets and variables β Actions β New repository secret
AWS_ACCESS_KEY_ID- Your AWS access keyAWS_SECRET_ACCESS_KEY- Your AWS secret key
Note: The deploy.sh script automatically detects CI/CD environments and skips AWS profile configuration.
Both options create:
- β 5 Lambda functions (2 multi-agent workflows, 2 single-agent workflows, 1 approval handler)
- β
DynamoDB table
blog-workflow-state - β API Gateway approval endpoint
- β EventBridge daily schedule (10 AM UTC)
- β IAM role with CloudWatch, Bedrock, DynamoDB, SES permissions
# Trigger multi-agent workflow
aws lambda invoke \
--function-name blog-multiagent-workflow \
--payload '{}' \
/tmp/test.json \
--profile blog-automation \
--region us-east-1
# Check DynamoDB for workflow
aws dynamodb scan \
--table-name blog-workflow-state \
--profile blog-automation \
--region us-east-1 \
--max-items 1- Check your inbox for approval email with quality score
- Click APPROVE to publish to Sanity CMS
- Click DECLINE to reject the article
Articles are generated automatically every day at 10 AM UTC using the multi-agent workflow.
Current EventBridge configuration:
# Check which workflow is scheduled
aws events list-targets-by-rule \
--rule blog-daily-trigger \
--profile blog-automation \
--region us-east-1Generate an article immediately:
# Multi-agent (recommended - includes quality validation)
aws lambda invoke \
--function-name blog-multiagent-workflow \
--payload '{}' \
/tmp/test.json \
--profile blog-automation
# Single-agent (faster, no quality validation)
aws lambda invoke \
--function-name blog-manual-trigger \
--payload '{}' \
/tmp/test.json \
--profile blog-automationMulti-Agent Email Includes:
- Quality score badge (0-100)
- Validation status (APPROVED/NEEDS_REVISION)
- Strengths and areas for improvement
- Article preview
- Metadata (word count, reading time, keywords)
- Up to 3 images preview
- Approve/Decline buttons
Single-Agent Email Includes:
- Article preview
- Metadata
- Images preview
- Approve/Decline buttons
# Recent workflows
aws dynamodb scan \
--table-name blog-workflow-state \
--profile blog-automation \
--max-items 5 \
--query 'Items[*].[workflow_id.S, status.S, article_title.S, created_at.S]' \
--output table| Function | Purpose | Memory | Timeout | Retries |
|---|---|---|---|---|
| blog-multiagent-workflow | Multi-agent manual trigger | 3008 MB | 900s | 0 |
| blog-multiagent-daily | Multi-agent daily schedule | 3008 MB | 900s | 0 |
| blog-manual-trigger | Single-agent manual trigger | 2048 MB | 900s | 0 |
| blog-daily-workflow | Single-agent daily schedule | 2048 MB | 900s | 0 |
| blog-approval-handler | Approval endpoint | 1024 MB | 900s | 0 |
Note: MaximumRetryAttempts is set to 0 to prevent duplicate workflows.
All daily workflow functions require:
API_GATEWAY_URL=https://your-api-gateway-url.amazonaws.com/prodSet via:
aws lambda update-function-configuration \
--function-name blog-multiagent-daily \
--environment "Variables={API_GATEWAY_URL=$API_URL}" \
--profile blog-automation24 SEO-optimized topics in agents/topic_discovery_agent.py:
Categories:
- Cultural Routes (5 topics)
- Geographic Regions (4 topics)
- Experience Types (5 topics)
- Rural Routes (3 topics)
- E-Bike Adventures (4 topics)
- Unique Experiences (3 topics)
Word count: 2,500-3,500 words Images: 3-5 per article Internal links: 2-3 per article Keyword density: 1-3% Format: Sanity Portable Text JSON
blog-automation/
βββ .github/
β βββ workflows/
β βββ deploy.yml # GitHub Actions deployment
β
βββ agents/ # Multi-agent system
β βββ base_agent.py # Base class with Bedrock client
β βββ manager_agent.py # Orchestrator + email sending
β βββ topic_discovery_agent.py # Topic selection
β βββ research_agent.py # Google Ads API + Claude
β βββ seo_writer_agent.py # Article generation
β βββ content_checker_agent.py # Quality validation (0-100 score)
β βββ metrics.py # CloudWatch metrics
β
βββ tests/ # Test suite
β βββ test_phase1.py # Foundation tests
β βββ test_phase2.py # Research & writing tests
β βββ test_phase3.py # Quality control tests
β βββ test_phase4.py # Monitoring tests
β
βββ blog_agent.py # Single-agent Lambda handler
βββ multi_agent_handler.py # Multi-agent Lambda handler
βββ deploy.sh # Infrastructure deployment script
βββ install_mcp.sh # MCP server installation (optional)
βββ requirements.txt # Python dependencies
β
βββ .gitignore # Git ignore rules
βββ MCP_SETUP.md # MCP configuration guide
βββ README.md # This file
# Multi-agent logs
aws logs tail /aws/lambda/blog-multiagent-workflow \
--follow \
--profile blog-automation \
--region us-east-1
# Single-agent logs
aws logs tail /aws/lambda/blog-manual-trigger \
--follow \
--profile blog-automation \
--region us-east-1
# Filter by workflow ID
aws logs filter-log-events \
--log-group-name /aws/lambda/blog-multiagent-workflow \
--filter-pattern "workflow-1234567890" \
--profile blog-automation# Get specific workflow
aws dynamodb get-item \
--table-name blog-workflow-state \
--key '{"workflow_id":{"S":"workflow-1234567890"}}' \
--profile blog-automation \
--query 'Item.[workflow_id.S, status.S, article_title.S]' \
--output json
# Get quality score
aws dynamodb get-item \
--table-name blog-workflow-state \
--key '{"workflow_id":{"S":"workflow-1234567890"}}' \
--profile blog-automation \
--query 'Item.agent_states.M.content_checker.M.output.M.[quality_score.N, status.S]' \
--output jsoninitialized- Workflow starteddiscovering_topic- Finding unique topicresearching- Gathering keyword datawriting- Generating articlechecking- Validating qualityneeds_revision- Quality 70-84%, email sentapproved- Quality β₯85%, email sent (ContentChecker status)rejected- Quality <70%, no emailemail_sent- Approval email deliveredpublished- Article published to Sanity CMSdeclined- User declined from email
Check workflow status:
aws dynamodb get-item \
--table-name blog-workflow-state \
--key '{"workflow_id":{"S":"YOUR_WORKFLOW_ID"}}' \
--profile blog-automation \
--query 'Item.[status.S, agent_states.M.content_checker.M.output.M.status.S]'If status is rejected: Article quality score was <70%, no email sent (by design)
If status is email_sent: Check CloudWatch logs for SES message ID:
aws logs filter-log-events \
--log-group-name /aws/lambda/blog-multiagent-workflow \
--filter-pattern "Approval email sent" \
--profile blog-automationVerify SES sender: Ensure chin@acrossceylon.com is verified in SES
Error: "the JSON object must be str, bytes or bytearray, not dict"
- Fixed in latest version - redeploy:
./deploy.sh
Error: "'original_topic'"
- Fixed in latest version - approval handler now supports both single-agent and multi-agent formats
Redeploy approval handler:
zip -q blog_agent.zip blog_agent.py
aws lambda update-function-code \
--function-name blog-approval-handler \
--zip-file fileb://blog_agent.zip \
--profile blog-automationSymptom: 3 workflows created for 1 trigger
Solution: Lambda retries are disabled (MaximumRetryAttempts=0)
Verify:
aws lambda get-function-event-invoke-config \
--function-name blog-multiagent-workflow \
--profile blog-automation \
--query 'MaximumRetryAttempts'Should return: 0
If not, fix with:
aws lambda put-function-event-invoke-config \
--function-name blog-multiagent-workflow \
--maximum-retry-attempts 0 \
--profile blog-automationError: "AccessDeniedException: You don't have access to the model"
Cause: Using wrong model ID
Solution: Ensure agents/base_agent.py uses:
self.model_id = "anthropic.claude-3-sonnet-20240229-v1:0"NOT:
self.model_id = "us.anthropic.claude-3-5-sonnet-20240620-v1:0" # β Wrong- Secrets Management: All API keys in AWS Secrets Manager
- IAM Roles: Least-privilege access per Lambda
- Approval Tokens: SHA-256 hashed, one-time use
- API Gateway: Lambda authorizer for approval endpoint
- No Hardcoded Credentials: Zero secrets in code
- Lambda Retries Disabled: Prevents duplicate workflows
| Feature | Single-Agent | Multi-Agent |
|---|---|---|
| Speed | 60-90s | 90-120s |
| Quality Validation | β None | β 5 checks + 0-100 score |
| Email Quality Report | β Basic | β Detailed with score |
| Automatic Rejection | β No | β Articles <70% |
| Memory | 2048 MB | 3008 MB |
| Status | Production | Production β |
Recommendation: Use multi-agent for quality validation and automatic rejection of low-quality articles.
MIT License
Chin @ Across Ceylon https://acrossceylon.com
Cycling tour company specializing in custom bicycle tours across Sri Lanka.
Last Updated: October 5, 2025 System Version: Multi-Agent Production Status: β Production Ready