AI Workforce -- PRD
Product: Objectuve (codename: Enkidu) Feature: In-house agentic AI employee team for business operations Status: Shipped — April 2026 Date: April 2026
1. Problem Statement
Objectuve is operated by a lean team spanning product, brand, marketing, content, social, SEO, analytics, and customer support. Each discipline consumes time that doesn't compound the product itself. The discovery analysis (docs/operations/ai-employees-discovery.md) evaluated external vendors (Sintra, Lindy, Gumloop) and concluded none of them fit:
Three problems:
Domain expertise exists but can't execute autonomously. The Claude Code harness contains ~40 specialized skills (
.claude/skills/) covering nearly every business operation --crafting-page-messaging,designing-lifecycle-messages,orchestrating-social-rhythm,inspecting-search-coverage, and more. Each encodes Objectuve-specific constraints: brand voice, anti-social philosophy, stack conventions, legal entity details. What's missing is not knowledge. It's scheduling, memory, integration, and oversight -- the ability to run on a cadence, remember past outputs, ship artifacts to the right destination, and stay accountable to a human reviewer.No vendor can leverage the existing skill library. External platforms wrap commodity LLMs in persona wrappers. They can't read
.claude/skills/, can't enforce the "coach who's also a friend" brand voice at the prompt level, and can't integrate with Objectuve's stack (PostHog, Sentry, Mailtrap, Clerk, Cloud Run). Every vendor requires rebuilding the domain expertise from scratch -- and maintaining it in a foreign DSL.Repetitive operational tasks are blocking product work. Weekly funnel digests, social cadence planning, SEO audits, lifecycle email drafts, and first-response support all follow predictable patterns. A human must currently initiate each task, provide context, review output, and ship it manually. This overhead scales linearly with operational scope and competes directly with engineering time.
AI Workforce solves this by wiring the existing skill library into autonomous agents that run on schedule, produce reviewable artifacts, and ship approved outputs to their destinations -- all built in-house on the Claude Agent SDK, riding on the LiteLLM proxy already in the stack.
2. Goals & Success Metrics
Primary Goals
- Build a reusable platform for defining, scheduling, executing, and overseeing AI employees
- Ship 5 employees in shadow mode (human approves every artifact before it ships)
- Reduce founder operational overhead by 8-12 hours/week within 90 days
- Maintain brand-voice fidelity by loading Objectuve's skill library into every employee's context
Success Metrics
| Metric | Target (90 days post-launch) | Measurement |
|---|---|---|
| Shadow-mode artifact approval rate | >60% ship with <10% edits | Edit distance on approved vs. generated content |
| Weekly artifacts produced | 25+ across all 5 employees | AiRun count where status = succeeded |
| Founder hours saved per week | 8-12 hrs | Self-reported time tracking, pre/post comparison |
| Cost per artifact | <$0.10 average | LiteLLM usage tracking / artifact count |
| Platform uptime | 99.5%+ | Agent Runner health check + Sentry error rate |
| Time from run trigger to artifact ready | <5 minutes for most employees | AiRun duration (started_at to finished_at) |
Guardrails
- Shadow mode is mandatory for all employees at launch. No auto-publishing until an employee demonstrates >80% approval rate over 4+ weeks.
- No customer-facing autonomous output. Customer support drafts require human review and send, with no exceptions, through all phases.
- Monthly cost cap per employee. Hard stop at budget threshold via LiteLLM. Default: $30/employee/month.
- Brand-voice post-filter. Every user-facing artifact passes through the
tightening-brand-voiceskill as a mandatory output validation step. - No access to production user data. Employees read aggregated analytics (PostHog), error patterns (Sentry), and public content -- never individual user records, PII, or authentication credentials.
3. User Stories
- US-1: As an operator, I want to define an AI employee with a name, role, skill set, and schedule, so it runs automatically without me initiating each task.
- US-2: As an operator, I want to see all pending artifacts in a review queue, so I can approve, edit, or reject each one before it ships.
- US-3: As an operator, I want each employee to remember context from previous runs (last post topics, engagement data, audit findings), so outputs improve over time and don't repeat themselves.
- US-4: As an operator, I want to trigger an employee run manually outside its schedule, so I can get ad-hoc outputs when needed.
- US-5: As an operator, I want to see cost and token usage per employee per month, so I can manage the AI budget.
- US-6: As an operator, I want to pause or disable an employee without deleting its configuration or memory, so I can temporarily stop runs during quiet periods.
- US-7: As an operator, I want approved content artifacts to be automatically delivered to their destination (PR opened, email queued, report posted to Slack), so I don't have to manually copy-paste.
- US-8: As an operator, I want to see a run history with status, duration, cost, and output for each employee, so I can audit performance and catch regressions.
- US-9: As an operator, I want to configure which LLM model an employee uses (Claude, Gemini, GPT), so I can optimize for cost vs. quality per role.
- US-10: As an operator, I want to receive Slack notifications when an artifact is ready for review, so I don't have to poll the dashboard.
4. Feature Design
4a. Employee Definitions (The Roster)
Each employee is defined by a role, a set of loaded skills, available tools (MCP servers), a trigger pattern, and output type. All start in shadow mode (artifacts queued for human approval).
Employee 1: Cori -- Content Drafter
Role: Generates marketing copy, blog post drafts, and feature release narratives aligned with Objectuve's brand voice.
Skills loaded:
crafting-page-messaging(primary)framing-release-storiestightening-brand-voice(mandatory post-filter)
Tools (MCP):
- GitHub MCP (read
marketing_landing/,CHANGELOG.md,docs/) - File system read (skill definitions, brand guide)
Trigger: Weekly (Monday 9 AM) + manual Output type: draft_post -- markdown content with metadata (target page, section, word count) Delivery on approval: Opens a PR against marketing_landing/ or docs/ with the drafted content
Example run: "Review the current hero section of marketing_landing/index.html. Draft an updated version that reflects the latest shipped features from CHANGELOG.md. Apply brand voice constraints. Output as a PR diff."
Employee 2: Emi -- Lifecycle Email Designer
Role: Drafts onboarding welcome series, re-engagement emails, milestone celebration emails, and streak recovery nudges.
Skills loaded:
designing-lifecycle-messages(primary)crafting-page-messagingtightening-brand-voice(mandatory post-filter)
Tools (MCP):
- Mailtrap API (list templates, create draft)
- GitHub MCP (read email templates in
rails_api/app/views/) - PostHog MCP (read activation funnel drop-off data)
Trigger: Bi-weekly (1st and 15th, 10 AM) + manual Output type: email_template -- subject line, preheader, HTML body, plain-text fallback Delivery on approval: Creates draft in Mailtrap (not sent until operator triggers send)
Example run: "Analyze PostHog activation funnel. Identify the step with highest drop-off. Draft a re-engagement email targeting users who stalled at that step. Include a coach-tone subject line, a single CTA, and unsubscribe footer."
Employee 3: Scout -- SEO Auditor
Role: Weekly audit of marketing landing meta tags, Schema.org markup, sitemap completeness, and Open Graph tags. Opens PRs with fixes.
Skills loaded:
inspecting-search-coverage(primary)adding-structured-signalsscaling-template-pages
Tools (MCP):
- GitHub MCP (read/write
marketing_landing/) - Web fetch (check live meta tags, validate OG rendering)
Trigger: Weekly (Wednesday 8 AM) + manual Output type: code_patch -- diff against marketing_landing/ with audit findings Delivery on approval: Opens a PR with SEO fixes and an audit summary in the PR description
Example run: "Crawl marketing_landing/index.html. Verify all meta tags are present and correct. Check Schema.org JSON-LD matches current product description. Validate Open Graph tags render correctly. Generate a PR with any fixes needed."
Employee 4: Dash -- Analytics Reporter
Role: Produces weekly funnel digests, activation metric summaries, and engagement trend reports.
Skills loaded:
instrumenting-product-metrics(primary)mapping-conversion-eventsrunning-product-experiments
Tools (MCP):
- PostHog MCP (read funnels, trends, cohorts, events)
- Sentry MCP (read error counts, issue trends)
Trigger: Weekly (Friday 8 AM) + manual Output type: report -- structured markdown with metrics tables, trend arrows, and recommendations Delivery on approval: Posts to a designated Slack channel + saves to docs/reports/
Example run: "Pull this week's activation funnel from PostHog (signup -> first goal -> first check-in -> 7-day retention). Compare to last week. Highlight any step with >5% regression. Include Sentry error count delta. Recommend one action item."
Employee 5: Ally -- Customer Support Drafter
Role: Drafts first-response templates for common support patterns. Maintains FAQ content. Never sends directly -- always human-reviewed.
Skills loaded:
refining-prompt-surfaces(primary)tightening-brand-voice(mandatory post-filter)crafting-empty-states
Tools (MCP):
- Sentry MCP (read recent error patterns, user-reported issues)
- GitHub MCP (read
docs/, existing FAQ content)
Trigger: Daily (7 AM) + manual Output type: recommendation -- response draft with context summary, suggested tone, and related docs Delivery on approval: Saved to support response library (never auto-sent)
Example run: "Review Sentry issues from the last 24 hours. For any issue affecting >5 users, draft a customer-facing response acknowledging the issue, explaining the impact, and providing a workaround if available. Use coach-friend tone."
4b. Autonomy Levels
Every employee progresses through three levels. Promotion requires evidence.
| Level | Behavior | Promotion criteria |
|---|---|---|
| Shadow | Runs on schedule, produces artifacts, all artifacts require human approval before delivery. | Default for all new employees. |
| Semi-autonomous | Low-risk artifacts auto-approve within guardrails (e.g., SEO fixes under 10 lines, reports to Slack). High-risk artifacts still require approval. | >80% approval rate over 4+ weeks. Zero rejected artifacts in last 2 weeks. |
| Autonomous | Runs and delivers without blocking on approval. Oversight shifts to anomaly detection (cost spikes, output length anomalies, brand-voice drift). | >95% approval rate over 8+ weeks. Founder explicitly promotes. Customer support is never eligible. |
4c. Memory System
Each employee maintains persistent memory across runs via AiEmployeeMemory records. Memory is key-value JSONB, scoped per employee.
Standard memory keys:
| Key | Purpose | Example |
|---|---|---|
last_run_summary | What the employee did last time | "Generated 3 blog post drafts, 2 approved, 1 rejected (too promotional)" |
running_context | Accumulated context that carries forward | "Current editorial theme: Summer 2026 launch. Last 3 posts covered: streaks, coach personas, community challenges." |
performance_stats | Approval rate, edit distance, rejection reasons | { approval_rate: 0.82, avg_edit_distance: 0.08, common_rejections: ["too long", "off-brand"] } |
domain_state | Role-specific state | For SEO Auditor: { last_audit_findings: [...], fixed_issues: [...], known_false_positives: [...] } |
Memory is loaded into the agent's system prompt at the start of each run and updated after the run completes. Memory records are append-only (soft-deleted, never overwritten) for audit trail.
4d. Approval Workflow
Run completes
│
▼
Artifact created (status: pending)
│
├─── Slack notification to operator
│
▼
Admin Dashboard: AI Workforce > Review Queue
│
├── Approve → artifact.delivery_method triggers
│ ├── draft_post → GitHub PR opened
│ ├── email_template → Mailtrap draft created
│ ├── code_patch → GitHub PR opened
│ ├── report → Slack post + docs/ save
│ └── recommendation → saved to support library
│
├── Edit + Approve → operator modifies payload, then delivery triggers
│ (edit distance tracked for performance metrics)
│
└── Reject → artifact marked rejected, rejection reason stored
(feeds back into employee memory for future improvement)4e. Skill Loading Architecture
Each .claude/skills/SKILL.md file is loaded as a system prompt fragment when an employee starts a run. The Agent Runner:
- Reads the employee's
skill_refsarray (e.g.,["crafting-page-messaging", "tightening-brand-voice"]) - Loads each skill file from
.claude/skills/{name}/SKILL.md - Strips YAML frontmatter, injects the markdown body into the agent's system prompt
- Appends the employee's memory context
- Appends the run-specific task prompt
The tightening-brand-voice skill is applied as a post-filter: after the agent produces output, a second LLM call validates the output against brand constraints and flags any violations. This is a hard gate -- artifacts with brand-voice violations are auto-rejected with a reason.
4f. Agent Runner Service
A standalone TypeScript service deployed on Cloud Run, using the Claude Agent SDK.
Endpoints:
| Endpoint | Method | Purpose |
|---|---|---|
/runs | POST | Trigger a new run for an employee |
/runs/:id/status | GET | Check run status |
/runs/:id/cancel | POST | Cancel a running agent |
/health | GET | Health check for Cloud Run |
Request flow:
- Rails enqueues
AiWorkforce::ExecuteRunJobvia Sidekiq (on schedule or manual trigger) - The job creates an
AiRunrecord (status:queued) and POSTs to the Agent Runner - Agent Runner loads employee config, skills, memory, and tools
- Agent executes using Claude Agent SDK (multi-step tool use)
- On completion, Agent Runner POSTs results back to Rails via webhook (
/webhooks/ai-workforce) - Rails creates
AiArtifactrecords from the results, updatesAiRunstatus - Slack notification fires if artifacts are pending approval
Authentication: OIDC service-to-service auth (same pattern as LiteLLM proxy). Agent Runner and Rails API both run on Cloud Run with IAM-managed identity tokens.
MCP Server Configuration: Each employee definition includes an mcp_servers array specifying which MCP servers the agent can access during its run. The Agent Runner spins up the specified MCP connections at run start and tears them down after.
4g. Admin Dashboard Views
New views added to the existing admin dashboard at admin_dashboard/.
1. AI Workforce Overview (/ai-workforce)
- Card grid showing each employee: name, role, status (active/paused), autonomy level, next scheduled run, last run result, monthly cost
- Quick actions: trigger run, pause/resume, view history
2. Review Queue (/ai-workforce/review)
- Table of pending artifacts sorted by creation time
- Each row: employee name, artifact type, preview snippet, created at, action buttons (approve/edit/reject)
- Slide-over panel for full artifact preview with diff view (for code patches) or rich text preview (for content/emails)
- Bulk approve for low-risk artifact types
3. Employee Detail (/ai-workforce/:id)
- Employee config: name, role, skills, model, schedule, autonomy level
- Run history table: date, duration, status, cost, artifact count
- Memory viewer: current memory state with expandable JSON
- Performance chart: approval rate over time, cost trend, output volume
4. Run Detail (/ai-workforce/runs/:id)
- Full run log: steps taken, tools invoked, tokens used, duration
- Artifacts produced with approval status
- Error details (if failed)
5. Technical Approach
5a. Data Model
All models inherit from PublicRecord and use acts_as_paranoid.
# rails_api/app/models/ai_employee.rb
class AiEmployee < PublicRecord
acts_as_paranoid
has_many :ai_runs, dependent: :destroy
has_many :ai_employee_memories, dependent: :destroy
# Validations
validates :name, presence: true
validates :role_key, presence: true, uniqueness: true
validates :autonomy_level, inclusion: { in: %w[shadow semi_autonomous autonomous] }
validates :monthly_budget_cents, numericality: { greater_than: 0 }
# Columns:
# - name (string) — "Cori, Content Drafter"
# - role_key (string, unique) — "content_drafter"
# - description (text) — role summary
# - skill_refs (string array) — ["crafting-page-messaging", "tightening-brand-voice"]
# - mcp_servers (string array) — ["github", "posthog"]
# - model_preference (string) — LiteLLM model alias, e.g. "claude-sonnet-4-5"
# - schedule_cron (string, nullable) — cron expression, null = manual only
# - autonomy_level (string) — "shadow" | "semi_autonomous" | "autonomous"
# - active (boolean, default: true)
# - monthly_budget_cents (integer, default: 3000) — $30
# - task_prompt (text) — the recurring task instruction
# - post_filter_skill (string, nullable) — e.g. "tightening-brand-voice"
scope :active, -> { where(active: true) }
scope :scheduled, -> { active.where.not(schedule_cron: nil) }
end# rails_api/app/models/ai_run.rb
class AiRun < PublicRecord
acts_as_paranoid
belongs_to :ai_employee
has_many :ai_artifacts, dependent: :destroy
# Columns:
# - ai_employee_id (bigint, FK)
# - triggered_by (string) — "schedule" | "manual" | "webhook"
# - status (string) — "queued" | "running" | "succeeded" | "failed" | "cancelled"
# - started_at (datetime)
# - finished_at (datetime)
# - prompt_tokens (integer)
# - completion_tokens (integer)
# - cost_cents (decimal)
# - error_message (text, nullable)
# - run_log (jsonb) — step-by-step execution trace
# - memory_snapshot (jsonb) — memory state at run start (for debugging)
scope :recent, -> { order(created_at: :desc) }
scope :pending_review, -> { joins(:ai_artifacts).where(ai_artifacts: { approval_status: 'pending' }).distinct }
end# rails_api/app/models/ai_artifact.rb
class AiArtifact < PublicRecord
acts_as_paranoid
belongs_to :ai_run
# Columns:
# - ai_run_id (bigint, FK)
# - kind (string) — "draft_post" | "email_template" | "code_patch" | "report" | "recommendation"
# - title (string) — human-readable artifact name
# - payload (jsonb) — content, metadata, file paths, diffs
# - approval_status (string) — "pending" | "approved" | "rejected" | "auto_approved"
# - rejection_reason (text, nullable)
# - edit_distance (float, nullable) — 0.0-1.0, how much the operator changed it
# - reviewed_by (bigint, FK to users, nullable)
# - reviewed_at (datetime, nullable)
# - delivered_at (datetime, nullable) — when the artifact was shipped to its destination
# - delivery_method (string) — "github_pr" | "mailtrap_draft" | "slack_post" | "file_save" | "none"
# - delivery_metadata (jsonb, nullable) — PR URL, Slack message ID, etc.
scope :pending, -> { where(approval_status: 'pending') }
scope :approved, -> { where(approval_status: %w[approved auto_approved]) }
end# rails_api/app/models/ai_employee_memory.rb
class AiEmployeeMemory < PublicRecord
acts_as_paranoid
belongs_to :ai_employee
# Columns:
# - ai_employee_id (bigint, FK)
# - key (string) — "last_run_summary", "running_context", "performance_stats", "domain_state"
# - value (jsonb)
validates :key, presence: true, uniqueness: { scope: :ai_employee_id }
end5b. Agent Runner Service
Location: agent_runner/ in the monorepo (new top-level directory, alongside ionic_frontend/, rails_api/, admin_dashboard/)
Stack:
- Node.js 20+ / TypeScript
- Claude Agent SDK (
@anthropic-ai/agent-sdk) - Express (HTTP server for webhook endpoints)
- MCP client libraries for tool access
Key files:
| File | Purpose |
|---|---|
agent_runner/src/server.ts | Express app with /runs, /health endpoints |
agent_runner/src/runner.ts | Core agent execution loop using Claude Agent SDK |
agent_runner/src/skills.ts | Skill loader: reads SKILL.md files, strips frontmatter, returns prompt fragments |
agent_runner/src/memory.ts | Memory loader/updater: fetches from Rails API, posts updates back |
agent_runner/src/delivery.ts | Artifact delivery handlers (GitHub PR, Mailtrap, Slack) |
agent_runner/src/mcp/ | MCP server connection configs per integration |
agent_runner/Dockerfile | Cloud Run container image |
agent_runner/package.json | Dependencies |
Environment variables:
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY | Claude API access (or route through LiteLLM) |
LITELLM_URL | LiteLLM proxy for model routing + cost tracking |
RAILS_API_URL | Callback URL for posting results |
GITHUB_TOKEN | GitHub MCP: open PRs, read repo |
POSTHOG_API_KEY | PostHog MCP: read analytics |
SENTRY_AUTH_TOKEN | Sentry MCP: read errors |
MAILTRAP_API_TOKEN | Mailtrap: create email drafts |
SLACK_WEBHOOK_URL | Slack: post reports and notifications |
SKILLS_PATH | Path to .claude/skills/ directory (mounted or fetched) |
5c. Rails Interactions
# app/interactions/ai_workforce/trigger_run.rb
class AiWorkforce::TriggerRun < Interaction::Base
delegate_input :employee_id, :triggered_by
def call
employee = AiEmployee.active.public_find(employee_id)
return result.fail(error: 'Employee not found or inactive') unless employee
# Check monthly budget
month_spend = employee.ai_runs
.where('created_at >= ?', Time.current.beginning_of_month)
.sum(:cost_cents)
if month_spend >= employee.monthly_budget_cents
return result.fail(error: 'Monthly budget exceeded', code: :budget_exceeded)
end
run = employee.ai_runs.create!(
triggered_by: triggered_by,
status: 'queued'
)
# Enqueue the actual execution
AiWorkforce::ExecuteRunJob.perform_async(run.public_id)
result.details = { run_id: run.public_id }
end
end# app/interactions/ai_workforce/approve_artifact.rb
class AiWorkforce::ApproveArtifact < Interaction::Base
delegate_input :artifact_id, :reviewer_id, :edited_payload
def call
artifact = AiArtifact.pending.public_find(artifact_id)
reviewer = User.public_find(reviewer_id)
# Calculate edit distance if payload was modified
edit_distance = if edited_payload.present?
calculate_edit_distance(artifact.payload, edited_payload)
else
0.0
end
artifact.update!(
approval_status: 'approved',
reviewed_by: reviewer.id,
reviewed_at: Time.current,
edit_distance: edit_distance,
payload: edited_payload || artifact.payload
)
# Trigger delivery
AiWorkforce::DeliverArtifactJob.perform_async(artifact.public_id)
# Update employee memory with approval feedback
AiWorkforce::UpdateEmployeeMemory.call(
employee_id: artifact.ai_run.ai_employee.public_id,
key: 'performance_stats',
event: :approved,
edit_distance: edit_distance
)
result.details = { artifact_id: artifact.public_id }
end
end5d. GraphQL Types & Mutations
New types:
| Type | Location | Fields |
|---|---|---|
AiEmployeeType | app/graphql/types/ai_employee_type.rb | name, roleKey, description, skillRefs, autonomyLevel, active, scheduleCron, monthlyBudgetCents, currentMonthCost, lastRun, nextRunAt |
AiRunType | app/graphql/types/ai_run_type.rb | employee, triggeredBy, status, startedAt, finishedAt, promptTokens, completionTokens, costCents, artifacts, errorMessage |
AiArtifactType | app/graphql/types/ai_artifact_type.rb | kind, title, payload, approvalStatus, rejectionReason, editDistance, reviewedBy, deliveredAt, deliveryMethod |
AiEmployeeMemoryType | app/graphql/types/ai_employee_memory_type.rb | key, value, updatedAt |
New mutations:
| Mutation | Purpose |
|---|---|
TriggerAiRun | Manually trigger a run for an employee |
ApproveAiArtifact | Approve an artifact (with optional edited payload) |
RejectAiArtifact | Reject an artifact with a reason |
UpdateAiEmployee | Update employee config (schedule, model, autonomy level, active) |
PauseAiEmployee | Toggle active status |
New queries:
| Query | Purpose |
|---|---|
aiEmployees | List all employees with status and stats |
aiEmployee(id:) | Single employee with full detail |
pendingArtifacts | All artifacts awaiting review |
aiRuns(employeeId:, limit:) | Run history for an employee |
5e. Sidekiq Jobs
| Job | Queue | Purpose |
|---|---|---|
AiWorkforce::ExecuteRunJob | ai_workforce | POSTs run request to Agent Runner service |
AiWorkforce::DeliverArtifactJob | ai_workforce | Delivers approved artifact to destination (GitHub PR, Mailtrap, Slack) |
AiWorkforce::ScheduleRunsJob | ai_workforce | Crono cron: checks all scheduled employees, enqueues due runs |
AiWorkforce::BudgetAlertJob | low | Crono cron: checks monthly spend per employee, alerts at 80%/100% |
5f. Admin Dashboard Integration
New views added to admin_dashboard/src/views/:
| File | Route | Purpose |
|---|---|---|
AIWorkforceView.vue | /ai-workforce | Employee overview grid |
AIReviewQueue.vue | /ai-workforce/review | Pending artifact approval queue |
AIEmployeeDetail.vue | /ai-workforce/:id | Single employee detail, history, memory |
AIRunDetail.vue | /ai-workforce/runs/:id | Single run detail with execution log |
New GraphQL operations in admin_dashboard/src/graphql/:
| File | Operations |
|---|---|
ai_workforce_queries.ts | GET_AI_EMPLOYEES, GET_AI_EMPLOYEE, GET_PENDING_ARTIFACTS, GET_AI_RUNS, GET_AI_RUN |
ai_workforce_mutations.ts | TRIGGER_AI_RUN, APPROVE_ARTIFACT, REJECT_ARTIFACT, UPDATE_AI_EMPLOYEE, PAUSE_AI_EMPLOYEE |
Navigation update in AdminLayout.vue: add "AI Workforce" nav item with CpuChipIcon from Heroicons.
6. Implementation Phases
Phase 1: Platform Infrastructure (2-3 weeks)
Backend:
- Database migrations:
ai_employees,ai_runs,ai_artifacts,ai_employee_memories - Models with validations, associations, scopes
- Interactions:
TriggerRun,ApproveArtifact,RejectArtifact,UpdateAiEmployee - GraphQL types and mutations
- Sidekiq jobs:
ExecuteRunJob,DeliverArtifactJob,ScheduleRunsJob - Webhook endpoint:
POST /webhooks/ai-workforcefor Agent Runner callbacks - Seed data: 5 employee definitions
Agent Runner: 8. Scaffold TypeScript service with Express 9. Core runner loop using Claude Agent SDK 10. Skill loader (read .claude/skills/, strip frontmatter) 11. Health check endpoint 12. Dockerfile + Cloud Run deploy config
Admin Dashboard: 13. AI Workforce overview page (employee cards) 14. Review queue page (approve/reject) 15. Navigation update
NOT in Phase 1: Memory system, delivery automation, Slack notifications, employee detail page, run detail page.
Phase 2: Shadow Mode -- 5 Employees (2-3 weeks)
- Connect Agent Runner to LiteLLM proxy (model routing, cost tracking)
- MCP server integrations: GitHub, PostHog, Sentry, Mailtrap
- Wire all 5 employees with their skill sets and task prompts
- Memory system: load at run start, update after run
- Brand-voice post-filter pipeline
- Employee detail page with run history
- Run detail page with execution log
- Slack notifications for pending artifacts
- Crono scheduled execution for all 5 employees
NOT in Phase 2: Auto-approval, delivery automation, autonomous mode.
Phase 3: Delivery Automation (1-2 weeks)
- GitHub PR delivery handler (Content Drafter + SEO Auditor)
- Mailtrap draft delivery handler (Lifecycle Email Designer)
- Slack post delivery handler (Analytics Reporter)
- Support library save handler (Customer Support Drafter)
- Delivery status tracking (delivered_at, delivery_metadata)
- Budget alert job (monthly spend per employee)
Phase 4: Semi-Autonomous Graduation (after 4+ weeks of shadow data)
- Auto-approval rules engine (per employee, per artifact type, configurable thresholds)
- Anomaly detection: flag runs with unusual cost, duration, or output length
- Performance dashboard: approval rate trends, edit distance over time
- Autonomy level promotion UI in admin dashboard
7. Out of Scope
- Social media posting. Platform API integrations (Twitter/X, LinkedIn, Instagram) are complex OAuth flows. Deferred to a future PRD. Employees can draft social content, but posting requires manual copy-paste or a future Buffer/Typefully integration.
- Consumer-facing AI changes. The AI Workforce is purely operational. It does not affect the user-facing AI Coach, check-in prompts, or insight cards.
- Multi-agent orchestration. A "manager agent" that delegates to "employee agents" in a single run is architecturally possible with Claude Agent SDK but deferred. Each employee runs independently in v1.
- Custom employee creation UI. Employees are defined in seed data and configured via the admin dashboard. A full "create employee" wizard is Phase 5+.
- Vector-based memory / RAG. Simple JSONB key-value memory is sufficient for v1. Embedding-based retrieval is premature optimization.
- External vendor integrations. No Gumloop, Zapier, Sintra, or other agent platforms. Fully in-house.
8. Decisions (Resolved)
Claude Agent SDK over CrewAI/LangGraph. Same mental model as Claude Code (already the daily driver). Tool-use-first design maps directly to MCP servers. Agents-invoke-agents is a first-class primitive. TypeScript matches the frontend ecosystem. CrewAI is Python-only; LangGraph is over-engineered for the current scope.
Separate TypeScript service over Ruby shim. Claude Agent SDK is TypeScript-native. Wrapping it in Ruby via HTTP calls to the Claude API would lose SDK features (tool orchestration, MCP connections, agent delegation). A separate Cloud Run service is clean, independently scalable, and follows the same pattern as the LiteLLM proxy.
Skills as system prompt fragments over fine-tuning. Loading
.claude/skills/as prompt context preserves the ability to edit skills and see changes on the next run. Fine-tuning would require retraining after every skill update and would lose the explicit, auditable constraint definitions.Shadow mode mandatory over opt-in. The discovery doc's risk analysis cited documented hallucination incidents (Medvi's fabricated drug prices). Every employee starts in shadow mode with no exceptions. Promotion to semi-autonomous requires evidence.
JSONB memory over vector embeddings. Employees have small, structured memory needs (last run summary, domain state, performance stats). JSONB with explicit keys is simpler, queryable with standard SQL, and doesn't require a vector database. Revisit if memory exceeds 100KB per employee.
Per-employee budget caps over shared pool. Individual caps prevent one runaway employee from consuming the entire budget. Default $30/employee/month ($150 total) is well within the discovery doc's $40-$150 estimate.
Admin dashboard integration over standalone UI. The admin dashboard already exists with Clerk auth, Apollo GraphQL, and Tailwind patterns. Adding 4 views is faster than building a new app.
9. Edge Cases & Open Questions
Edge Cases
| # | Scenario | Resolution |
|---|---|---|
| EC-1 | Agent Runner is down when a scheduled run fires | Sidekiq retries with exponential backoff (3 retries). AiRun stays queued. Alert after 3 failures. |
| EC-2 | Agent exceeds token limit mid-run | Claude Agent SDK handles context window limits. If a run hits the limit, it fails gracefully. AiRun status set to failed with error. |
| EC-3 | Approved artifact delivery fails (GitHub API down, Mailtrap error) | DeliverArtifactJob retries 3x. If still failing, artifact stays approved but delivered_at is null. Slack alert to operator. |
| EC-4 | Employee's skills are deleted or renamed | Skill loader logs a warning and continues with available skills. AiRun log includes "skill not found: {name}". Employee still runs with partial context. |
| EC-5 | Two scheduled runs for the same employee overlap | Mutex: ExecuteRunJob checks for an existing running AiRun. If found, skips with a log entry. No concurrent runs per employee. |
| EC-6 | Operator edits payload then approves, but edit breaks delivery format | Payload schema validation before delivery. If invalid, artifact stays approved but delivery fails with a descriptive error. |
| EC-7 | Monthly budget reached mid-run | The budget check is pre-run only. A run that starts under budget completes even if it exceeds the cap. Post-run cost tracking updates the total and alerts. |
| EC-8 | Brand-voice post-filter rejects output | Artifact is auto-rejected with reason "brand-voice violation: {details}". Counts against approval rate but not against the operator's review burden. |
Open Questions
Should the Agent Runner route LLM calls through LiteLLM or call Anthropic directly? Recommend LiteLLM -- unified cost tracking, model routing, and fallbacks already exist. The Agent Runner becomes another LiteLLM client with its own model aliases (e.g.,
workforce/content,workforce/support).How should skills be made available to the Agent Runner on Cloud Run? Options: (a) mount the repo directory, (b) fetch from GitHub API at startup, (c) bundle into the Docker image at build time. Recommend (c) -- simplest, skills change infrequently, and a new deploy picks up changes.
Should employees share a Slack channel or each post to their own? Recommend one shared
#ai-workforcechannel with employee name prefixes. Separate channels creates notification fatigue.XP/badge award for the operator who reviews artifacts? The existing gamification system could award XP for artifact reviews, creating an "AI Wrangler" badge. Deferred to Phase 4 but worth noting.
Should the Agent Runner service live in the same GCP project? Recommend yes -- same project (
enkidu-488723), same region (us-central1), IAM-managed auth. Keeps networking simple and costs consolidated.
Critical Files
| File | Change |
|---|---|
rails_api/db/migrate/YYYYMMDD_create_ai_employees.rb | Migration: ai_employees table |
rails_api/db/migrate/YYYYMMDD_create_ai_runs.rb | Migration: ai_runs table |
rails_api/db/migrate/YYYYMMDD_create_ai_artifacts.rb | Migration: ai_artifacts table |
rails_api/db/migrate/YYYYMMDD_create_ai_employee_memories.rb | Migration: ai_employee_memories table |
rails_api/app/models/ai_employee.rb | AiEmployee model |
rails_api/app/models/ai_run.rb | AiRun model |
rails_api/app/models/ai_artifact.rb | AiArtifact model |
rails_api/app/models/ai_employee_memory.rb | AiEmployeeMemory model |
rails_api/app/interactions/ai_workforce/trigger_run.rb | Run trigger interaction |
rails_api/app/interactions/ai_workforce/approve_artifact.rb | Artifact approval interaction |
rails_api/app/interactions/ai_workforce/reject_artifact.rb | Artifact rejection interaction |
rails_api/app/interactions/ai_workforce/update_employee_memory.rb | Memory update interaction |
rails_api/app/graphql/types/ai_employee_type.rb | GraphQL type |
rails_api/app/graphql/types/ai_run_type.rb | GraphQL type |
rails_api/app/graphql/types/ai_artifact_type.rb | GraphQL type |
rails_api/app/graphql/mutations/ai_workforce/ | All workforce mutations |
rails_api/app/jobs/ai_workforce/execute_run_job.rb | Sidekiq: trigger Agent Runner |
rails_api/app/jobs/ai_workforce/deliver_artifact_job.rb | Sidekiq: deliver approved artifacts |
rails_api/app/jobs/ai_workforce/schedule_runs_job.rb | Crono: check cron schedules |
rails_api/app/controllers/webhooks/ai_workforce_controller.rb | Webhook for Agent Runner callbacks |
agent_runner/src/server.ts | Agent Runner Express server |
agent_runner/src/runner.ts | Core agent execution using Claude Agent SDK |
agent_runner/src/skills.ts | Skill loader |
agent_runner/src/memory.ts | Memory loader/updater |
agent_runner/src/delivery.ts | Artifact delivery handlers |
agent_runner/src/mcp/ | MCP server configurations |
agent_runner/Dockerfile | Cloud Run container |
admin_dashboard/src/views/AIWorkforceView.vue | Employee overview |
admin_dashboard/src/views/AIReviewQueue.vue | Artifact approval queue |
admin_dashboard/src/views/AIEmployeeDetail.vue | Employee detail + history |
admin_dashboard/src/views/AIRunDetail.vue | Run detail + execution log |
admin_dashboard/src/graphql/ai_workforce_queries.ts | GraphQL queries |
admin_dashboard/src/graphql/ai_workforce_mutations.ts | GraphQL mutations |
admin_dashboard/src/layouts/AdminLayout.vue | Add AI Workforce nav item |
infra/litellm/config.production.yaml | Add workforce model aliases |
.claude/skills/tightening-brand-voice/SKILL.md | Referenced by brand-voice post-filter |
Related Documentation
- AI Workforce — feature doc for the shipped implementation
Last updated: 2026-07-15