# 31 — Autonomous AI Marketing Agents

**Version:** 1.0 | **Last Updated:** March 29, 2026 | **Status:** Planning Phase | **Dependency:** Instruction 03 (Self-Hosted AI), Instruction 26 (Ad Manager), Instruction 32 (Blog Engine)

---

## OVERVIEW

Introduce a team of autonomous AI specialists that continuously generate marketing content, optimize campaigns, and nurture leads without human intervention — but always with approval gates before publication.

**Operational Philosophy:** Unlike LP's PresenceAI (which auto-publishes), Fogbreak agents GENERATE content proactively but route everything through approval workflows before going live. This respects brokerage compliance requirements (some jurisdictions require human review before marketing content is sent). The workflow is: Agent creates → Queue for approval → Broker/admin reviews → Agent publishes.

**Five Autonomous Agents:**
1. **SEO Agent** — Audits website weekly. Generates optimized title tags, meta descriptions, header structure, schema markup. Suggests internal links. Tracks keyword rankings.
2. **Blog Agent** — Generates hyper-local blog posts 2-3x weekly based on market data, recent sales, listings, seasonal topics. Maintains content calendar. Drafts with images.
3. **Ad Agent** — Monitors ad campaign performance. Suggests budget shifts, new creative, audience adjustments. Can auto-execute pre-approved optimization rules.
4. **Lead Nurture Agent** — Monitors all leads 24/7. Detects engagement signals (email opens, property views, etc). Drafts personalized follow-ups (email/SMS). Respects frequency caps.
5. **Social Media Agent** — Creates social content (property posts, market updates, lifestyle content). Maintains posting calendar. Drafts with images for approval. Auto-posts after approval.

**Approval Workflow:** Content generated by agents queues in `ai_agent_outputs` table. Broker/admin reviews in "AI Approval Dashboard" tab. Can approve, reject with feedback, or request revision. Approved content auto-publishes via cron.

**Performance Tracking:** Each agent tracks its own KPIs. Weekly dashboard shows results (ranking improvements, blog traffic, ad ROI, lead response rate, social engagement). Agents adjust strategy based on performance.

**Tenant Configuration:** Each tenant can enable/disable agents, configure personality/voice, set approval thresholds (auto-approve after 3 good outputs, or always require approval).

---

## DATABASE SCHEMA

### Table: `ai_agents`
Definition of each agent. One per agent type per tenant.

```sql
CREATE TABLE ai_agents (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tenant_id INT DEFAULT NULL,
  agent_type ENUM('seo', 'blog', 'ad', 'lead_nurture', 'social') NOT NULL,
  name VARCHAR(255) NOT NULL,  -- e.g. "SEO Specialist", "Sarah's Blog Writer"
  is_enabled BOOLEAN DEFAULT false,

  -- Schedule & Frequency
  cron_expression VARCHAR(50),  -- "0 9 * * MON" for weekly Monday 9am
  run_frequency ENUM('hourly', 'daily', '3x_weekly', 'weekly', 'monthly') DEFAULT 'weekly',

  -- Personality & Voice
  system_prompt TEXT,  -- Custom instructions for this agent (agent tone, brand voice, constraints)
  personality VARCHAR(100),  -- 'formal', 'casual', 'friendly', 'professional'
  temperature DECIMAL(2,2) DEFAULT 0.7,  -- LLM temperature (creativity)

  -- Approval Configuration
  require_approval BOOLEAN DEFAULT true,
  auto_approve_after_consecutive INT DEFAULT NULL,  -- Auto-approve after N consecutive good outputs
  approval_assignee_id INT,  -- admin.id who reviews this agent's work

  -- Performance Tracking
  total_outputs INT DEFAULT 0,
  total_approvals INT DEFAULT 0,
  total_rejections INT DEFAULT 0,
  avg_approval_time INT,  -- seconds from creation to approval
  success_rate DECIMAL(3,2) DEFAULT 0,  -- approvals / (approvals + rejections)

  last_run_at TIMESTAMP NULL,
  next_scheduled_run_at TIMESTAMP NULL,

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
  FOREIGN KEY (approval_assignee_id) REFERENCES admins(id),
  INDEX idx_tenant_enabled (tenant_id, is_enabled),
  INDEX idx_next_run (next_scheduled_run_at)
);
```

