Skip to main content

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

One workspace per client

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

  1. AI Workforce → Hire AI employee
  2. Role: Customer Support
  3. Name: Aria (or client's preferred name)
  4. Channel: Website
  5. 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

SourceDetails
Website crawlhelp.stacknotes.com — crawl full site
Single pagestacknotes.com/pricing
Uploaded docChangelog / release notes (refresh monthly)
Manual textInternal 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

NameContactTrigger
Support teamsupport@stacknotes.com"User reports a bug, an error message, a payment failure, or account access issue"
Salessales@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

  1. Go to MCP Servers in the left sidebar (or Settings → MCP Servers)
  2. Click Add server
  3. Fill in:
    • Name: StackNotes DB
    • Endpoint: https://your-mcp-server.internal/mcp
    • Auth: Bearer token (the MCP_TOKEN you set)
  4. Save — Optimly will call tools/list to verify the connection

Hire the analytics employee

  1. AI Workforce → Hire AI employee
  2. Role: Analytics Reporter
  3. Name: Noah
  4. Channel: API (private — not customer-facing)
  5. 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.

Changing the schedule

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.com receives the thread
  • Test colleague escalation: say "I need to talk to someone", verify Sales or Support notified
  • Widget loads on stacknotes.com and app.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:

ItemDetails
Workspace loginInvite client's admin as an Optimly user
Conversations walkthroughShow them the split-panel, how to read lead badges, how Manual Mode works
Lead follow-up processDefine who checks Activity → Leads and how often
Escalation testWalk through a live colleague escalation so they know what the email/notification looks like
Noah first runCC the client on the first Monday digest so they see it working
Update cadenceMonthly knowledge refresh for Aria; quarterly instructions review

MCP Servers reference
Capabilities — lead capture & email handoff
Colleagues — escalation paths
Affiliate program