Export & filtering - Replace /api/admin/agents/export/csv with /api/admin/agents/export/xlsx (openpyxl). Multi-line system prompts stay inside one cell instead of fragmenting into thousands of rows when opened in Excel. - Accept filter query params on export: status, discipline, audit, business_entity, agent_classification, autonomy_level, risks_only, search. - Move Export/Import CSV/Delete by CSV buttons into the Agents Management tab, drop the duplicate links from the top nav, and rebuild the cramped filter row as a wrappable two-row layout. - Add a Discipline dropdown to the Agents Management filter row to match the Prompt Audit tab. Completion-reminder emails (fix for the broken Complete-button links) - Add h:Reply-To header to every Mailgun send so users can reply to a real mailbox instead of noreply@. Default Nick.Viljoen@oliver.agency, overridable via NOTIFICATION_REPLY_TO env var. - send_completion_reminders now skips with an error log when AGENTHUB_PUBLIC_URL is unset instead of mailing relative links email clients can't follow. UI polish - Restrict the 5-second alert auto-hide to .alert-dismissible so the load-bearing 'unresolved owner' banner stays visible until acted on. - Move the orange brand gradient to a fixed body::before pseudo-element so it can't be covered by the white content card or scrolled out of view. - Lock the navbar to viewport top (position: fixed) and reserve body padding-top so content doesn't sit beneath it. Audit polish (carried over from previous WIP) - Add batch-state tracking to audit_analyzer for in-flight progress visibility. - Update PLAN-prompt-audit.md to match the shipped behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
35 KiB
Agent Tracker: Prompt Audit & Auto-Audit — Implementation Plan
Context
The admin team needs visibility into what each LibreChat agent's system prompt says and whether it poses business/legal/compliance risks. System prompts will arrive via the agent-sync pipeline (see the agent-sync plan). Agent Tracker needs to: (1) accept and store them, and (2) provide on-demand AI analysis using Google Gemini to classify agents against business category definitions and flag those needing scrutiny.
Business Category Definitions (used for backend analysis, not visible fields)
- Cat 1: Created in the Oliver AI Sandbox, used behind the scenes for experimentation. Needs IT/Compliance consideration for safety.
- Cat 1B: Agents that may incur large cost but aren't Cat 2 or 3.
- Cat 2: Created in Pencil, exposed to clients but not for sale. Needs Legal consideration.
- Cat 3: Created in Pencil, sold to clients or sold via teams using them. Needs Commercial team consideration.
Architecture
Agent-sync pipeline → POST /agents collector API (includes instructions)
→ Stored on agent document (instructions field)
→ Auto-triggers classification for new/updated agents
→ Sends to Google Gemini API
→ Stores: discipline, department, category,
risk_level, client detection, audit results
→ If client work detected:
→ Sets client = "yes"
→ Sets verification_status = "needs_verification"
→ Agent appears on Verification tab
Manual "Run Audit" button (admin dashboard)
→ Batch-processes all agents (or unclassified only)
→ Same Gemini analysis pipeline
→ Used for one-off catch-up of existing 600+ agents
→ Results displayed in Prompt Audit tab
- LLM: Google Gemini (
gemini-2.5-pro) viagoogle-genaiSDK - API Key: Reuses existing
GOOGLE_API_KEY(same key as the ai_qc project) - Trigger: Two modes:
- Automatic — runs after collector API creates/updates an agent (non-blocking background task)
- Manual — admin clicks "Run Audit" for batch processing or re-audit
- No new DB connections — prompts come through the existing collector API
- Field used:
instructions(already stored on agent documents from LibreChat sync)
Files to Modify
1. MODIFY: requirements.txt
Add:
google-genai>=1.0.0
2. MODIFY: database.py — Add audit_history collection
After line 16 (after agent_ratings_collection), add:
audit_history_collection = db.get_collection("audit_history")
In ensure_indexes(), add:
await agents_collection.create_index([("audit_status", 1)])
await audit_history_collection.create_index([("agent_id", 1), ("audit_date", -1)])
Also update the import in audit_analyzer.py to use audit_history_collection.
3. MODIFY: models.py — Add audit models
Note: The instructions field already exists on AgentCollectorCreate, AiAgentCreate, AiAgent, and AiAgentResponse (added in 2026-03-26 update). No changes needed for that field.
Add to AiAgentResponse (around line 146, after instructions):
audit_status: Optional[str] = None
audit_date: Optional[str] = None
audit_category: Optional[str] = None
audit_risk_level: Optional[str] = None
Add new model after line 210:
class AuditReviewRequest(BaseModel):
audit_status: str = Field(..., pattern="^(flagged|reviewed|cleared)$")
reviewer_notes: Optional[str] = None
4. NEW: audit_analyzer.py — Core audit module
New file with two responsibilities: Gemini analysis and result storage.
A. Gemini API Analysis
import os
import json
import re
import asyncio
from datetime import datetime
from bson import ObjectId
import google.generativeai as genai
from database import agents_collection, audit_history_collection
def is_gemini_configured() -> bool:
return bool(os.getenv("GOOGLE_API_KEY"))
def _get_model():
api_key = os.getenv("GOOGLE_API_KEY")
genai.configure(api_key=api_key)
model_name = os.getenv("AUDIT_GEMINI_MODEL", "gemini-2.5-pro")
return genai.GenerativeModel(model_name)
Key functions:
| Function | Purpose |
|---|---|
is_gemini_configured() |
Checks GOOGLE_API_KEY env var |
analyze_single_agent(agent_name, instructions, tools, description, model_name, author) |
Builds prompt with category/discipline/client definitions, calls Gemini, parses JSON response |
run_audit_batch(agents, concurrency=3) |
asyncio.Semaphore for rate-limit-safe parallel processing |
store_audit_result(agent_id, audit_data) |
$set on agent doc (includes discipline, department, client flags) + insert into audit_history |
apply_client_detection(agent_id, audit_data) |
If is_client_work == true: sets client = "yes", verification_status = "needs_verification", and client_name if detected. Does NOT overwrite if agent already has client = "yes" set manually. |
classify_single_agent(agent_id) |
Convenience function for post-sync: loads agent doc, runs analyze_single_agent, stores result, applies client detection. Used by automatic trigger. |
get_all_audit_results() |
Query agents with audit fields |
update_audit_review(agent_id, status, notes, reviewer_info) |
Mark as reviewed/cleared |
Gemini Prompt Design:
System instruction:
You are an AI agent compliance analyst and classifier. Analyse AI agents based on their
system prompts/instructions and metadata. You must:
1. Classify into business risk categories
2. Assign a business discipline
3. Infer the department/team from the instructions
4. Detect whether the agent is used for client-specific work
Respond ONLY with valid JSON matching this schema:
{
"category": "1" | "1B" | "2" | "3",
"category_reasoning": "string",
"discipline": "Strategy" | "Creative" | "Oversight including delivery" | "Optimization" | "Back Office including operations" | "Pencil Agents",
"discipline_reasoning": "string — why this discipline was chosen",
"department": "string or null — inferred team/department from instructions (e.g. Project Management, Creative Services, Finance, Media, Strategy). null if not determinable",
"is_client_work": true | false,
"client_work_reasoning": "string — evidence from instructions that this agent handles client-specific work, references client names, brands, deliverables, or external-facing outputs. Empty string if not client work",
"client_name_detected": "string or null — specific client or brand name found in instructions, null if none",
"flags": ["array", "of", "strings"],
"summary": "2-3 sentence analysis",
"recommendations": "which team(s) should review and why",
"risk_level": "low" | "medium" | "high" | "critical"
}
CATEGORY DEFINITIONS:
Cat 1 - Internal Sandbox/Experimentation (Oliver AI Sandbox, behind the scenes, needs IT/Compliance)
Cat 1B - High Cost Internal (may incur large cost, not Cat 2 or 3)
Cat 2 - Client-Exposed Not Sold (Pencil platform, exposed to clients, needs Legal)
Cat 3 - Client-Sold (Pencil platform, sold to clients, needs Commercial team)
DISCIPLINE DEFINITIONS (pick the best fit):
- Strategy: Agents focused on strategic planning, research, market analysis, insights
- Creative: Agents focused on creative work, content creation, design, copywriting
- Oversight including delivery: Agents focused on project management, delivery, QA, oversight, compliance
- Optimization: Agents focused on performance optimization, data analysis, efficiency, media planning
- Back Office including operations: Agents focused on internal operations, HR, finance, IT support, admin tasks
- Pencil Agents: Agents built on or for the Pencil platform specifically
DEPARTMENT INFERENCE:
Look for clues in the instructions about which team or role uses this agent. Examples:
- "I am a project manager" → department: "Project Management"
- "help the media team" → department: "Media"
- "creative brief" → department: "Creative"
- If no department/team clues found, set department to null
CLIENT WORK DETECTION:
An agent is client work (is_client_work = true) if the instructions reference:
- Specific client names or brand names
- Client deliverables, client presentations, client reports
- External-facing outputs meant for clients
- Client briefs, client feedback, client approvals
- Work explicitly described as "for the client" or "client-facing"
Do NOT flag as client work if the agent merely mentions "users" or "stakeholders" generically.
RISK LEVELS:
- low: Internal-only, limited capabilities
- medium: Internal with external tool access or moderate cost
- high: Client-facing or accesses sensitive data
- critical: Client-sold or handles financial/legal/PII data
FLAGS TO CONSIDER:
internal_only, experimental, sandbox, client_facing, pencil_platform,
revenue_generating, not_for_sale, uses_external_tools, uses_code_interpreter,
uses_file_search, accesses_sensitive_data, handles_pii, high_cost,
resource_intensive, legal_review_needed, commercial_review_needed,
compliance_review_needed, no_instructions
User prompt per agent:
Analyse this AI agent:
AGENT NAME: {name}
DESCRIPTION: {description}
MODEL: {model}
AUTHOR: {author}
INSTRUCTIONS (SYSTEM PROMPT):
---
{instructions}
---
TOOLS: {tools_list}
TOOL RESOURCES: {tool_resources}
ACTIONS: {actions}
Concurrency: asyncio.Semaphore(3) — configurable via AUDIT_CONCURRENCY env var. ~50 agents completes in ~50-85 seconds. For the one-off batch of 600+ agents, expect ~10-17 minutes at concurrency 3.
Error handling per agent: Wrap each call in try/except. On failure, return {"error": "...", "agent_name": "..."}. Parse JSON response with regex fallback if needed. All audited agents start with audit_status = "flagged".
Auto-classification post-sync: For single-agent classification triggered by the collector API, no semaphore needed — it's one call at a time. Uses classify_single_agent() which runs analyze_single_agent + store_audit_result + apply_client_detection in sequence.
One-off batch for existing agents: Use the POST /api/admin/audit/run endpoint (or a management script) to process all 600+ existing agents. The instructions field is already populated from LibreChat sync. Agents without instructions will be skipped (counted as skipped_count). The batch will:
- Auto-assign
disciplinefrom the existing list - Infer
departmentfrom the instructions - Classify into Cat 1/1B/2/3
- Detect client work and auto-flag for verification
- Store all results on agent documents
5. MODIFY: main.py — 3 new endpoints + automatic post-sync trigger
A. map_agent_collector_to_internal() — no change needed
The instructions field is already mapped through the collector API (added in the 2026-03-26 update).
B. Update create_agent_response() to include audit fields:
Add to the response construction:
audit_status=agent.get("audit_status"),
audit_date=agent.get("audit_date"),
C. Automatic post-sync classification trigger
In the collector API endpoint (POST /agents), after an agent is created or updated, trigger classification as a non-blocking background task:
from audit_analyzer import classify_single_agent, is_gemini_configured
# Inside the collector endpoint, after successful create/update:
if is_gemini_configured():
# Fire-and-forget: classify in background, don't block the sync response
asyncio.create_task(classify_single_agent(agent_id))
Key behaviours:
- Non-blocking: The collector API returns immediately; classification runs in background
- Idempotent: If the agent already has
audit_status, re-classification overwrites with fresh results - Graceful degradation: If
GOOGLE_API_KEYnot set, classification is silently skipped - Client detection: If Gemini returns
is_client_work = true, the background task auto-setsclient = "yes"andverification_status = "needs_verification"on the agent doc (does NOT overwrite manually-set values)
D. Add 3 admin endpoints (after the analytics endpoint, ~line 1163):
| Endpoint | Method | Purpose |
|---|---|---|
POST /api/admin/audit/run |
POST | Batch audit: reads stored instructions from agent docs, sends to Gemini. Optional agent_id body param for single agent. Optional unclassified_only param (default false) to only process agents without audit_status. Returns {status, total, audited_count, failed_count, skipped_count, results_summary}. |
GET /api/admin/audit/results |
GET | Returns all agents with audit data + config_status: {gemini_configured: bool}. |
PUT /api/admin/audit/{agent_id}/review |
PUT | Admin marks agent as reviewed/cleared with optional reviewer_notes. |
Pre-flight check: POST /api/admin/audit/run returns 503 if GOOGLE_API_KEY not set.
6. MODIFY: templates/admin/dashboard.html — Add Prompt Audit tab
A. New tab (4th tab, after Agents Management):
<li class="nav-item">
<a class="nav-link" id="audit-tab" data-bs-toggle="tab" href="#audit">
<i class="fas fa-shield-alt me-2"></i>Prompt Audit
<span class="badge bg-danger ms-1" id="auditFlaggedBadge" style="display:none;">0</span>
</a>
</li>
B. Tab content layout:
Row 1: Header + [Run Audit] button (with spinner loading state) + [Run Unclassified Only] button
Row 2: Summary cards — Audited | Flagged | Reviewed | Cleared | Client Detected | No Instructions
Row 3: Filters — [Category ▼] [Risk Level ▼] [Discipline ▼] [Status ▼] [Search...]
Row 4: Results table:
Agent Name | Discipline | Department | Category | Risk Level | Client Work | Flags | Status | Last Audited | Actions
Row 5: Agents without instructions notice (collapsible)
C. Audit Detail Modal:
- Agent name + basic info header
- LLM analysis summary
- Discipline badge + reasoning
- Department (inferred from instructions)
- Category badge + reasoning
- Client work detection: Yes/No badge + reasoning + detected client name (if any)
- Flags list (as badges)
- Recommendations text
- Instructions (collapsible
<pre>block, can be long) - Tools config (collapsible)
- Review controls: status dropdown (flagged/reviewed/cleared) + notes textarea + Save button
- Review history trail (who reviewed, when)
D. JavaScript functions:
| Function | Purpose |
|---|---|
loadAuditResults() |
GET /api/admin/audit/results → populate table + summary cards + flagged badge count |
runAudit(agentId?) |
POST /api/admin/audit/run with loading spinner. Disable button during run. On complete, show summary alert + reload results. |
displayAuditResults(agents) |
Render table rows with color-coded badges |
showAuditDetail(agentId) |
Open detail modal with full analysis |
submitAuditReview(agentId) |
PUT /api/admin/audit/{id}/review → reload results |
filterAuditResults() |
Client-side filtering by category, risk, status, search text |
Wire up:
DOMContentLoaded→ callloadAuditResults()alongside existingloadAdminData()andloadAnalytics()- Refresh button → also call
loadAuditResults()
E. CSS additions:
.risk-critical { background-color: #dc3545; color: white; }
.risk-high { background-color: #fd7e14; color: white; }
.risk-medium { background-color: #ffc107; color: #212529; }
.risk-low { background-color: #28a745; color: white; }
.cat-1 { background-color: #28a745; color: white; }
.cat-1b { background-color: #17a2b8; color: white; }
.cat-2 { background-color: #6f42c1; color: white; }
.cat-3 { background-color: #dc3545; color: white; }
.audit-status-flagged { border-left: 4px solid #dc3545; }
.audit-status-reviewed { border-left: 4px solid #ffc107; }
.audit-status-cleared { border-left: 4px solid #28a745; }
F. Config warning: If config_status.gemini_configured is false in the results response, show an info card explaining that GOOGLE_API_KEY needs to be set, and hide the Run Audit button.
Environment Variables
Add to .env:
GOOGLE_API_KEY=AIzaSyDMWN_PAnyU7bPmtWcEKq4LJfiu1KuwUsU
AUDIT_GEMINI_MODEL=gemini-2.5-pro # default, configurable
AUDIT_CONCURRENCY=3 # concurrent Gemini API calls
Error Handling
| Scenario | Handling |
|---|---|
GOOGLE_API_KEY not set |
503 with message; UI shows config warning, hides Run button |
Agent has no system_prompt |
Skip during audit, count as "skipped", show in "No Prompt" card |
| Gemini API auth failure | Abort batch, return 503 |
| Gemini rate limit | Retry with backoff; semaphore limits concurrency; mark agent as failed |
| Prompt too long | Gemini 2.5 Pro has 1M token limit — unlikely to hit; truncate at 900K chars if needed |
| JSON parse failure from Gemini | Regex extraction fallback \{[\s\S]*\}; if fails, store raw text as summary |
| Partial failures | Continue processing remaining agents; report success/fail/skipped counts |
Design Decisions
- Instructions via agent-sync —
instructionsfield already flows through the collector API, no second DB connection needed - All audited agents start as "flagged" — admin must explicitly review/clear each
- Automatic post-sync classification — non-blocking
asyncio.create_task()after collector API create/update, so sync is never slowed down - Manual batch for catch-up — "Run Audit" button for the initial 600+ agent batch and future re-audits. Synchronous with spinner (~10-17 mins for full batch)
- Gemini 2.5 Pro — fast, 1M token context window, reuses existing Google API key
- Discipline auto-assignment — Gemini picks from the existing discipline list; written to the
disciplinefield on the agent doc (overwrites only if currently empty, to respect manual edits) - Department inference — free text field inferred from instructions; written to
agent_departmenton the agent doc (overwrites only if currently empty) - Client work auto-detection — Gemini scans instructions for client references; if detected, sets
client = "yes"andverification_status = "needs_verification". Does NOT overwrite ifclientis already manually set to"yes"or"no". - Separate from quality_audit_status — that's a manual human checkbox; this is automated analysis. They coexist.
- audit_history_collection — historical record of each audit run
- No keyword list initially — Gemini handles all client/discipline/department detection via LLM reasoning. Keyword overrides can be added later if needed.
Implementation Order
requirements.txt— addgoogle-genaidatabase.py— addaudit_history_collection+ indexesmodels.py— add audit fields to response model, addAuditReviewRequestaudit_analyzer.py— new file (Gemini analysis + result storage + client detection + discipline/department assignment)main.py— updatecreate_agent_response()+ add 3 audit endpoints + add automatic post-sync trigger in collector APItemplates/admin/dashboard.html— Prompt Audit tab (HTML, JS, CSS)- One-off batch run — deploy, then trigger
POST /api/admin/audit/runto classify all 600+ existing agents
Verification
- Add
GOOGLE_API_KEYto.env pip install -r requirements.txtuvicorn main:app --reload --port 8000- Login as admin →
/admin→ click "Prompt Audit" tab - Agents with
instructionsshow as ready for audit; agents without show in "No Instructions" count - Click "Run Audit" → spinner → results populate with category/risk/discipline/department/flags badges
- Verify discipline was auto-assigned from the existing list
- Verify department was inferred from instructions (or null if not determinable)
- Check agents where
is_client_work = true→ confirm they now appear on the Verification tab withverification_status = "needs_verification" - Click detail on an agent → see full LLM analysis + raw instructions + client work reasoning
- Mark as "Reviewed" with notes → status updates, reviewer trail shows
- Test automatic trigger: POST a new agent via collector API → verify it auto-classifies within seconds
- Refresh → all data persists
- Test without
GOOGLE_API_KEY→ config warning shown gracefully, auto-trigger silently skipped - One-off batch: Run
POST /api/admin/audit/runto process all 600+ existing agents, verify results
Files Changed Summary (for deployment)
| File | Change |
|---|---|
audit_analyzer.py |
New file — Gemini analysis, discipline/department assignment, client detection, batch processing |
database.py |
Add audit_history_collection + indexes |
models.py |
Add audit fields to response model + AuditReviewRequest |
main.py |
3 new admin endpoints + automatic post-sync classification trigger in collector API + update create_agent_response() |
templates/admin/dashboard.html |
Add Prompt Audit tab with discipline/department/client columns |
requirements.txt |
Add google-genai |
Phase 2: Read-Only Admin, Client Verification & Daily Digest
1. Read-Only Admin Role
Problem
Currently only two roles exist: user and admin. There's a need for users who can view the admin dashboard but not modify anything.
Solution
Add a third role: readonly_admin.
A. Data Model Changes (models.py)
- Update
UserCreateand user handling to supportrolevalues:user,admin,readonly_admin
B. Auth Changes (auth.py / main.py)
- Add
require_admin_or_readonly()dependency — allows access to admin dashboard GET routes - Existing
require_admin()stays as-is for write operations (create/update/delete) readonly_adminusers:- Can access
/admindashboard and view all tabs - Cannot run audits, edit agents, edit users, approve verifications, or perform any write action
- UI hides action buttons (edit, delete, approve, Run Audit) for readonly users
- Can access
C. User Management UI (templates/admin/dashboard.html)
- In the user edit modal, add a role dropdown:
User|Admin|Read-Only Admin - Admin can change any user's role (except their own demotion from admin)
D. API Changes (main.py)
GET /admin→ allowadmin+readonly_admin- All
POST/PUT/DELETEadmin endpoints → requireadminonly (no change) GET /api/admin/*data endpoints → allowadmin+readonly_admin- New endpoint:
PUT /api/users/{user_id}/role— admin-only, sets role
2. Client Verification System
Problem
Agents created for clients need a verification step before they're considered approved for use.
Solution
Add a verification_status field to agents and a verification workflow.
A. Data Model Changes (models.py)
- Add to
AiAgent/AiAgentResponse:client: Optional[str] = None—"yes"or"no"client_name: Optional[str] = None— free text, required whenclient == "yes"studio_name: Optional[str] = None— free text, not mandatoryverification_status: Optional[str] = None—"needs_verification"|"verified"|Noneverified_by: Optional[str] = None— user who verifiedverified_date: Optional[str] = None— when verified
- Add to
AiAgentCreate:client: str(mandatory,"yes"or"no")client_name: Optional[str] = Nonestudio_name: Optional[str] = None
- Add to
AgentCollectorCreate:client: Optional[str] = Noneclient_name: Optional[str] = Nonestudio_name: Optional[str] = None
B. Registration Form (templates/agent_register.html)
Reorder form fields to:
- Agent Name (mandatory)
- Description (stays the same)
- Purpose (stays the same)
- Client — dropdown:
Yes/No(mandatory)- 4a. If
Yes→ show a text input for Client Name (mandatory when visible)
- 4a. If
- Studio Name — free text (not mandatory)
- Tool (stays the same)
- ...rest of form continues from Version, Status, etc.
JavaScript: toggle client_name field visibility based on client dropdown value.
C. Auto-Tagging Logic
- When
client == "yes"on agent creation → setverification_status = "needs_verification" - When
client == "no"or not set →verification_statusremainsNone
D. Verification Tab on Admin Dashboard (templates/admin/dashboard.html)
New tab: Verification (between Agents Management and Prompt Audit tabs)
Tab content:
- Header: "Agents Pending Verification"
- Table: Agent Name | Client Name | Studio | Created By | Date Created | Status | Action
- Each row with status "needs_verification" shows an [Approve ✓] button
- Clicking Approve → calls PUT /api/admin/agents/{id}/verify → status changes to "verified"
- Verified agents move to a collapsible "Recently Verified" section below
- Filter: Show All | Needs Verification | Verified
E. API Endpoints (main.py)
PUT /api/admin/agents/{agent_id}/verify— admin-only, setsverification_status = "verified", recordsverified_byandverified_dateGET /api/admin/agents/pending-verification— returns agents whereverification_status == "needs_verification"
F. Agent Card Display
- Show verification badge on agent cards:
needs_verification→ orange badge: "Needs Verification"verified→ green badge: "Verified"None→ no badge (non-client agents)
3. Client Agent Email Notification
Problem
When a client-facing agent is created, stakeholders need to be notified immediately.
Solution
Trigger an email via Mailgun when client == "yes" on agent creation.
A. Mailgun Configuration (.env)
MAILGUN_API_KEY=4fccfbb0606b55852d243fc848f0356c-c6620443-faf31917
MAILGUN_DOMAIN=mg.oliver.solutions
MAILGUN_FROM_EMAIL=AgentHub <noreply@mg.oliver.solutions>
CLIENT_AGENT_NOTIFY_EMAILS=EstellevanHeerden@oliver.agency
Note: CLIENT_AGENT_NOTIFY_EMAILS is a comma-separated list, making it easy to add recipients later.
B. Notification Logic (notifications.py)
New function: send_client_agent_notification(agent_data: dict)
- Trigger: Called from the agent creation endpoint when
client == "yes" - Subject:
Client Agent Created - Body (HTML email):
A new client-facing agent has been created and requires verification. Agent Name: {agent_name} Description: {description} Purpose: {purpose} Client: Yes Client Name: {client_name} Studio Name: {studio_name or "N/A"} Tool: {tool} Created By: {user_email} Please review this agent in AgentHub. - To:
CLIENT_AGENT_NOTIFY_EMAILSenv var (comma-split) - Non-blocking: Failure does not break agent creation (try/except, log warning)
C. Mailgun API Call
Uses existing send_mailgun_email() pattern from notifications.py:
POST https://api.mailgun.net/v3/mg.oliver.solutions/messages
Auth: api:{MAILGUN_API_KEY}
Form data: from, to, subject, html
4. Daily Agent Digest Email
Problem
Admins need a daily summary of all agents created in the last 24 hours for quick scanning.
Solution
A scheduled daily email summarising new agents.
A. New Function (notifications.py)
send_daily_agent_digest()
- Query: Find all agents with
created_datein the last 24 hours - Subject:
Agents Created Last 24 Hours - Body (HTML email, kept short and scannable):
{count} agent(s) created in the last 24 hours: ┌──────────────────────────────────────────────────┐ │ 1. Agent Name │ │ Purpose: Brief purpose text │ │ Description: Brief description text │ │ Created by: user@email.com │ │ │ │ 2. Agent Name │ │ Purpose: ... │ │ Description: ... │ │ Created by: ... │ └──────────────────────────────────────────────────┘ View full details at {AGENTHUB_URL}/admin - To: All active admin users (query
userscollection forrole == "admin") - If no agents created: Skip sending (don't send empty digest)
B. Scheduling
Option: Use apscheduler (add to requirements.txt):
from apscheduler.schedulers.asyncio import AsyncIOScheduler
scheduler = AsyncIOScheduler()
scheduler.add_job(send_daily_agent_digest, 'cron', hour=7, minute=0) # 7:00 AM daily
scheduler.start()
Wire into FastAPI startup event in main.py.
Alternatively, can be triggered via a cron job calling a new admin endpoint:
POST /api/admin/digest/send— admin-only, manually trigger the digest
C. Configuration (.env)
DAILY_DIGEST_HOUR=7 # Hour to send (24h format), default 7
DAILY_DIGEST_TIMEZONE=UTC # Timezone, default UTC
Implementation Order (Phase 2)
- Models — Add
client,client_name,studio_name,verification_status,verified_by,verified_datefields; updateroleto supportreadonly_admin - Database — Add index on
verification_status - Auth — Add
require_admin_or_readonly()dependency - Registration Form — Reorder fields, add Client dropdown with conditional Client Name, add Studio Name
- CRUD/main.py — Auto-tag verification status on creation, add verification + role endpoints
- Notifications — Client agent email + daily digest function
- Admin Dashboard — Verification tab, role dropdown in user edit, readonly UI gating
- Scheduler — Wire up daily digest (apscheduler or cron endpoint)
Files Changed Summary (Phase 2)
| File | Change |
|---|---|
models.py |
Add client/verification/studio fields, readonly_admin role support |
database.py |
Add index on verification_status |
auth.py |
Add require_admin_or_readonly() |
crud.py |
Verification queries, daily digest query |
main.py |
Verification endpoints, role endpoint, digest endpoint, auth updates |
notifications.py |
Client agent notification, daily digest email |
templates/agent_register.html |
Reorder form, add Client/Studio fields |
templates/admin/dashboard.html |
Verification tab, role dropdown, readonly gating |
requirements.txt |
Add apscheduler (if using scheduler approach) |
.env |
Add CLIENT_AGENT_NOTIFY_EMAILS, DAILY_DIGEST_HOUR, DAILY_DIGEST_TIMEZONE |
Verification (Phase 2)
- Create a user with
readonly_adminrole → confirm they can view/adminbut all action buttons are hidden - Register agent with Client = Yes, Client Name = "Test Client" → confirm
verification_status = "needs_verification"and email sent to configured address - Register agent with Client = No → confirm no verification tag, no email
- Admin dashboard → Verification tab → approve an agent → confirm status changes to "verified" with badge update
- Wait for scheduled time (or trigger manually) → confirm daily digest email arrives with correct agent list
- Test with no agents created → confirm no email sent
Dependency on Agent-Sync
The instructions field is already flowing through the agent-sync pipeline and stored on agent documents. No further sync changes are required. Agents without instructions will be skipped during audit (counted as "No Instructions") and can be re-audited once their instructions arrive via a future sync.
Flow Summary
┌─────────────────────────────────────────────────────────────────┐
│ DAILY SYNC FLOW │
│ │
│ LibreChat ──sync──▶ POST /agents (collector API) │
│ │ │
│ ▼ │
│ Agent stored/updated in DB │
│ (includes instructions field) │
│ │ │
│ ▼ │
│ asyncio.create_task(classify_single_agent) │
│ │ │
│ ▼ │
│ Gemini Analysis │
│ ├─ discipline (from defined list) │
│ ├─ department (free text, inferred) │
│ ├─ category (1/1B/2/3) │
│ ├─ risk_level │
│ ├─ is_client_work │
│ └─ flags, summary, recommendations │
│ │ │
│ ▼ │
│ Store results on agent doc │
│ If client work detected: │
│ ├─ client = "yes" │
│ ├─ verification_status = "needs_verification" │
│ └─ Agent appears on Verification tab │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ ONE-OFF / MANUAL BATCH │
│ │
│ Admin clicks "Run Audit" on dashboard │
│ │ │
│ ▼ │
│ POST /api/admin/audit/run │
│ (processes all agents, or unclassified_only=true) │
│ │ │
│ ▼ │
│ Same Gemini pipeline as above, batched with semaphore(3) │
│ ~600 agents ≈ 10-17 minutes │
│ │ │
│ ▼ │
│ Results displayed in Prompt Audit tab │
│ Client agents flagged on Verification tab │
└─────────────────────────────────────────────────────────────────┘