# Chronis AI — Product Requirements Document
**Project:** `chronis-ai-agents`
**Version:** 2.0 (Opus-reviewed, fact-checked)
**Status:** Ready for Cursor

> Reviewed and revised by Claude Opus 4.7. All external assumptions (OpenRouter model slugs, Mailblaze API capabilities) verified against live sources May 2026. Changes from v1.0 noted inline as **[v2]**.

---

## 1. Project Overview

Chronis AI is a self-hosted, multi-tenant AI agent platform built in Laravel. It lets a business deploy, configure, and operate a suite of specialised AI agents (Sales, Marketing, PR, Retention, Deliverability, Lead Generation) with human-in-the-loop approval, persistent memory, full audit logging, and third-party integrations.

Design principles:
- **Multi-company** — one installation serves multiple businesses (Mailblaze first)
- **Multi-agent** — each company runs a suite of agents, each with its own skills, tools, integrations
- **Portable** — skills, prompts, agent templates reusable across companies
- **Human-supervised** — no agent takes an external action without explicit approval via Slack
- **Auditable & idempotent** — every action logged; no double-sends on retry **[v2]**
- **Self-hosted** — Docker Compose on bare metal; full data ownership

First business: **Mailblaze** (email marketing platform). First agent: **Sales Agent**.

---

## 2. Tech Stack

| Layer | Technology |
|---|---|
| Backend | Laravel 11 (PHP 8.3) |
| Frontend | Laravel + Livewire 3 + Alpine.js + Tailwind CSS |
| LLM Routing | OpenRouter API |
| Queue/Jobs | Laravel Horizon + Redis |
| Database | PostgreSQL 16 |
| Cache | Redis 7 |
| Web server | Nginx |
| Containers | Docker Compose |
| Approvals | Slack (Bolt + Block Kit) |
| CRM | Pipedrive API |
| Prospecting | Apollo.io API |
| Email send + engagement | Mailblaze API |
| **Reply detection** | **Gmail API or IMAP — NOT Mailblaze [v2]** |

### 2.1 Model strategy **[v2]**
Verified current OpenRouter slugs (May 2026):
- `anthropic/claude-opus-4.7` — heavy reasoning, final drafting
- `anthropic/claude-sonnet-4.6` — default drafting/research
- `anthropic/claude-haiku-4.5` — cheap, high-volume (ICP scoring, dedup classification)
- Floating aliases (`anthropic/claude-sonnet`) always resolve to latest — use a pinned slug in production, alias only in dev.

Tier the model **per skill**, not per agent: score with Haiku, draft with Sonnet, escalate edge cases to Opus. This is the single biggest cost lever.

---

## 3. Docker Compose Architecture

```yaml
services:
  app:          # Laravel (PHP-FPM)
  nginx:        # Web server, only externally exposed service (80/443)
  postgres:     # Primary database
  redis:        # Cache + queues
  horizon:      # Queue workers (Laravel Horizon)
  scheduler:    # Laravel scheduler (runs `schedule:run` each minute)
```

All on an internal Docker network. Secrets injected via `.env` / Docker secrets, never baked into images.

---

## 4. Database Schema

> **[v2] Key additions:** `agent_runs`, `sequences`/`sequence_steps`, `idempotency_keys`, `skill_type` on skills, soft-deletes + retention notes for POPIA/GDPR compliance.

### 4.1 companies
```
id, name, slug, logo_url, website,
settings (jsonb), data_retention_days (int, default 365),  -- [v2] POPIA
is_active, deleted_at, created_at, updated_at
```

### 4.2 users
```
id, company_id, name, email, password,
role (enum: owner|admin|approver|viewer),
slack_user_id, created_at, updated_at
```

### 4.3 agent_types
```
id, name (slug: sales|marketing|pr|retention|deliverability|lead_gen),
display_name, base_system_prompt (text), description, icon,
created_at, updated_at
```

### 4.4 agents
```
id, company_id, agent_type_id, name,
custom_system_prompt (text), default_model (string),
temperature (float), max_tokens (int),
status (enum: active|paused|draft),
token_budget_daily_usd (decimal),         -- [v2] budget in $, not tokens
spend_today_usd (decimal),
metadata (jsonb), deleted_at, created_at, updated_at
```