### Table: `ai_agent_runs`
Execution log for each agent run. Audit trail.

```sql
CREATE TABLE ai_agent_runs (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tenant_id INT DEFAULT NULL,
  agent_id INT NOT NULL,
  run_number INT,  -- Sequential run count for this agent
  status ENUM('scheduled', 'started', 'completed', 'failed', 'skipped') DEFAULT 'scheduled',
  started_at TIMESTAMP NULL,
  completed_at TIMESTAMP NULL,
  duration_seconds INT,  -- How long the run took
  output_count INT,  -- Number of items generated in this run
  error_message TEXT,  -- If status='failed'

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
  FOREIGN KEY (agent_id) REFERENCES ai_agents(id) ON DELETE CASCADE,
  INDEX idx_tenant_agent (tenant_id, agent_id),
  INDEX idx_completed (completed_at)
);
```

### Table: `ai_agent_outputs`
Content generated by agents. Queue for approval.

```sql
CREATE TABLE ai_agent_outputs (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tenant_id INT DEFAULT NULL,
  agent_id INT NOT NULL,
  output_type VARCHAR(50),  -- 'seo_suggestion', 'blog_post', 'ad_variant', 'email_draft', 'social_post'
  status ENUM('draft', 'pending_approval', 'approved', 'rejected', 'published', 'archived') DEFAULT 'draft',

  -- Content
  title VARCHAR(255),
  content TEXT,  -- Full content (HTML, markdown, JSON, or plain text depending on type)
  content_format ENUM('plain', 'markdown', 'html', 'json') DEFAULT 'plain',
  metadata JSON,  -- Type-specific metadata: { keyword_target, page_url, blog_topic, ad_placement, etc. }

  -- Images/Media
  featured_image_url VARCHAR(500),
  image_count INT DEFAULT 0,

  -- Target Entity
  entity_type VARCHAR(50),  -- 'property', 'agent', 'listing', 'page', 'campaign'
  entity_id INT,  -- properties.id, admins.id, etc.

  -- Approval Workflow
  submitted_by INT DEFAULT NULL,  -- ai_agents.id (always the agent)
  reviewed_by INT,  -- admins.id (human who approved/rejected)
  review_feedback TEXT,
  approved_at TIMESTAMP NULL,
  rejected_at TIMESTAMP NULL,

  -- Publishing
  published_at TIMESTAMP NULL,
  published_to VARCHAR(100),  -- 'blog', 'social_facebook', 'social_instagram', 'social_linkedin', 'email', 'page', etc.
  published_url VARCHAR(500),

  -- Performance Metrics (populated after publish)
  metric_views INT DEFAULT 0,
  metric_engagement INT DEFAULT 0,
  metric_clicks INT DEFAULT 0,
  metric_conversions INT DEFAULT 0,
  metric_last_updated TIMESTAMP NULL,

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
  FOREIGN KEY (agent_id) REFERENCES ai_agents(id),
  FOREIGN KEY (reviewed_by) REFERENCES admins(id),
  INDEX idx_tenant_status (tenant_id, status),
  INDEX idx_agent_status (agent_id, status),
  INDEX idx_pending (status, created_at)
);
```

### Table: `ai_agent_approvals`
Approval workflow history. Who approved what, when.

