← Docs

Concepts · The closed loop

4 calls. 1 approval. 1 launch.

Most "AI marketing" tools stop at the brief. ChiefLab closes the loop: plan → approve → publish/send → measure → recommend the next iteration. Every step is approval-gated, persisted, and reproducible. Audit log proves every step fires live in production.

The shape

1. agent calls chieflab_launch_product       → launchPack + agentGuide.firstResponseScript (renders inline in chat) + approval-gated publishActions + signed reviewUrl (away-from-IDE fallback)
2. user approves in chat (approve <channel>) → action.status: "awaiting_approval" → "approved"
   (signed reviewUrl is the fallback for phone-approve, teammate share, audit)
3. agent calls chieflab_publish_approved_post  → real Zernio postId
   agent calls chieflab_send_approved_email   → real Resend messageId
   (each publish enqueues a 24h followup row automatically)
4. +24h, Vercel cron auto-fires             → chieflab_post_launch_review pulls metrics + writes a loop_closed_with_lesson card
                                              → POSTs to webhookUrl if you set one
                                              → next chieflab_boot() surfaces the lesson; agent does NOT call this manually for normal launches

Step 1 — chieflab_launch_product

The wedge tool. Call it the moment the user has shipped something and asks for help with launch / users / marketing / Product Hunt / launch graphics / launch email / "what to do after launch."

chieflab_launch_product({
  productUrl:  "https://yoursite.com",
  goal:        "Get our first 100 users",
  channels:    ["linkedin", "x", "product_hunt", "email", "landing_hero"],
  tenantId:    "acme-co",          // optional: load stored brand context
  brand:       { name, audience, voice, pillars },  // OR pass inline
  imagesNeeded: 1,                 // 0 to skip; default 1
  scheduleFor: "2026-06-01T09:00Z", // optional ISO 8601
  webhookUrl:  "https://your-app.com/hooks/chieflab",  // optional
  idempotencyKey: "launch-yoursite-2026-06"  // CRITICAL on retries
})

Returns:

{
  "launchId":      "<UUID>",
  "intent":        "launch_product",
  "run":           { ... },
  "launchPack": {
    "positioning": { assetId, body },
    "launchAngle": { assetId, body },
    "channels": {
      "linkedin":  { assetId, body, publishViaConnector: "zernio" },
      "x":         { assetId, body, publishViaConnector: "zernio" },
      "email":     { assetId, body, publishViaConnector: "resend" },
      "landing_hero": { assetId, body, publishViaConnector: "site_builder" }
    }
  },
  "generatedImages": [{ id, usedFor, status, generator: "gemini-2.5-flash-image" }],
  "publishActions": [
    {
      "id":            "<UUID>",          // PERSISTED to actionStore — pass to publish
      "channel":       "linkedin",
      "connector":     "zernio",
      "executorTool":  "chieflab_publish_approved_post",  // ← what to call after approval
      "status":        "awaiting_approval",
      "preflight": {
        "severity":    "medium",
        "warnings":    ["Publishing to linkedin via zernio — visible to your audience..."],
        "recommendations": [...],
        "gates":       ["Requires explicit human approval before execution."]
      }
    }
  ],
  "reviewUrl":     "https://chieflab.io/runs/<id>?token=...",  // HMAC, 7-day TTL
  "trackingPlan": { metricsTrackedAfter: "24h", nextRunTool: "chieflab_post_launch_review" }
}

Step 2 — user approves inline in the IDE

The normal path is inline: the agent renders the drafts in chat and the user replies approve all, approve <channel>, revise <channel>: ..., or reject <channel>: .... Those commands transition the same approval state machine and rejected feedback becomes memory for the next launch.

The signed reviewUrl is the fallback when the human is away from the IDE, wants phone approval, needs a teammate to review, or wants the richer image-heavy approval surface. It is HMAC-signed, no-login, and expires after 7 days.

Step 3a — chieflab_publish_approved_post (Zernio)

Strict approval gate. Refuses with { reason: "requires_approval", reviewUrl } if status is anything but "approved".

chieflab_publish_approved_post({
  actionId:    "<UUID from launchPack.publishActions>",
  content:     "<final post text — your LLM rendered this from the brief>",
  platforms:   [{ platform: "linkedin", accountId: "zer_acc_..." }],
  mediaUrls:   ["data:image/png;base64,..."],   // optional — ChiefLab uploads to Zernio CDN
  scheduleAt:  "2026-06-01T09:00:00Z"           // optional — schedule instead of fire-now
})