### 4.5 skills
```
id, name, slug, description,
skill_type (enum: context|evaluator|transformer|tool),   -- [v2] CRITICAL
prompt_instructions (text),
scope (enum: global|company|agent), company_id (nullable),
model_override (nullable),                 -- per-skill model tiering
version (int), is_active, deleted_at, created_at, updated_at

skill_versions: id, skill_id, version, prompt_instructions, created_by, created_at
```
**[v2] skill_type semantics:**
- `context` — injects info into the prompt (ICP, brand guidelines). No own LLM call.
- `evaluator` — returns a judgement/score (ICP Matcher). Own cheap LLM call, can short-circuit the pipeline.
- `transformer` — rewrites content (Humanizer, Tone). Own LLM call; runs as a discrete pipeline stage so output is inspectable.
- `tool` — calls an external API (Apollo enrich, web search). No LLM call.

### 4.6 agent_skills (pivot)
```
id, agent_id, skill_id, execution_order (int),
is_enabled, config (jsonb), created_at, updated_at
```

### 4.7 integrations
```
id, agent_id,
type (enum: pipedrive|apollo|slack|mailblaze|gmail|imap|webhook),  -- [v2] gmail/imap added
secret_ref (string),          -- [v2] reference to secrets table, not raw creds
settings (jsonb), is_active, last_tested_at, created_at, updated_at
```

### 4.8 integration_secrets **[v2]**
```
id, integration_id, encrypted_payload (text),   -- AES-256-GCM, app key in env
key_version (int),            -- supports rotation
rotated_at, created_at, updated_at
```

### 4.9 icps
```
id, company_id, name, description,
industries (jsonb), company_sizes (jsonb), geographies (jsonb),
job_titles (jsonb), pain_points (jsonb), disqualifiers (jsonb),
tam_description (text), ibp_description (text),
created_at, updated_at
```

### 4.10 agent_runs **[v2 — NEW]**
One row per agent execution; groups many tasks. Enables idempotency, cost roll-up, and "what did this run do" audit.
```
id, agent_id, company_id,
trigger (enum: manual|schedule|event|webhook),
status (enum: running|completed|failed|cancelled),
idempotency_key (string, unique),
total_tokens (int), total_cost_usd (decimal),
started_at, finished_at, created_at
```

### 4.11 agent_tasks
```
id, agent_run_id, agent_id, company_id,
type (enum: prospect|dedup|score|research|draft|follow_up|enrich|newsletter_add|send),
status (enum: pending|running|awaiting_approval|approved|rejected|executed|failed|skipped),
input_payload (jsonb), output_payload (jsonb),
model_used, tokens_used (int), cost_usd (decimal),
skills_applied (jsonb), skip_reason (text),     -- [v2] log why skipped
error_message (text), idempotency_key (string), -- [v2]
created_at, updated_at, executed_at
```

### 4.12 sequences + sequence_steps **[v2 — NEW]**
Follow-up cadence as data, not hardcoded logic.
```
sequences: id, company_id, agent_id, name, is_active, created_at, updated_at

sequence_steps:
  id, sequence_id, step_order, delay_days,
  channel (enum: email|newsletter|linkedin_manual),
  template_ref (string), requires_approval (bool),
  stop_on_reply (bool), created_at, updated_at
```

### 4.13 memories
```
id, agent_id, company_id,
entity_type (enum: contact|company|deal|campaign),
entity_id (string), entity_name,
summary (text), raw_events (jsonb),
last_interaction_at, deleted_at, created_at, updated_at
```

### 4.14 approvals
```
id, agent_task_id, agent_id, company_id,
slack_message_ts, slack_channel_id,
status (enum: pending|approved|rejected|edited|expired),  -- [v2] expired
original_content (text), edited_content (text),
reviewed_by (user_id), review_note (text),
expires_at,                   -- [v2] auto-expire stale approvals
reviewed_at, created_at, updated_at
```

### 4.15 feedback_signals
```
id, approval_id, agent_task_id,
original_output (text), approved_output (text),
edit_distance (int), signal_type (enum: approved_as_is|approved_with_edits|rejected),
notes (text), created_at
```

### 4.16 events
```
id, company_id,
source (enum: pipedrive|mailblaze|apollo|gmail|manual|schedule),
event_type (string), payload (jsonb),
processed (bool), processed_at,
dedup_hash (string, unique),  -- [v2] drop duplicate webhook deliveries
created_at
```

