Content Repurposer
[Live — April 2026] ·
agent__content-repurposer· Claude Sonnet 4.6
Transforms approved content (blog posts, reports, social posts, newsletters) into derivative formats in a single batch job. Source-constrained — extracts and reformats, does not invent new claims.
Related: Blog Writer · Social Post Writer · Email Writer
Overview
| Function | Transform approved content into derivative formats (social post, email, ad copy, thread, carousel, landing page) |
| Type | Batch worker — Content |
| Model | Claude Sonnet 4.6 (via adapter-claude-local) |
| Queue | agent__content-repurposer |
| Concurrency | 3 batches; 10 jobs/minute rate limit |
| Credits | 0.5 cr per derivative format |
| Plan | Pro+ |
| Status | Live (April 2026) |
| Worker | packages/agents/src/workers/content-repurposer.worker.ts |
Triggers
| Trigger | When | Who initiates |
|---|---|---|
| Manual | Client clicks “Repurpose Content” on an approved blog post detail page | Client (dashboard) |
The “Repurpose Content” button (apps/dashboard/src/app/(dashboard)/blog/[id]/BlogPostDetail.tsx) renders only when post.status === "client_approved". It opens RepurposeModal.tsx where the client picks one or more target formats. On submit, repurposeContent() server action creates Activity records and enqueues this worker.
Input
interface ContentRepurposerJobData {
batchId: string; // UUID grouping all derivatives in this run
tenantId: string;
tenantName: string;
sourceDeliverableId: string; // ID of source BlogPost / Report / SocialPost / EmailNewsletter
sourceActivityId: string; // Activity linked to the source deliverable
sourceType: "blog_post" | "report" | "social_post" | "newsletter";
sourceContent: string; // Full markdown / text content
sourceVersion: number; // Source record version at time of repurposing
targetFormats: Array<{
type: "social_post" | "email_newsletter" | "ad_copy" | "thread" | "carousel" | "landing_page";
platform?: string; // For social_post: "linkedin" | "instagram" | "facebook" | "x"
activityId: string; // Pre-created Activity record for this derivative
}>;
templateId?: string; // FK → RepurposingTemplate (custom prompt override)
agentRole?: string;
wakeReason?: string;
}Activity records are created before the job is enqueued (one per target format, status: "pending", deliverablePeriodId: null).
Repurposing Matrix
| Source | Supported target formats |
|---|---|
blog_post | social_post, email_newsletter, ad_copy, thread, carousel, landing_page |
report | social_post, email_newsletter, thread, carousel |
social_post | ad_copy, thread |
newsletter | social_post, ad_copy |
Invalid source → target combinations throw immediately before credits are reserved.
Output
Each derivative is persisted and the associated Activity is updated:
| Target type | DB table | Key fields |
|---|---|---|
social_post | SocialPost | bodyText, hashtags, engagementHook, contentType, platform; status: "dm_review" |
email_newsletter | EmailNewsletter | subject, preheader, content; status: "dm_review" |
ad_copy | Activity outputPayload | headline, description, cta, targetAudience |
thread | Activity outputPayload | tweets[] (order, text, type: hook/body/cta) |
carousel | Activity outputPayload | slides[] (order, title, text, visualSuggestion), caption, hashtags |
landing_page | Activity outputPayload | headline, subheadline, hero/problem/solution/benefits sections, CTAs |
After each derivative: Activity.status → "awaiting_approval", Activity.outputPayload = raw JSON.
How It Works
- Validate all repurposing paths against the matrix.
- Fetch tenant context —
ClientContext.contentandBrandVoice.guidelinesare injected into every prompt. - Reserve credits —
targetFormats.length × 0.5credits reserved upfront; unused credits released on batch-level failure. - Emit
agent:started. - For each target format (sequentially):
- Build prompt via
buildRepurposePrompt()— selects built-in system prompt by key"${sourceType}_to_${targetType}", or usesRepurposingTemplate.promptTemplateiftemplateIdprovided. - Call Claude (
temperature: 0.7,maxTokens: 2000). - Parse JSON via
extractJson()(handles fenced code blocks and raw JSON; balanced-brace parser). - Save derivative via
saveDerivative()switch statement. Activity.status → "awaiting_approval",Activity.outputPayload = content.- Consume 0.5 credits.
- On per-format failure: mark activity
"failed", log error, continue to next format (batch is not aborted).
- Build prompt via
- Save
RepurposingAnalyticsrecord. - Write activity log and send in-app notification (“Content Repurposing Complete — N of M derivatives generated”).
- Emit
agent:completedwithdurationMs.
System Prompts
One built-in prompt per source→target path. All prompts:
- Instruct Claude to extract/reformat from source without inventing new claims
- Specify the exact JSON schema Claude must output
- Platform-specific tone/format rules for social posts (LinkedIn professional, Instagram hook-first, etc.)
Custom templates override via RepurposingTemplate.promptTemplate (DB model).
Prompts also include CLIENT CONTEXT and BRAND VOICE GUIDELINES when available on the tenant.
HITL Gates
Derivatives follow the standard 2-gate workflow:
| Gate | Approver | Portal |
|---|---|---|
| DM review | DM reviewer | DM Portal |
| Client review | Client | Dashboard |
Cost Profile
| Derivatives | Credits | Approx. LLM tokens |
|---|---|---|
| 1 format | 0.5 cr | ~2,000–3,000 tokens |
| 3 formats | 1.5 cr | ~6,000–9,000 tokens |
| 6 formats | 3.0 cr | ~12,000–18,000 tokens |
Cost is ~50% of from-scratch generation because source content eliminates research steps.
Error Handling
| Failure | Behaviour |
|---|---|
| Invalid repurposing path | Throws immediately; credits never reserved |
Missing activityId | Server action guards (if (!post.activityId) return error) |
| Per-format Claude failure | Activity marked "failed"; batch continues; credits not consumed for that format |
| Batch-level exception | Unreserved credits released; agent:failed emitted; job retried (attempts: 2, exponential backoff 5 s) |
E2E Tests
apps/dashboard/tests/e2e/blog-flow.spec.ts — Step 7 (8 tests):
- Button visible only on
client_approvedposts; hidden ondm_review - Modal opens with 6 format cards
- Submit disabled when no format selected
- Social Post card shows platform picker (LinkedIn / Instagram / Facebook / X)
- Cancel closes modal
- Email Newsletter + Social Post full submit: success banner + DB Activity record verification