```sql
CREATE TABLE ai_agent_approvals (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tenant_id INT DEFAULT NULL,
  output_id INT NOT NULL,
  reviewer_id INT NOT NULL,  -- admin.id who reviewed
  decision ENUM('approved', 'rejected', 'request_revision') DEFAULT 'approved',
  feedback TEXT,
  decision_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
  FOREIGN KEY (output_id) REFERENCES ai_agent_outputs(id) ON DELETE CASCADE,
  FOREIGN KEY (reviewer_id) REFERENCES admins(id),
  INDEX idx_tenant_reviewer (tenant_id, reviewer_id),
  INDEX idx_output (output_id)
);
```

### Table: `ai_agent_metrics`
Aggregated KPIs per agent per time period. Used for dashboards.

```sql
CREATE TABLE ai_agent_metrics (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tenant_id INT DEFAULT NULL,
  agent_id INT NOT NULL,
  period_start DATE,  -- e.g., 2026-03-24 (Monday of week)
  period_type ENUM('daily', 'weekly', 'monthly') DEFAULT 'weekly',

  outputs_generated INT DEFAULT 0,
  outputs_approved INT DEFAULT 0,
  outputs_rejected INT DEFAULT 0,
  outputs_published INT DEFAULT 0,
  approval_rate DECIMAL(3,2),  -- approved / (approved + rejected)
  avg_approval_time INT,  -- seconds

  -- Agent-specific KPIs
  seo_ranking_changes INT,  -- Net positive keyword ranking changes
  blog_traffic_increase INT,  -- % increase in blog views
  ad_roi_improvement DECIMAL(5,2),  -- ROI % change
  lead_response_rate DECIMAL(3,2),  -- % of leads that engaged
  social_engagement_rate DECIMAL(3,2),  -- engagement / total followers

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
  FOREIGN KEY (agent_id) REFERENCES ai_agents(id),
  INDEX idx_tenant_period (tenant_id, period_start DESC),
  UNIQUE KEY uq_agent_period (agent_id, period_start)
);
```

### Table: `ai_agent_schedules`
Detailed cron-style schedule per agent. Allows complex scheduling.

```sql
CREATE TABLE ai_agent_schedules (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tenant_id INT DEFAULT NULL,
  agent_id INT NOT NULL,
  cron_expression VARCHAR(50) NOT NULL,  -- "0 9 * * MON" (5-field cron)
  timezone VARCHAR(50) DEFAULT 'America/Los_Angeles',
  is_active BOOLEAN DEFAULT true,
  next_scheduled_run TIMESTAMP,
  last_run TIMESTAMP NULL,

  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,

  FOREIGN KEY (tenant_id) REFERENCES tenants(id) ON DELETE CASCADE,
  FOREIGN KEY (agent_id) REFERENCES ai_agents(id),
  UNIQUE KEY uq_agent_schedule (agent_id)
);
```

---

## API ENDPOINTS (ai_agents.php)

All endpoints: `/api/ai_agents.php?action=<action>`

### Agent Management

**POST createAgent**
```
Body: { agent_type, name, personality, system_prompt, cron_expression,
        require_approval, approval_assignee_id, temperature }
Response: { success: true, data: { agent } }
Rules: Session required (admin only). Auto-create matching ai_agent_schedule.
```

**GET getAgents**
```
Query: ?enabled_only=true?
Response: { success: true, data: [ agents ] }
Rules: Return all agents for tenant. Include last_run_at, success_rate.
```

**GET getAgent**
```
Query: ?id=<agent_id>
Response: { success: true, data: { agent, recent_runs: [ runs ], recent_outputs: [ outputs ] } }
Rules: Full agent details + last 10 runs + last 10 outputs.
```

**PATCH updateAgent**
```
Body: { id, name?, system_prompt?, personality?, cron_expression?,
        require_approval?, approval_assignee_id?, temperature?, is_enabled? }
Response: { success: true, data: { agent } }
Rules: Admin only. Update ai_agent_schedule if cron_expression changes.
```

