Operational workflow for onboarding Kaizen clients. Internal stack: Basecamp (canonical) + Mission Control (UI layer) + Campfire (chat). Client delivery: per-client Campfire + Kai corporate brain.
{slug}.pages.dev.Kaizen needs one repeatable, low-friction onboarding pipeline that takes a signed deal from "yes" to "first update shipped" without manual ops drudgery.
Stack pivot in this rev: Off Linear, off Slack, off Fizzy. Onto Basecamp + Mission Control + Campfire — all 37signals tooling for internal ops, all open or one-time license. Per-client Campfire instances become the client portal. Kai is the corporate brain.
Patterns: Stripe Atlas, Linear, Mercury, Ramp.
The first days set client expectations. Every status change (intake submitted → contract sent → workspace provisioned → kickoff scheduled) is system-triggered, not human-triggered. They watch their workspace get built.
Patterns: Basecamp / Shape Up; Linear's single inbox.
| Concern | Source of Truth |
|---|---|
| Tasks, deadlines, project structure | Basecamp |
| Smart task surfacing (queue, energy, urgency) | Mission Control (bi-directional sync) |
| Synchronous internal chat | Campfire (self-hosted, single instance) |
| Synchronous client chat | Per-client Campfire instance |
| Contracts & SOWs | PandaDoc → PDFs in Basecamp project |
| Client metadata | Supabase clients |
| Files & deliverables | Basecamp Docs & Files |
| Long-form SOPs (Kai) | Obsidian vault |
If two systems claim the same fact, the system NOT in this table is wrong by definition.
Patterns: Pulumi, Terraform.
Declarative state: "this client should have a Basecamp project, a Card Table, a Campfire instance at {slug}.kaizenportal.com, a Supabase row, a Kai instance (if applicable)." The provisioner checks each, creates what's missing, reports diffs.
Patterns: Apollo / Outreach playbooks; Basecamp's "the work is the proposal."
Revised sequence: Discovery call → client says "yes, go" → MSA + SOW (one PandaDoc envelope) AND intake form link (separate email) sent in parallel within 1 hour. Provisioning fires on signed contract regardless of intake state. Intake fields enrich the workspace as they arrive.
Patterns: Mercury, First Round Review.
Automate project creation, card tables, file folders, agent provisioning, calendar invites, welcome emails, kickoff agendas. Leave for Don: discovery, kickoff, first Loom, strategic calls.
Litmus: if Don has to manually do something an intern could do, the workflow is wrong.
Patterns: Stripe status pages, Basecamp's native public link.
Don't email status — show it. Percy toggles on Basecamp's native public-link feature for the relevant card tables / lists per the client SOW. No custom subdomain build required.
Revised down from v1.0's 7 days.
Something built, shipped, or measurably progressed within 3 business days. Websites: staging URL with brand applied. Kai: live in client's Campfire, answering one real question. Day 4 with nothing shipped = off-tempo.
Each client gets a dedicated Campfire instance at {slug}.kaizenportal.com. Their Kai bot lives inside it. Carson is building this layer.
Why per-client Campfire: it's single-tenant by design — "to support entirely distinct groups of customers, deploy multiple instances." That's a feature. Hard architectural isolation = HIPAA/SOC2/GDPR-friendly by default.
Clients use the portal to ask Kai questions, request website changes (Kai files them as Basecamp cards), see status, and chat with the Kaizen team.
qualified), drafts SOWcontract_sent + intake_sentPOST /api/onboarding/contract-signed → status = signedPOST /api/onboarding/intake → status += intake_completesigned. Personalization continues as intake arrives.Kaizen × {Client Name}{slug}.kaizenportal.com (Docker, one VM)client-{slug}-site from kaizen-website-templateRemoved in v1.1: "Adjacent Media Slack Connect" handoff. AM is internal Kaizen now.
Live staging URL at {slug}.pages.dev with brand applied (logo + colors + typography), nav structure, hero section.
Hard rule: per-client isolation by architecture, never multi-tenant. Each client gets own Supabase, own repo, own vault, own Hermes workspace, own Campfire instance, own Kai bot.
kaizen-client-templatekaizen-{slug}-colonykaizen-{slug}-vault{slug}.kaizenportal.com (Docker, single VM)Kai or white-label override.secrets/ (chmod 600)Kai is live in client's Campfire portal, has ingested seed docs, correctly answers the "day-3 question" from intake. One question, real data, working bot — within 3 business days.
client_name: string (required)
primary_contact_name: string (required)
primary_contact_email: email (required)
primary_contact_phone: string (optional)
billing_email: email (required)
company_size: enum [1-10, 11-50, 51-200, 201-1000, 1000+]
products_interested: MULTI-SELECT [website, seo_geo, kai_corporate_brain, other]
budget_range: enum [<10k, 10-25k, 25-50k, 50-100k, 100k+]
timeline_urgency: enum [yesterday, this_month, this_quarter, exploratory]
how_did_you_find_us: string
decision_makers: string
success_criteria_3mo: textarea
Conditional fields load per selected product (see §3). SEO/GEO additions: target keywords, geographic markets, existing rankings, current tooling, content production capacity.
POST https://mission-control.kaizenailab.com/api/onboarding/intake
Headers: X-Intake-Signature: <hmac>
Body: { submission_id, fields: { ...schema... } }
Response: 200 { client_id, supabase_row_id, ops_message_ts }
POST https://mission-control.kaizenailab.com/api/onboarding/contract-signed
Headers: X-PandaDoc-Signature: <hmac>
Body: { document_id, client_id, signed_at, pdf_url }
Response: 200 { provisioning_run_id }
POST https://mission-control.kaizenailab.com/api/sync/basecamp
Headers: X-Basecamp-Webhook-Token: <signing_secret>
Body: { kind: 'todo_created' | 'card_moved' | ..., recording: {...} }
Response: 200
qualified
↓ (Don approves contract send)
contract_sent + intake_sent
↓ (PandaDoc webhook)
signed
↓ (provisioner picks up — does NOT wait for intake)
provisioning
↓ (all steps succeed)
provisioned
↓ (kickoff held)
active
↓ (engagement ends)
offboarding
↓ (90-day archive complete)
archived
Intake fields land async and trigger personalization passes. Failure states: provisioning_failed → Campfire #ops-alerts; contract_expired → re-send.
curl -X POST https://3.basecampapi.com/${ACCOUNT_ID}/projects.json \
-H "Authorization: Bearer ${BASECAMP_TOKEN}" \
-H "Content-Type: application/json" \
-H "User-Agent: Kaizen Provisioner (don@kaizenailab.com)" \
-d '{"name":"Kaizen × {Client Name}","description":"{summary}"}'
curl -X PUT https://3.basecampapi.com/${ACCOUNT_ID}/projects/${PROJECT_ID}/people/users.json \
-H "Authorization: Bearer ${BASECAMP_TOKEN}" \
-d '{"grant":[{"email_address":"{client_email}",
"name":"{client_name}",
"title":"Client",
"company_name":"{client_company}"}]}'
# Currently manual via Campfire admin UI (~30 sec):
# 1. Log in to {slug}.kaizenportal.com as admin
# 2. Bot section → Add bot → Name: "Kai",
# Webhook URL: https://kai-{slug}.kaizenailab.com/webhook
# 3. Campfire generates per-room URLs for Kai to post unprompted
# 4. Save URLs into Supabase clients.kai_room_urls (JSON map)
curl -X POST "https://{slug}.kaizenportal.com/rooms/{room_id}/bot/{bot_token}/messages" \
-H "Content-Type: text/plain" \
--data "Hello — Kai here. What can I help with?"
curl -X POST "https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/pages/projects" \
-H "Authorization: Bearer ${CF_TOKEN}" \
-d '{"name":"{slug}-site","production_branch":"main"}'
# Staging at {slug}.pages.dev (native CF subdomain — no custom DNS)
curl -X POST https://api.supabase.com/v1/projects \
-H "Authorization: Bearer ${SUPABASE_MGMT_KEY}" \
-d '{"name":"kaizen-{slug}","region":"us-west-1","plan":"pro"}'
Percy + [agent TBD] until you name additional agents.| Step | Owning agent | Action |
|---|---|---|
| Intake parse + SOW draft | Percy | Reads intake, drafts SOW |
| Contract send | Percy | Calls PandaDoc on Don's approval |
| Provisioning orchestration | Percy | Fans out steps |
| Basecamp provisioning | Percy + [TBD] | Project, Card Table, invites |
| Campfire instance provisioning | Percy + [TBD] | Docker deploy + admin setup |
| GitHub / Cloudflare (website) | Percy + [TBD] | Repo + Pages provisioning |
| Supabase / Hermes (Kai) | Percy + [TBD] | Per-client infra spin-up |
| Welcome packet | Percy + [TBD] | Brand-aligned comms |
| Kickoff agenda | Percy | Pulls intake data, generates agenda |
| Status digest | Percy | Weekly Basecamp → client Campfire digest |
| Client-facing chat in portal | Kai (per-client) | Answers questions, files Basecamp cards |
intake.kaizenailab.com + Supabase clients + ops notifications.{slug}.kaizenportal.com. Carson's build slots in here.intake.kaizenailab.comintake.kaizenailab.com build scope. Separate engagement, ~1 week. Confirm you want a standalone spec.intake.kaizenailab.com build specCustom Tally/Typeform-style page, fully owned.
/ — landing, brief explainer/start — Step 1: universal fields/product — Step 2: products multi-select/details — Step 3: dynamic conditional fields/review — Step 4: review/done — confirmation + "what happens next"localStorage auto-save for resumable drafts/api/onboarding/intake AND insert into SupabaseBuild estimate: 5-7 working days. Recommendation: Scope as separate deliverable. Launch the workflow on Tally first if needed, swap to custom in week 2.
@mention or ping a bot → Campfire POSTs to that bot's webhook (JSON: room, user, message body)No documented limit on bot count. 10+ agents = 10+ bot entries. Each agent runs as its own webhook endpoint inside the Hermes runtime.
| Dimension | Campfire | Slack |
|---|---|---|
| Cost | One-time ~$300, self-hosted | $X/user/month forever |
| Multi-bot | Unlimited, simple webhooks | Bot apps, more friction |
| Per-client isolation | Native (single-tenant by design) | Multi-tenant; Connect/guest |
| Data ownership | Fully owned (your Docker, your DB) | Salesforce cloud |
| Compliance posture | HIPAA/SOC2/GDPR by box control | Vendor certifications |
| Threading | Light (rooms + messages) | Heavy threads |
| Search | Built-in, basic | Strong but rate-limited on lower tiers |
| Integrations marketplace | None — custom webhooks | Massive |
| Mobile apps | PWA only | First-class native |
| Voice/video | None | Huddles, Clips |
Recommendation: Yes, migrate. Slack subscription savings + per-client isolation outweigh polish gap. Plan a 1-week parallel run. PWA install as default mobile path.
Why the swap is clean: 37signals owns both. Basecamp's Card Table primitive is the same metaphor as Fizzy's boards. Migration = data shape, not concept shift.
| Fizzy concept | Basecamp concept |
|---|---|
| Account | Basecamp account |
| Board | Project's Card Table (one per project) |
| Column | Card Table column |
| Card | Card (Basecamp primitive) |
Tag priority:red | Basecamp tag OR title prefix [🔴] |
Tag size:* | Basecamp tag OR title prefix |
Tag context:* | Project assignment (each context = own project) |
Tag energy:* | MC-side only (energy is an MC concept) |
| Comments | Card comments (native) |
| Card detail body | Card content (markdown) |
fizzy-data.ts → basecamp-data.ts using @37signals/basecamp SDKEffort estimate: 1-2 weeks of Carson's time. Bonus: Basecamp brings To-do lists, Messages, Docs & Files, Schedules, Check-ins — all in the canonical store. MC can surface any of them over time.
Architecture:
Cost @ 20 clients (illustrative):
Savings: ~$300/mo at 20-client scale + full data ownership + native per-client isolation.
docker run \
--publish 80:80 --publish 443:443 \
--restart unless-stopped \
--volume campfire-{slug}:/rails/storage \
--env SECRET_KEY_BASE=$YOUR_SECRET_KEY_BASE \
--env VAPID_PUBLIC_KEY=$YOUR_PUBLIC_KEY \
--env VAPID_PRIVATE_KEY=$YOUR_PRIVATE_KEY \
--env SSL_DOMAIN={slug}.kaizenportal.com \
campfire
Wrap in a provisioning script Percy calls per new client. DNS for *.kaizenportal.com wildcards to a single rule.
https://hermes.kaizenailab.com/agents/percy/webhook)Kai (per-client): Each client's Campfire registers Kai with webhook → https://kai-{slug}.kaizenailab.com/webhook, served by the client's dedicated Hermes workspace. Kai inherits client's seed docs, integrations, and isolation by construction.