Example: SaaS AI Workforce Fleet
This guide builds a complete two-employee fleet for a SaaS product (modeled on StackNotes, a note-taking app). You can adapt it for any software product.
What you'll build:
- Employee 1 — Aria: A website customer assistant that answers product questions, captures upgrade leads, and escalates bugs to support
- Employee 2 — Noah: An analytics reporter that queries your database via MCP every Monday and emails a digest to your management team automatically
Audience: Optimly agency partners and developers building AI workforce deployments for clients.
Time: ~2 hours for first client, ~45 minutes for each subsequent.
Workspace setup
Create a separate Optimly workspace for each client. MCP servers are currently shared across all employees in a workspace — a client's database MCP server should not be accessible to another client's employees.
Recommended naming: [ClientName]-Production (e.g. StackNotes-Production)
Employee 1: Website Customer Assistant
Hire
- AI Workforce → Hire AI employee
- Role: Customer Support
- Name:
Aria(or client's preferred name) - Channel: Website
- Create draft
Behavior instructions
You are Aria, StackNotes' customer assistant on the website.
YOUR JOB:
- Answer questions about StackNotes features, plans, and pricing
- Help users who are stuck with onboarding or basic usage
- Capture contact info from visitors interested in the Teams or Enterprise plan
- Escalate bug reports and account issues to the support team
GUIDELINES:
- Be concise. StackNotes users are productivity-focused — avoid walls of text.
- When a user asks about pricing or upgrading, offer to connect them with
the team before giving generic pricing info.
- If something isn't in your knowledge base, say so clearly.
Don't fabricate feature capabilities.
- If a user says "human", "agent", "support", or "manager", escalate immediately.
FALLBACK:
"I don't have enough information to answer that accurately.
Let me connect you with the support team."
Response style: Direct & Technical
Knowledge sources
| Source | Details |
|---|---|
| Website crawl | help.stacknotes.com — crawl full site |
| Single page | stacknotes.com/pricing |
| Uploaded doc | Changelog / release notes (refresh monthly) |
| Manual text | Internal FAQ: billing edge cases, integrations, known issues |
Capabilities
Lead capture — on
- Fields: Name (required), Email (required), Company (optional), Team size (optional)
- Trigger: When user asks about Teams plan, API access, or mentions a team of 5+ people
- Trigger message:
"I'd love to get you connected with the right person — mind sharing your details?"
Email handoff — on
- Destination:
support@stacknotes.com - Include transcript: on
- Handoff message:
"I'm looping in the support team. They'll be with you within one business day."
Colleagues
| Name | Contact | Trigger |
|---|---|---|
| Support team | support@stacknotes.com | "User reports a bug, an error message, a payment failure, or account access issue" |
| Sales | sales@stacknotes.com | "User asks about enterprise pricing, custom contracts, or a team of 50 or more seats" |
Deploy
- Audience: Public
- Allowed origins:
https://stacknotes.com,https://app.stacknotes.com - Widget placement: Bottom right
- Accent color: Client's brand hex
Employee 2: Analytics Reporter
This employee runs proactively on a schedule — it doesn't wait for a customer to message it. Every Monday morning it queries the client's database via MCP tools, composes a digest, and emails it to the management team.
How scheduled tasks work
Optimly employees can schedule themselves. When you tell an employee in a chat:
"Run the weekly digest every Monday at 9am UTC"
The employee calls the built-in schedule_task tool, which creates a recurring job. The scheduler invokes the employee with the instruction every Monday at 9am UTC — no external cron job, no webhook, no extra infrastructure.
To set this up, you chat with the employee once after publishing.
Build the database MCP server
Your employee needs tools to query the client's database. You expose these as a Streamable HTTP MCP server.
Minimal FastAPI implementation:
# db_mcp_server.py
from datetime import date
from fastapi import FastAPI, Depends, HTTPException, Header
from pydantic import BaseModel
import os
import asyncpg
app = FastAPI()
DATABASE_URL = os.environ["DATABASE_URL"] # read-only Postgres connection
MCP_TOKEN = os.environ["MCP_TOKEN"] # shared secret
def verify_token(authorization: str = Header(...)):
if authorization != f"Bearer {MCP_TOKEN}":
raise HTTPException(status_code=401)
# --- MCP discovery ---
@app.post("/mcp")
async def mcp_handler(body: dict, _=Depends(verify_token)):
method = body.get("method")
params = body.get("params", {})
id_ = body.get("id")
if method == "tools/list":
return {"jsonrpc": "2.0", "id": id_, "result": {"tools": TOOLS}}
if method == "tools/call":
name = params.get("name")
args = params.get("arguments", {})
result = await call_tool(name, args)
return {"jsonrpc": "2.0", "id": id_, "result": {"content": [{"type": "text", "text": result}]}}
return {"jsonrpc": "2.0", "id": id_, "error": {"code": -32601, "message": "Method not found"}}
# --- Tool definitions ---
TOOLS = [
{
"name": "get_weekly_active_users",
"description": "Count distinct users who performed any action between start_date and end_date",
"inputSchema": {
"type": "object",
"properties": {
"start_date": {"type": "string", "description": "ISO 8601 date, e.g. 2025-01-06"},
"end_date": {"type": "string", "description": "ISO 8601 date, e.g. 2025-01-12"}
},
"required": ["start_date", "end_date"]
}
},
{
"name": "get_new_signups",
"description": "Count new user registrations between start_date and end_date",
"inputSchema": {
"type": "object",
"properties": {
"start_date": {"type": "string"},
"end_date": {"type": "string"}
},
"required": ["start_date", "end_date"]
}
},
{
"name": "get_churn_events",
"description": "Count users who cancelled or downgraded between start_date and end_date",
"inputSchema": {
"type": "object",
"properties": {
"start_date": {"type": "string"},
"end_date": {"type": "string"}
},
"required": ["start_date", "end_date"]
}
},
{
"name": "get_revenue_metrics",
"description": "Return MRR, new MRR, and churned MRR for the given date range",
"inputSchema": {
"type": "object",
"properties": {
"start_date": {"type": "string"},
"end_date": {"type": "string"}
},
"required": ["start_date", "end_date"]
}
},
{
"name": "get_feature_usage",
"description": "Count events for a named feature between start_date and end_date",
"inputSchema": {
"type": "object",
"properties": {
"feature": {"type": "string", "description": "e.g. notes_created, notes_shared, exports"},
"start_date": {"type": "string"},
"end_date": {"type": "string"}
},
"required": ["feature", "start_date", "end_date"]
}
}
]
# --- Tool implementations (read-only queries) ---
async def call_tool(name: str, args: dict) -> str:
conn = await asyncpg.connect(DATABASE_URL)
try:
if name == "get_weekly_active_users":
count = await conn.fetchval(
"SELECT COUNT(DISTINCT user_id) FROM events WHERE created_at BETWEEN $1 AND $2",
args["start_date"], args["end_date"]
)
return f"Weekly active users: {count}"
if name == "get_new_signups":
count = await conn.fetchval(
"SELECT COUNT(*) FROM users WHERE created_at BETWEEN $1 AND $2",
args["start_date"], args["end_date"]
)
return f"New signups: {count}"
if name == "get_churn_events":
count = await conn.fetchval(
"SELECT COUNT(*) FROM subscriptions WHERE cancelled_at BETWEEN $1 AND $2",
args["start_date"], args["end_date"]
)
return f"Churn events: {count}"
if name == "get_revenue_metrics":
row = await conn.fetchrow(
"SELECT SUM(mrr) as total_mrr FROM subscriptions WHERE status='active' AND updated_at <= $2",
args["start_date"], args["end_date"]
)
return f"MRR: ${row['total_mrr'] or 0:,.0f}"
if name == "get_feature_usage":
count = await conn.fetchval(
"SELECT COUNT(*) FROM events WHERE event_name=$1 AND created_at BETWEEN $2 AND $3",
args["feature"], args["start_date"], args["end_date"]
)
return f"{args['feature']} events: {count}"
return "Unknown tool"
finally:
await conn.close()
Deploy it:
# Run locally or on client's infra (VPC preferred for DB access)
pip install fastapi uvicorn asyncpg
MCP_TOKEN=your-secret DATABASE_URL=postgresql://readonly:pass@host/db \
uvicorn db_mcp_server:app --host 0.0.0.0 --port 8001
For production: deploy as a Cloud Run service, Fly.io app, or any container — behind the client's VPC so it has private DB access. Expose only the /mcp endpoint publicly (behind the token).
Register the MCP server in Optimly
- Go to MCP Servers in the left sidebar (or Settings → MCP Servers)
- Click Add server
- Fill in:
- Name:
StackNotes DB - Endpoint:
https://your-mcp-server.internal/mcp - Auth: Bearer token (the
MCP_TOKENyou set)
- Name:
- Save — Optimly will call
tools/listto verify the connection
Hire the analytics employee
- AI Workforce → Hire AI employee
- Role: Analytics Reporter
- Name:
Noah - Channel: API (private — not customer-facing)
- Create draft
Behavior instructions
You are Noah, StackNotes' internal analytics reporter.
When invoked, you run a weekly business digest:
1. Determine the date range: the 7 days ending yesterday (or use dates provided).
2. Query the database using your available tools:
- get_weekly_active_users
- get_new_signups
- get_churn_events
- get_revenue_metrics
- get_feature_usage (run for: notes_created, notes_shared, exports)
3. Compose an executive digest with:
- Headline numbers (WAU, new signups, churn)
- Week-over-week comparison if previous data is available
- Top 3 observations (at least one positive, flag any risks)
- One clear recommendation for the team
4. Keep the digest under 400 words. Use short paragraphs, not bullet hell.
RULES:
- Never fabricate numbers. If a tool call fails, report the failure clearly.
- Do not include raw SQL or technical jargon in the digest.
- Sign off as: "Noah · StackNotes Analytics Reporter"
Response style: Direct & Technical
Audience scope: Private
Capabilities
Email handoff — on
- Destination:
management@stacknotes.com(add all recipients) - Include transcript: on — the full digest is the "conversation"
- Handoff message:
"Weekly StackNotes digest — see below."
Schedule the weekly digest
After publishing Noah, open a conversation with him (via the API or test sandbox) and send:
"Schedule a weekly digest every Monday at 9am UTC. Query the database for the past 7 days and email the results to management."
Noah will call the schedule_task tool and confirm:
"Done — I'll run the weekly digest every Monday at 9am UTC."
From that point, Noah runs automatically. No external cron. No webhooks. The scheduler invokes him with the instruction every Monday and he does the rest.
To update or cancel, open a new conversation with Noah and say: "Change the weekly digest to Fridays at 8am" or "Cancel the weekly digest."
Go-live checklist
Employee 1 (Aria — website)
- Test lead capture: ask about Teams plan, verify lead appears in Activity → Leads
- Test email handoff: ask about a bug, verify
support@stacknotes.comreceives the thread - Test colleague escalation: say "I need to talk to someone", verify Sales or Support notified
- Widget loads on
stacknotes.comandapp.stacknotes.com - Widget doesn't load on other domains (test with an unauthorized URL)
Employee 2 (Noah — analytics)
- MCP server responds: Optimly shows tools listed after registration
- Manual test: open conversation with Noah, ask "Run a test digest for last week"
- Verify the digest arrives at
management@stacknotes.com - Schedule confirmed: Noah acknowledges the Monday schedule
- First Monday: verify the email arrives and data is accurate
Client handover
Deliver to the client after go-live:
| Item | Details |
|---|---|
| Workspace login | Invite client's admin as an Optimly user |
| Conversations walkthrough | Show them the split-panel, how to read lead badges, how Manual Mode works |
| Lead follow-up process | Define who checks Activity → Leads and how often |
| Escalation test | Walk through a live colleague escalation so they know what the email/notification looks like |
| Noah first run | CC the client on the first Monday digest so they see it working |
| Update cadence | Monthly knowledge refresh for Aria; quarterly instructions review |
Related
→ MCP Servers reference
→ Capabilities — lead capture & email handoff
→ Colleagues — escalation paths
→ Affiliate program