**DELETE deleteAgent**
```
Query: ?id=<agent_id>
Response: { success: true, message: "Agent disabled" }
Rules: Soft-delete: set is_enabled=false. Keep history for audit.
```

### Agent Output Approval Queue

**GET getApprovalQueue**
```
Query: ?agent_id=?&limit=20&offset=0
Response: { success: true, data: { pending: [ outputs ], total_pending_count } }
Rules: Return all outputs with status='pending_approval', sorted by created_at.
       Include agent name, output_type, title, content preview, created_at.
```

**PATCH approveOutput**
```
Body: { output_id, feedback? }
Response: { success: true, data: { output } }
Rules: Set status='approved', reviewed_by=current_user_id, approved_at=now.
       Create ai_agent_approvals row.
       If auto_approve rule met, set status='published', publish_at=now.
       Trigger cron job to publish (tie-in to blog.php, social.php, etc).
```

**PATCH rejectOutput**
```
Body: { output_id, feedback }
Response: { success: true, data: { output } }
Rules: Set status='rejected', reviewed_by=current_user_id, rejected_at=now.
       Create ai_agent_approvals row with feedback.
       Notify agent (if applicable) via dashboard alert.
```

**PATCH requestRevision**
```
Body: { output_id, feedback }
Response: { success: true, data: { output } }
Rules: Set status='pending_revision', reviewed_by=current_user_id.
       Cron job will re-run agent with feedback for this entity.
```

### Bulk Actions

**POST bulkApprove**
```
Body: { output_ids: [ id, id, id ] }
Response: { success: true, data: { approved_count } }
Rules: Approve multiple outputs at once. Useful for bulk review.
```

**POST bulkPublish**
```
Body: { output_ids: [ id ] }
Response: { success: true, data: { published_count } }
Rules: Immediately publish approved outputs. Trigger publish handlers.
```

### Manual Agent Execution

**POST runAgentManually**
```
Body: { agent_id, override_last_run_check? }
Response: { success: true, data: { run_id, started_at } }
Rules: Enqueue agent for immediate execution (async via queue or cron).
       If override_last_run_check=true, run even if recently executed.
```

### Metrics & Dashboard

**GET getAgentMetrics**
```
Query: ?agent_id=<id>&period_type=weekly&weeks_back=12
Response: { success: true, data: { metrics: [ { period_start, outputs_generated,
            approval_rate, success_metric } ], chart_data } }
Rules: Return metrics for last N periods. Include trend line.
```

**GET getAgentDashboard**
```
Query: ?agent_id=?
Response: { success: true, data: { agent, stats: { total_outputs, total_approved,
            success_rate, avg_approval_time }, recent_runs: [ runs ],
            performance_chart, next_scheduled_run } }
Rules: Agent detail view for admin dashboard.
```

**GET getAgentHistory**
```
Query: ?agent_id=<id>&limit=50&offset=0
Response: { success: true, data: { runs: [ runs ] } }
Rules: Paginated run history. Include run status, start/end time, output count, errors.
```

### Configuration

**PATCH configureAgent**
```
Body: { agent_id, config: { temperature, personality, auto_approve_threshold,
        max_outputs_per_run, pause_if_approval_backlog? } }
Response: { success: true, data: { agent } }
Rules: Adjust agent behavior on the fly.
```

**POST testAgent**
```
Body: { agent_id, test_entity_id? }
Response: { success: true, data: { test_output, completion_time_ms } }
Rules: Run agent once on test entity (for tuning). Don't save output.
```

---

## IMPLEMENTATION STEPS

### Phase 1: Foundation (Days 1-3)

1. **Create ai_agents.php**
   - Core endpoints per spec above
   - All CRUD operations for agents, outputs, approvals
   - PDO prepared statements, tenant isolation
   - Standard JSON response pattern