### 4.17 audit_logs
```
id, agent_id, company_id, user_id (nullable),
action (string), description (text), metadata (jsonb),
ip_address, created_at
```

### 4.18 idempotency_keys **[v2 — NEW]**
```
id, scope (string), key (string, unique),
result_ref (string), created_at, expires_at
```
Checked before any external write (send email, create Pipedrive lead). Prevents double-execution on job retry.

### 4.19 notifications
```
id, user_id, company_id, type, title, body, data (jsonb), read_at, created_at
```

### 4.20 Dead-letter handling **[v2]**
Use a dedicated `failed_jobs` review surface (Horizon already persists these). Approvals that error get retried 3× then moved to a `needs_attention` state surfaced in the dashboard — never silently dropped.

---

## 5. Core Modules

### 5.1 Auth & multi-tenancy
Laravel auth + company scoping. Global Eloquent scope on every company-owned model. Middleware: `EnsureCompanySelected`, `EnsureAgentBelongsToCompany`. Roles gate approval rights (only `approver`+ can approve).

### 5.2 Agent engine **[v2 — pipeline, not single prompt]**
```php
class AgentEngine {
  public function run(Agent $agent, string $taskType, array $input, string $idempotencyKey): AgentRun;
  // Builds an ordered pipeline from agent_skills, executes stage by stage:
  //   context skills  -> assemble prompt context
  //   tool skills     -> fetch external data
  //   evaluator skills-> score; may short-circuit (e.g. ICP < 7 -> skip)
  //   transformer skills -> each is its own LLM call, output inspectable
  // Each stage writes an agent_task row. Cost checked against budget BEFORE each LLM call.
}
```
Budget guard: `spend_today_usd + estimated_call_cost <= token_budget_daily_usd`, decremented atomically (DB transaction / Redis lock) so parallel jobs can't overshoot. **[v2]**

### 5.3 Skills engine
Loads `agent_skills` in `execution_order`, dispatches each by `skill_type` (see 4.5). Transformer/evaluator skills each produce their own task row + token cost. Versioned; rollback supported.

### 5.4 Approval workflow
1. Draft task completes → `RequestApprovalJob` (idempotent).
2. Slack Block Kit message: recipient context + draft + Approve / Edit / Reject. `expires_at` set (e.g. 72h).
3. Slack action → `POST /webhooks/slack/actions` (signature-verified).
4. Approve → `ExecuteTaskJob` (checks idempotency_keys before sending). Edit → dashboard editor → re-approve. Reject → log feedback signal.
5. Expired approvals auto-cancel and notify. **[v2]**
All outcomes → `approvals` + `feedback_signals`.

### 5.5 Memory manager
`load(agent, entityId)` before tasks; append event after executed tasks; periodic summarisation job compresses `raw_events` → `summary`. Respects `data_retention_days` — prospect data purged on schedule. **[v2]**

### 5.6 Event/webhook listener
`POST /webhooks/{company_slug}/{source}` — signature-verified, `dedup_hash` drops duplicates. `EventDispatcherJob` maps event → agent trigger. E.g. Pipedrive deal created → Sales prospect task; Gmail reply received → pause sequence + notify.

### 5.7 Observability
Per agent/company: tasks by status, approval rate (as-is / edited / rejected), token spend vs budget, skill firing frequency, funnel (prospected → contacted → replied → converted). Failed-jobs / needs-attention surface.

---

## 6. Sales Agent — Full Specification

### 6.1 Overview
Autonomous outbound sales agent for Mailblaze. Identifies, researches, and nurtures prospects. **Never sends without approval. Never tries to close** — goal is a booked demo or free-trial sign-up, then human handoff.

