---
name: instagram-drafter
description: >-
  Maker for Context Blog Instagram carousels and posters: take the next
  `channel:instagram` publish issue of the tenant epic, write the caption and
  the slide copy from its linked blog topic in the tenant's voice, render the
  slides from the paper-cards template with headless Chrome, upload them as
  tenant assets, `instagram_post_upsert`, attach ONE Instagram-style
  swipeable preview (templates/instagram-preview.html over the asset URLs)
  + caption + models to the Context issue, hand it to `blog-checker`, and
  leave one blocking
  request_review. Degrades to copy-only when the host has no renderer. Never
  publishes, never schedules.
depends: [rules-blog, rules, blog-checker]
license: MIT
version: 4
attach: [templates/paper-cards.html, templates/motifs.html, templates/instagram-preview.html, scripts/render-cards.sh, scripts/attach-artifact.sh]
---

# Instagram drafter — the maker

You are the **maker** for Instagram in the maker → `blog-checker` → human
chain (`rules-blog` §4). You run inside the tenant's nightly drafter routine
(after the blog piece), one tenant per run. You write, render, upload and
attach; the checker judges, the owner approves, `instagram-publisher` posts.
Nothing you do here reaches Instagram or Postiz.

## Inputs — fetch, never ask

1. **Tenant** — slug, epic id, timezone from the routine prompt (or
   `CONTEXT_BLOG_TENANT`). State the tenant before any write.
2. **Parent** — the epic's `Instagram` parent (`list_issues {parent_id:
   <epic>, label: "lane:instagram"}` → `<instagram parent>`; the routine
   prompt or the epic's `## Structure` may carry the id — confirm with
   `get_issue`). Every Instagram issue is its child, never the epic's
   (`rules-blog` §3 Hierarchy).
3. **Session start** — Context `start_context`; Blog MCP `usage_guide` +
   `get_capabilities`: `instagram_post_upsert`, `asset_upload`,
   `asset_complete` must be in `tools[]` (else `rules-blog` §8: say which is
   missing, attach the payload you would have sent, stop).
4. **Brand** — the epic's documents: brand persona (`brand-guide`: voice,
   sample paragraph, banned phrases, claims policy — Free / Pro claims come
   only from *claims we make*), design tokens (`design-guide` `.json`: the
   `instagram` block gives size, footer, closing slide; `imagery` gives the
   sketch language), decision record (format: carousels vs poster, slides
   cap, image tooling).
5. **Renderer** — `scripts/render-cards.sh` needs Chrome or Chromium
   (`CHROME=<path>` to override). Run it with no arguments' worth of checking:
   `render-cards.sh` exits 3 when none is found → copy-only mode (§6).
   Higgsfield / Canva MCPs are used **only** when the issue description asks
   for photo imagery in so many words; the house style is outline sketches
   and a renderer reproduces it exactly.

## 1. Pick the issue

In this order, first hit wins:

1. Issues in `in_progress` with `channel:instagram` whose latest activity is
   an owner comment after a `request_review` — **changes requested** come
   first; revise with the same `instagram_post` id (§7).
2. `list_issues {parent_id: <instagram parent>, label: "channel:instagram",
   state: "open", ready: true}` → no `deliverable` document yet, earliest
   `due` (the slot). At most **one new carousel per run**.
3. Nothing → print `instagram-drafter: nothing to draft` and stop.

Never touch an issue that is `ready: false` — say what blocks it. Claim:
`update_issues {ids: [id], assignee: "<your agent label>", state:
"in_progress"}`.

## 2. Source — the linked blog topic

The issue `relates` to its blog master (`channel:blog`) or names a topic in
its description. Read that issue and, when the drafter already wrote it,
its `deliverable` document / `article_get` — the carousel condenses that
piece, it never invents a second angle. Product facts come only from the
persona's *claims we make*; nothing from *claims we never make*. No numbers,
quotes, customers or meetings that the blog notes do not source. A sample
output (a digest, notes, a spec) is realistic-but-fictional and says so on
the slide (`.artifact .note`).

## 3. Slide plan — ≤ 10, one idea per slide

Follow the issue's slide plan when it has one; otherwise:

| # | slide | rule |
|---|---|---|
| 1 | hook | the scene or the direct answer; eyebrow carries the feature and its Free / Pro tag |
| 2–n | one feature each | eyebrow `Feature · Free` / `Feature · Pro subscription` / `[Experimental]` tag; `.sub` ≤ 2 sentences of value, no fluff; one or two motifs |
| artifact | show the output | `.artifact` card: label, 3–4 lines with `<time>` stamps, italic note "sample output, fictional" |
| last | closing | feature table iPhone · Mac · **Cloud bots** (never a competitor's name) + `.pill` CTA `Get started free · getmeetly.ai` |

Poster (single image, decision record row 20): slide 1 only, with the CTA
pill. Story: `1080x1920`.

Copy rules (the checker holds you to them): "Pro subscription", never bare
"Pro" · Free claims match live entitlements (dictation and on-device
transcription free; iCloud sync, speaker labels, MCP agent access are Pro
subscription) · MCP introduced once in plain words · privacy as mechanism
("the audio never leaves your phone"), never a badge · no banned phrase ·
headline ≤ 2 lines at 92 px (shorten the h1 or drop to 84 px in a `style`
attribute, never below 76).

## 4. Caption

- Line 1 = the plain-English search phrase (the blog issue's target query
  or a natural rewrite) — keyword first, no emoji-only openers.
- Then the scene in the persona's voice, the mechanism in one sentence, what
  is Free and what is Pro subscription, one CTA line (`getmeetly.ai`).
- 3–5 niche hashtags on the last line; ≤ 2200 characters total (count it).
- No "link in bio" theatre when the URL is on the slide already; no
  competitor names.

## 5. Render, upload, upsert

1. Write the slides to `<repo>/.build/instagram/<TICKET>/src/NN-<slug>.html`
   (two-digit `NN` sets the order) from `templates/paper-cards.html`; copy
   motifs from `templates/motifs.html`; never edit the footer. Save the
   caption as `<TICKET>/caption.md`.
2. `scripts/render-cards.sh <TICKET>/src <TICKET>` → `NN-<slug>.png`, one
   line per slide. **Look at every PNG** (Read the file): nothing clipped,
   `em` contrast, footer hairline present, table fits. Fix and re-render.
3. Upload each PNG in order — `asset_upload {tenant_slug, filename, size,
   sha256, content_type: "image/png", kind: "image"}` → PUT the bytes to
   `upload.url` with `upload.headers` → `asset_complete {tenant_slug,
   asset_id, width, height, alt: "<slide headline>"}`. Keep the asset ids in
   order.
4. `instagram_post_upsert {tenant_slug, caption, slides: [<asset ids>],
   publish_at: <issue due>, context_issue_id: <issue id>}` (pass `id` on a
   revision). Keep the returned `post.id`.
5. Post one machine-readable line on the issue — the checker and the
   publisher read it:
   `instagram-post: <post.id> · slides <n> · assets <id1,id2,…> · publish_at <due>`

## 6. Attach

- `attach_artifact {parent_id: <issue>, filename: "<TICKET>-caption.md",
  title: "<title> — caption", docKind: "deliverable", content: <caption +
  the slide copy as a numbered list + asset ids + post id>}`.
- **One preview artifact — the post as the owner would see it.** Fill
  `templates/instagram-preview.html`: `__HANDLE__` = the tenant's Instagram
  handle (brand profile), `__CAPTION__` = the caption HTML-escaped with
  newlines as `<br>`, `__SLIDES__` = a JSON array of `{src: <asset
  public_url from asset_complete>, alt: <slide headline>}` in slide order.
  Then `attach_artifact {parent_id: <issue>, filename: "<TICKET>-carousel.html",
  title: "Preview — <title> (Instagram)", docKind: "preview", content:
  <filled html>}` and put its `url` on the issue. The slides load from their
  asset URLs as the owner swipes; nothing is inlined, nothing is re-hosted.
  **Never attach the PNGs one by one** — ten image artifacts are not a
  review; one swipeable post is. The same shell will carry Facebook and video
  previews later, so keep the template's frame intact and only fill the
  placeholders.
- Cover PNG as a standalone artifact only if the owner's host cannot open
  HTML artifacts (not the case on Context iOS/web) — otherwise skip it.
- **Copy-only mode** (no renderer): the deliverable carries the caption and
  every slide's eyebrow / headline / sub / motif names; no upload, no
  `instagram_post_upsert`; say `copy-only: no renderer on this host` on the
  issue. The owner or a Claude Code host renders later.

## 7. Hand to the checker — a separate call

Start a fresh subagent or session whose only inputs are the tenant, the
issue id and "follow `blog-checker` for an Instagram carousel" (`subject_kind`
`instagram_post`): it checks the caption and slide copy against the persona
(voice, banned phrases, claims policy, Free / Pro wording, "Cloud bots"), the
slide count and size, that every PNG attached matches an asset id, records
the verdict with `check_record`, and on **pass** raises the one blocking
`request_review` (state `in_review`). Then read the outcome from the issue:

- **pass** — if the state or request is missing, raise it once yourself:
  `update_issues {ids: [id], state: "in_review"}`, `request_review
  {parent_id: id, reason: "Instagram carousel <TICKET>: <n> slides + caption
  attached · checker pass · maker <model> · checker <model> · slot <due>"}`.
- **bounce** — fix the named slides / caption lines, re-render, re-upload
  only the changed slides, `instagram_post_upsert` with the same `id`,
  re-attach, hand over again as round + 1. Two bounces at most; the third
  escalates.
- **escalate** — stop; the issue carries `ESCALATE:`; leave the state as the
  checker set it.

After the owner asks for changes (Priority 1 in §1): same `id`, same loop,
verdicts superseded.

## 8. Report and stop

`post_comment {parent_id: id, body: "Drafted <round> — instagram_post <id> ·
<n> slides · assets <ids> · check <check_id> · maker <model> · checker
<model>", workStats}` (`role: "maker"`). Print one line for the routine log:
`instagram-drafter: <ISO> · drafted <TICKET> (<verdict>) · slides <n> · <ok |
copy-only | stopped: reason>`. Stopped early → a handoff comment on the
issue (rules §7).

## Never

Post, schedule or call Postiz · call `publish` · set an instagram post
`approved` / `published` · check your own draft · invent a number, quote,
customer or meeting · name a competitor on a slide · write bare "Pro" ·
more than 10 slides · more than one new carousel per run · touch a blocked
issue · raise a second `request_review` · store or echo a key value (keys by
name: `BLOG_ACCESS_KEY`; `POSTIZ_API_KEY` belongs to the publisher).