2. **Create ai_agent_scheduler.php**
   - Runs every 5 minutes via cron (more frequent than general cron.php)
   - Check `ai_agent_schedules.next_scheduled_run <= NOW()`
   - For each agent ready to run:
     - Create `ai_agent_runs` row with status='scheduled'
     - Enqueue to task queue (or call agent directly)
     - Update `next_scheduled_run` based on cron_expression
   - Idempotent: never run same agent twice in overlapping window

3. **Integrate with Ollama (instruction 03)**
   - ai_agents.php calls FastAPI proxy (`ai_inference.php`)
   - Each agent has a system prompt defining role + constraints
   - Temperature per agent (0.5 = conservative, 0.8 = creative)
   - Handle token limits gracefully (truncate output if too long)

4. **Update cron.php**
   - Add new job: `ai_agent_scheduler` (every 5 minutes)
   - Add new job: `ai_agent_metrics_daily` (8 AM, aggregate previous 24h metrics)

### Phase 2: Approval Dashboard UI (Days 4-5)

5. **Add "AI Agents" tab to fogbreak.html**
   - Between "Calendar" and "Documents"
   - Subtabs: "Approval Queue", "Agents", "Metrics"

6. **Build Approval Queue view**
   - List of pending outputs with: agent name, type (SEO/Blog/Ad/etc), title, preview, created_at
   - Approve/reject/request revision buttons
   - Inline feedback textarea
   - Bulk approve checkbox
   - Sort by date or agent

7. **Build Agents view**
   - Cards per agent: enabled/disabled toggle, last run, next run, success rate
   - Click card to expand: recent outputs, recent runs, KPIs
   - Config button: opens modal to adjust personality, temperature, approval rules
   - Manual run button
   - Test button (generate one output without saving)

8. **Build Metrics view**
   - Dashboard per agent: success rate, outputs/week, approval rate
   - Charts: output generation trend (line), approval rate (line), KPI progress (bar)
   - Period selector: daily/weekly/monthly
   - Compare agents side-by-side

### Phase 3: Agent Implementations (Days 6-12)

**Each agent follows the same pattern:**
- Fetch configuration from `ai_agents` table
- Fetch context data (properties, sales, ads, leads, etc.)
- Call Ollama via FastAPI proxy with system prompt + context
- Structure output into JSON
- Save to `ai_agent_outputs` with status='draft' or 'pending_approval'
- Return output ID

9. **SEO Agent (seo.php)**
   - Weekly: fetch all pages for tenant (website, property pages, agent pages)
   - For each page:
     - Fetch current title, meta description, headers from site (or DB metadata)
     - Analyze current SEO (title length, keyword density, schema markup)
     - Compare to competitors (hardcoded top 3 local competitors)
     - Generate suggestions: optimized title (55-60 chars), meta desc (150-160 chars), header structure
   - Store output_type='seo_suggestion' with metadata: { page_url, current_title, suggested_title, target_keyword }
   - Optional: tie to Google Search Console API to fetch ranking data
   - KPI: track keyword rankings for target keywords weekly

10. **Blog Agent (blog.php)**
    - 2-3x weekly: generate blog post
    - Context: recent sales (past 7 days), price trends, new listings, seasonal topics
    - Fetch from properties.php + transactions.php
    - Prompt: "You are a luxury real estate content specialist writing for [BRAND]. Generate a blog post about [TOPIC]. Include at least one recent sale as example. 800-1200 words. Hyper-local to [MARKET]. First-person voice."
    - Output: title, content (markdown), featured image prompt (for Collov API in instruction 12)
    - Store output_type='blog_post' with metadata: { topic, target_keyword, internal_links: [ { text, url } ] }
    - Suggest 3 internal links to existing content
    - KPI: track blog traffic (+traffic %), page views per post