### 6.2 Mailblaze ICP (pre-loaded)
```json
{
  "name": "Mailblaze ICP v1",
  "industries": ["retail","ecommerce","hospitality","health & wellness","food & beverage","non-profit","professional services"],
  "company_sizes": ["1-10","11-50","51-200"],
  "geographies": ["South Africa","Sub-Saharan Africa","UK","Australia"],
  "job_titles": ["Marketing Manager","Marketing Director","Head of Marketing","Digital Marketing Manager","CMO","Founder","Owner","CEO"],
  "pain_points": ["Low open/engagement rates","Poor support from current provider","Overly complex or expensive platforms","No deliverability insight","Bot-only support","Locked into long contracts","Weak comparative reporting"],
  "disqualifiers": ["Enterprise 500+ (needs enterprise sol.)","Developer-only/transactional-only teams","Already on Mailblaze"],
  "tam_description": "SMB & mid-market in SA + Africa + key English markets sending bulk campaigns who value engagement over raw volume",
  "ibp_description": "Marketing Manager or Owner at a 10-200 person retail/hospitality/services business, frustrated with Mailchimp/Brevo cost or complexity, values human support and clean UX, budget-conscious but quality-driven"
}
```

### 6.3 Skills (typed + tiered) **[v2]**
| Order | Skill | Type | Model |
|---|---|---|---|
| 1 | ICP Matcher | evaluator | haiku-4.5 |
| 2 | Lead Researcher | tool + context | sonnet-4.6 |
| 3 | Email Drafter | transformer | sonnet-4.6 |
| 4 | Humanizer | transformer | sonnet-4.6 |
| 5 | Tone Adjuster (brand voice) | transformer | sonnet-4.6 |
| 6 | Brand/Compliance Checker | evaluator | haiku-4.5 |
| 7 | Subject Line Optimizer | transformer | haiku-4.5 |
Edge cases / low-confidence drafts escalate to `opus-4.7`.

### 6.4 Integrations
Apollo (prospect + enrich) · Pipedrive (CRM read/write + activity log) · Mailblaze (**send + opens/clicks/bounces only**) · Gmail/IMAP (**reply detection** — Mailblaze does not parse inbound) **[v2]** · Slack (approvals) · Web search (research).

### 6.5 Task flow
```
TRIGGER: manual | scheduled | new Apollo lead event   (creates one agent_run with idempotency_key)

1. PROSPECT      Query Apollo with ICP filters -> N prospects
2. DEDUP         Pipedrive lookup by email + org
                   exists & contacted <30d & no reply -> SKIP (log skip_reason)
                   exists & stalled/>30d            -> follow-up branch (step 6)
                   exists & customer                -> SKIP
                   not found                        -> continue
3. SCORE         ICP Matcher (haiku). score <7 -> SKIP(reason). >=7 -> continue
4. RESEARCH      Apollo full profile + web search -> {pain_hypothesis, personalisation_hook}
5. CREATE LEAD   Pipedrive contact+org, tag "AI Prospected", stage "Prospecting"
                   (idempotency_key guards against double-create)
6. DRAFT         pipeline: Researcher->Drafter->Humanizer->Tone->Brand->Subject
                   -> {subject, body, send_to, context_summary}
7. APPROVE       Slack Block Kit -> Approve / Edit / Reject (expires 72h)
8. EXECUTE       on approve: Mailblaze transactional send (idempotency-guarded)
                   -> Pipedrive activity "Email sent via Chronis"
                   -> memory update; stage "Contacted"
9. SEQUENCE      driven by sequences/sequence_steps (editable, not hardcoded):
                   default: +4d follow-up1, +9d social-proof, +16d trial CTA,
                            +21d newsletter add, +30d breakup; stop_on_reply=true
10. REPLY        Gmail/IMAP webhook detects reply ->
                   Slack "X replied!" -> pause sequence -> draft suggested reply
                   -> Pipedrive stage "Engaged" -> human takeover
```

### 6.6 Channels
Primary: email (Mailblaze). Secondary: newsletter nurture (Mailblaze list opt-in). Future: LinkedIn (agent drafts, human sends manually). **Reply handling depends on the Gmail/IMAP integration, not Mailblaze.** **[v2]**

### 6.7 End goal
Booked demo or free-trial sign-up, then hand to human. Agent warms, never closes.

### 6.8 Compliance **[v2]**
Outbound B2B email in SA falls under POPIA; honour opt-out, log lawful basis, respect `data_retention_days`, and include a clear unsubscribe path. Document this; don't treat it as optional.

---