// → { executed: true, zernioPostId, platforms, scheduled, results }
// → action.status flips "approved" → "executed", metadata.zernioPostId persisted

Get the Zernio accountIds with chieflab_list_publish_accounts({}). Connect new accounts with chieflab_connect_publish_account({ platform }) — Zernio handles the per-platform OAuth dance.

Step 3b — chieflab_send_approved_email (Resend)

chieflab_send_approved_email({
  actionId:    "<UUID — required for the gate>",
  from:        "Brand <hi@yourdomain.com>",  // must be a verified Resend domain
  to:          "user@example.com",
  subject:     "Launch day",
  html:        "<h1>...</h1>",
  text:        "Plain-text fallback for inboxing"
})

// → { sent: true, messageId, to, sentAt }
// → action.status flips "approved" → "executed", metadata.resendMessageId persisted

Verify your sender domains with chieflab_list_email_senders({}). Set the workspace-scoped Resend key with chieflab_set_resend_key({ apiKey }) if not already configured.

Step 4 — 24h follow-up (automatic by default)

You don't have to remember anything here. When step 3 fires, ChiefLab records a launch_followup row scheduled for publishedAt + 24h (configurable via CHIEFLAB_REVIEW_DELAY_HOURS). The cron at /api/cron/tick scans due rows, fires chieflab_post_launch_review, pulls Zernio engagement + GA4 + Search Console, writes channel performance into per-tenant memory, emits a learned-loop event, surfaces a loop_closed_with_lesson card on the next chieflab_boot(), and POSTs the result to your webhookUrl if you set one. The next launch grounds against it without anyone asking.

The manual call exists for replay or debugging — it is not part of the normal path:

# Force-run a readback. Useful for replay or debugging.
# In the normal flow the cron fires this for you.
chieflab_post_launch_review({
  runId:        "<launchId from step 1>",
  lookbackDays: 7,                // 1-90, default 7
  outputMode:   "context"          // "context" returns metrics + brief; "full" renders the recommendation
})

// → {
//     totals:        { posts, likes, comments, views, shares },
//     perAccount:    [{ accountId, profile, analytics, topPosts }],
//     ga4:           { connected, metrics, period, rows },
//     searchConsole: { connected, metrics, topQueries },
//     brief:         "<recommendation brief — your LLM renders into customer-facing summary>"
//   }

The webhook payload:

POST <your-webhookUrl>
X-ChiefLab-Signature: sha256=<hex>
Content-Type: application/json

{
  "event":        "launch.review_ready",
  "operator":     "chiefmo",
  "tool":         "chieflab_post_launch_review",
  "launchId":     "<UUID>",
  "actionId":     "<UUID>",
  "workspaceId":  "<your-workspace>",
  "result":       { ...same shape as the tool returns... },
  "deliveredAt":  "2026-05-05T09:00:00.000Z"
}

Verify the signature using your workspace's webhook secret (set via CHIEFLAB_WEBHOOK_SECRET).

Hard guarantees

  • No publish or send tool ever fires without status: "approved" on the action. The gate checks the actionStore record, not just the input. Calling without an approved actionId returns { reason: "requires_approval", reviewUrl } — never silently fires.
  • The reviewUrl is HMAC-signed. 7-day TTL. Opens with no login. Tampering rejects. Use buildReviewUrl() and verifyRunToken() if you want to re-issue.
  • Idempotency keys cache results for 24h. Retrying a launch with the same idempotencyKey returns the original launchPack — won't double-create actions, won't double-fire publishes.
  • Runs persist across cold starts. Supabase-backed actionStore + assetStore + runStore. Cold-start on Vercel doesn't lose state.
  • Per-tenant isolation. Brand context, voice samples, memory, runs all scoped by tenantId. Multi-tenant agent products can serve thousands of end-users from one workspace.
  • Webhook delivery retries with exponential backoff. 1m → 5m → 30m → 2h → 12h. Signature on every attempt.

What's verified live

Every step above has been observed firing end-to-end against production with real run IDs, action IDs, and Zernio postIds. The wedge claim isn't aspirational.

What's next

  • MCP reference — JSON-RPC contract, all production tools, error codes
  • Quickstart — install + first launch in 60 seconds
  • For AI agents — instruction text, hard safety rules
  • Examples — real launch pack JSON envelopes from production