11. **Ad Agent (ad_manager.php tie-in)**
    - Daily: analyze all active ad campaigns (Facebook, Instagram, Google Ads)
    - Fetch campaign metrics: spend, impressions, clicks, conversions, CPC, ROAS
    - Identify: underperforming campaigns (ROAS < target), budget waste, high-CPC audiences
    - Generate suggestions: "Pause audience X (CPC $12, 0.5 ROAS)", "Increase budget for campaign Y (1.8 ROAS, scaling room)", "Test new creative: [description]"
    - Output_type='ad_suggestion' with metadata: { campaign_id, suggestion_type, estimated_impact }
    - Optional: pre-approved auto-optimization (if rule="pause_campaigns_below_1.0_roas", agent can execute directly)
    - KPI: track ROI improvement month-over-month

12. **Lead Nurture Agent (drips.php + email.php tie-in)**
    - Continuous (every 2 hours): fetch leads with engagement signals
    - Signals: email opened, property viewed, saved search added, board item added (instruction 30)
    - For each signal: generate personalized follow-up (email or SMS)
    - Prompt: "You are a real estate agent assistant. Generate a brief follow-up message to [CLIENT_NAME] who viewed [PROPERTY]. Reference their profile (interested in [PRICE_RANGE], [LOCATION], [STYLE]). Keep it 1-2 sentences."
    - Output_type='email_draft' or 'sms_draft' with metadata: { lead_id, signal_type, frequency_cap_exceeded? }
    - Respect frequency caps: max 1 email + 1 SMS per lead per day
    - Store as pending output for approval OR auto-send if rule="auto_send_first_follow_up_after_signal"
    - KPI: lead response rate (emails opened + clicked)

13. **Social Media Agent (social.php tie-in)**
    - Daily: generate social post (Facebook, Instagram, LinkedIn)
    - Types: property post (new listing, price reduction, sold), market update, lifestyle, team update
    - Context: recent listings, sales, market trends, brand calendar
    - Prompt: "You are [BRAND] social media manager. Create an Instagram caption for [PROPERTY]. Include 3-4 hashtags, call-to-action, luxury tone. Caption under 250 chars. Also suggest 3 hashtags."
    - Output: caption, hashtags, image prompt (for Collov/Runway in instruction 13)
    - Output_type='social_post' with metadata: { platform, property_id?, post_type, image_prompt, scheduled_time? }
    - Store in queue. Auto-post after approval via social.php (instruction 11)
    - KPI: engagement rate (likes + comments + shares) / impressions

### Phase 4: Publishing & Cron Integration (Days 13-15)

14. **Publishing Handler**
    - New cron job: `ai_agent_publish` (every 30 minutes)
    - Fetch all outputs with status='approved'
    - Route to appropriate system:
      - 'blog_post' → blog.php: createPost(), set published_at, status='published'
      - 'social_post' → social.php: schedule post for platform
      - 'email_draft' → drips.php: create draft step, enroll leads, send
      - 'seo_suggestion' → create task for agent to review/implement
      - 'ad_suggestion' → if auto_optimize rule, execute; else create task
    - Update ai_agent_outputs: status='published', published_at=now, published_url/published_to
    - Log to board_activity for transparency

15. **Performance Metrics Aggregation**
    - Daily: `ai_agent_metrics_daily` cron job
    - For each agent, calculate metrics for previous 24h:
      - outputs_generated = count(outputs where created_at in last 24h)
      - outputs_approved = count(approvals where decision='approved' in last 24h)
      - approval_rate = approved / (approved + rejected)
      - avg_approval_time = AVG(approved_at - created_at)
    - Agent-specific:
      - SEO: query Google Search Console for ranking changes (if connected)
      - Blog: query Google Analytics for traffic to published posts
      - Ad: fetch campaign metrics from ad_manager tables
      - Lead Nurture: count email opens + clicks
      - Social: fetch engagement from social.php metrics
    - Store aggregated row in ai_agent_metrics
    - Update ai_agents.total_outputs, total_approvals, success_rate

16. **Integration with CRM**
    - Every published output creates interaction record for relevant client/property
    - Example: "SEO Agent generated optimized title tag for 456 Carmel Valley Rd"
    - Visible in client/property activity feed for transparency