## 7. UI Structure
```
/dashboard                      cross-company overview
/companies, /companies/{id}     company CRUD + overview
/companies/{id}/agents          agents for company
/agents/{id}                    agent detail + run
/agents/{id}/config             prompt, model, ICP, budget
/agents/{id}/tasks              run + task history
/agents/{id}/skills             attach/order skills
/agents/{id}/sequences          edit follow-up cadence   [v2]
/agents/{id}/integrations       connect APIs
/agents/{id}/memory             view/edit memory
/skills                         global skills library
/approvals, /approvals/{id}     queue + edit/approve
/audit                          audit log
/playground                     test agent+skill on sample input
/settings                       users, keys, company settings
```

## 8. OpenRouter integration
```php
// config/openrouter.php
return [
  'api_key' => env('OPENROUTER_API_KEY'),
  'base_url' => 'https://openrouter.ai/api/v1',
  'default_model' => env('OPENROUTER_DEFAULT_MODEL', 'anthropic/claude-sonnet-4.6'),
];
```
Send `HTTP-Referer` + `X-Title` headers. Capture `usage` from response to populate `tokens_used` / `cost_usd`. Use pinned slugs in prod. **[v2]**

## 9. Slack approvals
Block Kit: header (agent + task) · context (recipient, ICP score, why) · draft (code block) · actions (Approve/Edit/Reject). Inbound `POST /webhooks/slack/actions` with `X-Slack-Signature` verification + timestamp replay guard.

## 10. Security & compliance
App-layer AES-256-GCM for secrets via `integration_secrets` with key rotation. All webhooks signature-verified + replay-protected. Company scoping at model level. Rate-limit webhook endpoints. Soft-deletes + scheduled purge for POPIA/GDPR. No secrets in DB rows or images. **[v2]**

## 11. Environment variables
```env
APP_NAME="Chronis AI"
APP_URL=https://chronis.ai
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_DATABASE=chronis
REDIS_HOST=redis
OPENROUTER_API_KEY=
OPENROUTER_DEFAULT_MODEL=anthropic/claude-sonnet-4.6
SLACK_BOT_TOKEN=
SLACK_SIGNING_SECRET=
SLACK_DEFAULT_CHANNEL=
PIPEDRIVE_API_KEY=
APOLLO_API_KEY=
MAILBLAZE_API_KEY=
GMAIL_CLIENT_ID=
GMAIL_CLIENT_SECRET=
QUEUE_CONNECTION=redis
HORIZON_PREFIX=chronis
APP_ENCRYPTION_KEY_VERSION=1
```

## 12. Build order for Cursor
1. Docker Compose (app, nginx, postgres, redis, horizon, scheduler)
2. Laravel skeleton: multi-company auth, global scopes, middleware
3. Migrations — all tables (incl. agent_runs, sequences, idempotency_keys, integration_secrets)
4. OpenRouter service + usage/cost capture + budget guard
5. Skills engine (typed: context/evaluator/transformer/tool)
6. Agent engine (pipeline executor + agent_runs)
7. Approval workflow (queue jobs + Slack Block Kit + signature verify + expiry)
8. Idempotency layer (before every external write)
9. Memory manager + retention purge
10. Event/webhook system (dedup_hash, dispatcher)
11. Sales agent: ICP, sequences data, integrations (Apollo, Pipedrive, Mailblaze, Gmail/IMAP)
12. UI (dashboard, config, approvals, sequences, observability)
13. Playground; audit; notifications; dead-letter/needs-attention surface

## 13. Cursor kickoff prompt
> Build a Laravel 11 app "Chronis AI Agents" (project: chronis-ai-agents): a self-hosted multi-tenant AI agent platform. Stack: PostgreSQL, Redis, Laravel Horizon, Docker Compose, Livewire 3 + Alpine + Tailwind. Full spec in chronis-ai-agents-PRD.md; visual rules in chronis-design-system.md. Follow Section 12 build order exactly. Critical: skills are typed (context/evaluator/transformer/tool) and run as an inspectable pipeline of discrete LLM calls — not one mega-prompt. Every external write (email send, CRM create) must be idempotency-guarded via the idempotency_keys table. Follow-up cadence lives in sequences/sequence_steps, never hardcoded. Reply detection uses Gmail/IMAP, NOT Mailblaze (Mailblaze only sends + reports opens/clicks/bounces). Budgets are in USD, checked atomically before each LLM call. Build services fully — no stubs.

---
*v2.0 — Chronis AI — Opus-reviewed*