### Phase 5: Mobile & Advanced Features (Days 16-18)

17. **Mobile approval dashboard**
    - React Native (instruction 16): simple approval queue
    - Swipe to approve/reject
    - Read feedback inline
    - Bulk approve checkbox

18. **Approval rules engine**
    - If auto_approve_after_consecutive = 3, auto-approve next output from agent after 3 successful approvals
    - If approval_backlog > 10, flag for admin attention (performance issue)
    - If agent success_rate < 0.5 for week, disable auto-publish (require manual review)

19. **Agent feedback loop**
    - Store rejected outputs with feedback
    - Next run of agent, include feedback in system prompt: "Your recent output was rejected: '[feedback]'. Adjust your approach."
    - Agent learns from rejections (prompt injection, not model fine-tuning)

---

## TESTING CRITERIA

### Unit Tests (PHP)
- [ ] createAgent: valid agent_type, cron validation, tenant isolation
- [ ] getApprovalQueue: returns only 'pending_approval' outputs, sorted correctly
- [ ] approveOutput: sets status, creates approval record, triggers publish (if applicable)
- [ ] rejectOutput: sets status='rejected', creates approval record with feedback
- [ ] runAgentManually: enqueues run, updates next_scheduled_run, prevents duplicate runs
- [ ] Scheduler: calculates next run correctly from cron expression, prevents overlap
- [ ] All queries: PDO binding, NULL tenant_id handling, index usage

### Integration Tests
- [ ] End-to-end SEO agent: fetch pages → analyze → generate suggestions → queue → approve → publish (task creation)
- [ ] End-to-end blog agent: generate post → queue → approve → publish to blog.php → verify published_at + published_url
- [ ] End-to-end lead nurture: detect signal → generate email → queue → approve → send via drips.php → verify in email log
- [ ] Approval workflow: approve output → publish cron runs → verify published_to + published_url populated
- [ ] Metrics aggregation: run agents, generate outputs, approve/reject, daily cron calculates correct approval_rate
- [ ] Multi-tenant isolation: agent in tenant A cannot see outputs from tenant B

### Frontend Tests
- [ ] Approval queue loads, displays pending outputs, sorted by created_at
- [ ] Approve button: updates status, refreshes list, shows toast notification
- [ ] Reject button: opens feedback modal, saves feedback, refreshes list
- [ ] Bulk approve: selects multiple, approves all in one call
- [ ] Metrics view: charts render, period selector filters data correctly
- [ ] Manual run button: enqueues agent, shows loading state, displays run ID
- [ ] Test button: generates one output, displays in modal, doesn't save to DB

### Security Tests
- [ ] Tenant isolation: user from tenant A cannot see agents/outputs from tenant B
- [ ] Permission checks: only admin can create/configure agents, only assigned reviewers can approve
- [ ] Content sanitization: agent output (especially user-facing copy) sanitized before render/publish
- [ ] Fair Housing: all agent-generated outreach (email, SMS, social) scanned for discrimination language before approval

### Performance Tests
- [ ] Scheduler: handles 10+ agents, calculates next runs in < 100ms
- [ ] Agent execution: Ollama call completes within token limit, doesn't hang
- [ ] Metrics aggregation: calculates metrics for 100 outputs in < 5 seconds
- [ ] Approval queue: loads 100 pending outputs with pagination in < 500ms

---

## INTEGRATION POINTS

| System | Connection | Notes |
|--------|-----------|-------|
| **ai_inference.php** (instruction 03) | FastAPI proxy for LLM calls | Each agent calls via HTTP to inference service |
| **blog.php** (instruction 32) | Blog agent publishes to blog engine | Auto-create posts after approval |
| **social.php** (instruction 11) | Social agent publishes to social platforms | Auto-schedule posts after approval |
| **drips.php** (email.php) | Lead nurture agent sends emails/SMS | Auto-enroll leads, send drafts after approval |
| **ad_manager.php** (instruction 26) | Ad agent analyzes campaigns | Fetch metrics, suggest optimizations |
| **properties.php** | Context data for content generation | Recent listings, sales, market trends |
| **transactions.php** | Market data for blog/ad agents | Sold prices, trends, comparables |
| **clients.php** | Lead data for nurture agent | Lead profile, engagement history |
| **admins.php** | Reviewer assignments | approval_assignee_id, approval workflow |
| **tenants.php** | Multi-tenant configuration | Brand voice, market config per tenant |
| **cron.php** | Task scheduling | ai_agent_scheduler, ai_agent_metrics_daily, ai_agent_publish |
| **notifications.php** (instruction 27) | Admin alerts | Notify reviewer when output pending, backlog builds up |
| **google_search_console API** (optional) | SEO agent ranking data | Real keyword ranking tracking (if connected) |
| **Google Analytics API** (optional) | Blog agent traffic data | Post traffic attribution (if connected) |

---

## DEPLOYMENT CHECKLIST

- [ ] ai_agents.php created, all endpoints tested
- [ ] ai_agent_scheduler.php created, scheduled in cron.php (every 5 min)
- [ ] All 6 tables auto-migrated on first API call
- [ ] "AI Agents" tab added to fogbreak.html
- [ ] Approval Queue view built and functional
- [ ] Agents view built with config modal
- [ ] Metrics dashboard built with charts
- [ ] SEO agent implemented and tested
- [ ] Blog agent implemented and tested (ties to blog.php)
- [ ] Ad agent implemented (ties to ad_manager.php)
- [ ] Lead Nurture agent implemented (ties to drips.php, email.php)
- [ ] Social Media agent implemented (ties to social.php)
- [ ] Publishing handler cron job added (`ai_agent_publish`)
- [ ] Metrics aggregation cron job added (`ai_agent_metrics_daily`)
- [ ] CRM interaction logging added (board_activity)
- [ ] Fair Housing audit integrated (scan all generated copy)
- [ ] Mobile approval dashboard added (React Native)
- [ ] Approval rules engine implemented (auto-approve thresholds)
- [ ] Feedback loop implemented (rejected outputs inform next run)
- [ ] GitHub commit + deploy to Bluehost

---

## NOTES

- **Approval Philosophy:** Unlike LP, Fogbreak agents never auto-publish without approval. Content queues, broker reviews, agent publishes. This respects compliance requirements and broker brand voice.
- **Idempotency:** Each agent run is idempotent. Running agent twice in same window produces same output. Scheduler prevents duplicate runs.
- **Token Limits:** Ollama/vLLM have max token limits. Agent prompts must be concise. If context too large, summarize properties/leads before passing to agent.
- **Frequency Caps:** Lead Nurture agent respects frequency caps (max 1 email + 1 SMS per lead per day). Prevents spam. Configurable per tenant.
- **Fair Housing Scanning:** All agent outputs (especially email drafts, social posts) scanned for prohibited language (age, race, familial status, disability references). Flag for review before approval.
- **Performance Degradation:** If approval backlog > 20, disable new agent runs until backlog clears. Prevents queue explosion.
- **Timezone Handling:** All cron expressions in tenant's configured timezone. Scheduler converts to UTC for storage, converts back for display.
- **Success Rate Calculation:** success_rate = approvals / (approvals + rejections). Doesn't count drafts. If agent has 0 approvals, success_rate = 0 (not N/A).
- **Cost Tracking:** Optional: track Ollama inference cost per agent (tokens * model.cost_per_token). Store in ai_agent_runs.inference_cost. Use for ROI calculation.
- **A/B Testing:** Optional advanced: allow agent to generate 2-3 variants of same output (e.g., 3 email subject lines). Admin picks best. Agent learns from winner.

