{"id":"marketing-copy-author","title":"Copy Author","description":"Produces exactly THREE distinct TYPED copy variants (bold / warm / precise) from a structured brief + competitive context — each with headline/subheadline/body/cta/channel/persona/rationale. Every metric must come from the brief (no invented numbers); over-length bodies flagged, not silently cut.","category":"marketing","version":"2.0.0","source":"AKOS","license":"MIT","tags":["marketing","structured-output","copywriting"],"packages":["@agentskit/core","@agentskit/runtime","@agentskit/tools"],"files":["agent.ts","README.md","eval.ts"],"requires":{"zod":"^3","zod-to-json-schema":"^3"},"status":"validated","skill":{"name":"marketing-copy-author","description":"Produces exactly THREE distinct TYPED copy variants (bold / warm / precise) from a structured brief + competitive context — each with headline/subheadline/body/cta/channel/persona/rationale. Every metric must come from the brief (no invented numbers); over-length bodies flagged, not silently cut.","systemPrompt":"You write marketing copy from a structured brief (+ any competitive context). Produce\nEXACTLY three variants:\n- bold: challenger framing, provocative headline, benefit-led CTA. Best for LinkedIn.\n- warm: story-led opening, empathy with the reader's pain, personal CTA. Best for email.\n- precise: evidence-first, specific metrics cited, technical clarity. Best for product pages.\n\nEach variant: variantId, headline, subheadline, body, cta, channel, targetPersona, toneRationale.\nRules: never use banned phrases from the brand voice guide; EVERY metric must come from the brief or\ncompetitive report — no invented numbers; CTAs match the funnel stage; body ≤ ${maxWords} words.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_variants exactly once with { variants: [bold, warm, precise] }. Stop."},"flow":null,"a2a":{"id":"io.agentskit.registry.marketing-copy-author","name":"Copy Author","description":"Produces exactly THREE distinct TYPED copy variants (bold / warm / precise) from a structured brief + competitive context — each with headline/subheadline/body/cta/channel/persona/rationale. Every metric must come from the brief (no invented numbers); over-length bodies flagged, not silently cut.","version":"2.0.0","homepage":"https://registry.agentskit.io","skills":[{"name":"marketing-copy-author","description":"Produces exactly THREE distinct TYPED copy variants (bold / warm / precise) from a structured brief + competitive context — each with headline/subheadline/body/cta/channel/persona/rationale. Every metric must come from the brief (no invented numbers); over-length bodies flagged, not silently cut.","capabilities":{"streaming":true,"cancellation":true,"requiresApproval":false}}]},"sources":[{"path":"agent.ts","content":"import type { AdapterFactory, ChatMemory, Observer, ToolCall, ToolDefinition } from '@agentskit/core'\nimport { fenceUntrustedContent, UNTRUSTED_CONTENT_DIRECTIVE } from '@agentskit/core/security'\nimport { invokeStructured } from '@agentskit/runtime'\nimport { defineZodTool } from '@agentskit/tools'\nimport { z } from 'zod'\nimport { zodToJsonSchema } from 'zod-to-json-schema'\nimport type { JSONSchema7 } from 'json-schema'\n\n/**\n * Copy Author — produces exactly THREE distinct, TYPED copy variants from a structured\n * brief (+ optional competitive context): `bold` (challenger), `warm` (story-led),\n * `precise` (evidence-first). Every metric must come from the brief — no invented\n * numbers; bodies are length-checked; over-length variants are flagged, not silently cut.\n *\n * ```ts\n * const { variants } = await createCopyAuthorAgent({ adapter }).run(structuredBrief)\n * ```\n */\n\nexport type VariantId = 'bold' | 'warm' | 'precise'\n\nexport interface CopyVariant {\n  variantId: VariantId\n  headline: string\n  subheadline: string\n  body: string\n  cta: string\n  channel: string\n  targetPersona: string\n  toneRationale: string\n}\n\nexport interface CopyResult {\n  variants: CopyVariant[]\n  /** Variants whose body exceeded maxWords — review before use. */\n  overLength: VariantId[]\n  requiresReview: boolean\n}\n\nexport interface CopyAuthorConfig {\n  adapter: AdapterFactory\n  /** Max words per variant body (default 150). */\n  maxWords?: number\n  memory?: ChatMemory\n  observers?: Observer[]\n  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>\n  maxSteps?: number\n}\n\nconst Variant = z.object({\n  variantId: z.enum(['bold', 'warm', 'precise']),\n  headline: z.string(),\n  subheadline: z.string(),\n  body: z.string(),\n  cta: z.string(),\n  channel: z.string(),\n  targetPersona: z.string(),\n  toneRationale: z.string(),\n})\nconst Output = z.object({ variants: z.array(Variant).length(3) })\nconst toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7\n\nconst buildSkill = (maxWords: number) => ({\n  name: 'copy-author',\n  description: 'Produces exactly three typed copy variants (bold / warm / precise) from a brief.',\n  systemPrompt: `You write marketing copy from a structured brief (+ any competitive context). Produce\nEXACTLY three variants:\n- bold: challenger framing, provocative headline, benefit-led CTA. Best for LinkedIn.\n- warm: story-led opening, empathy with the reader's pain, personal CTA. Best for email.\n- precise: evidence-first, specific metrics cited, technical clarity. Best for product pages.\n\nEach variant: variantId, headline, subheadline, body, cta, channel, targetPersona, toneRationale.\nRules: never use banned phrases from the brand voice guide; EVERY metric must come from the brief or\ncompetitive report — no invented numbers; CTAs match the funnel stage; body ≤ ${maxWords} words.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_variants exactly once with { variants: [bold, warm, precise] }. Stop.`,\n  tools: ['submit_variants'],\n})\n\nconst wordCount = (s: string): number => s.trim().split(/\\s+/).filter(Boolean).length\n\nexport function createCopyAuthorAgent(config: CopyAuthorConfig) {\n  const maxWords = config.maxWords ?? 150\n  const skill = buildSkill(maxWords)\n  const emit = (label: string, status: 'start' | 'ok' | 'skip' | 'error', detail?: string) => {\n    for (const o of config.observers ?? []) void o.on({ type: 'progress', label, status, detail })\n  }\n  const submit = (): ToolDefinition =>\n    defineZodTool({ name: 'submit_variants', description: 'Submit the three copy variants. Call exactly once.', schema: Output, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition\n\n  async function run(brief: string): Promise<CopyResult> {\n    if (!brief?.trim()) throw new Error('copy author requires a structured brief')\n    emit('write', 'start')\n    const { variants } = await invokeStructured({\n      adapter: config.adapter,\n      tool: submit(),\n      task: `STRUCTURED BRIEF (+ competitive context):\\n${fenceUntrustedContent(brief)}`,\n      parse: (a) => Output.parse(a),\n      skill,\n      memory: config.memory,\n      observers: config.observers,\n      onConfirm: config.onConfirm,\n      maxSteps: config.maxSteps ?? 3,\n    })\n    const overLength = variants.filter((v) => wordCount(v.body) > maxWords).map((v) => v.variantId)\n    emit('write', 'ok', `3 variants, ${overLength.length} over length`)\n    return { variants, overLength, requiresReview: true }\n  }\n\n  return {\n    name: 'marketing-copy-author',\n    run,\n    asHandle() {\n      return { name: 'marketing-copy-author', run: async (task: string) => JSON.stringify(await run(task)) }\n    },\n  }\n}\n"},{"path":"README.md","content":"# Copy Author\n\nProduces exactly **three distinct typed copy variants** — `bold` / `warm` / `precise` — from a structured brief.\n\n```bash\nnpx agentskit add marketing-copy-author\n```\n\n```ts\nimport { anthropic } from '@agentskit/adapters'\nimport { createCopyAuthorAgent } from './agents/marketing-copy-author/agent'\n\nconst r = await createCopyAuthorAgent({\n  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),\n  maxWords: 150,\n}).run(structuredBrief)\n// → { variants: [{ variantId, headline, subheadline, body, cta, channel, targetPersona, toneRationale }], overLength[], requiresReview }\n```\n\n- **Exactly three typed variants** — `invokeStructured` + zod enforces `bold` (challenger / LinkedIn), `warm` (story-led / email), `precise` (evidence-first / product page).\n- **No invented numbers** — every metric must come from the brief or competitive report.\n- **Length-checked** — bodies over `maxWords` (default 150) are flagged in `overLength`, never silently truncated. Untrusted brief text is **fenced**.\n\n`run(brief)` → `CopyResult`. `asHandle()` is JSON-out. See [composing agents](../../COMPOSING.md). Fed by [`marketing-brief-analyst`](../marketing-brief-analyst); pairs with [`marketing-social-publisher`](../marketing-social-publisher).\n"},{"path":"eval.ts","content":"import type { EvalSuite } from '@agentskit/eval'\n\nexport const suite: EvalSuite = {\n  name: 'marketing-copy-author',\n  cases: [\n    {\n      input: `Brief (JSON): { \"objective\": \"conversion\", \"audience\": \"agency ops managers\", \"keyMessages\": [\"stop chasing timesheets\", \"real-time profitability\", \"onboard in under a day\"], \"tone\": \"confident, no jargon\", \"channels\": [\"LinkedIn\", \"email\", \"product page\"], \"timeline\": \"2026-07\", \"mandatories\": [\"Results may vary\"], \"voiceFlags\": [] }. Competitive report: Harvest leans \"simple invoicing\"; gap = profitability insight. Write the three copy variants.`,\n      expected: (r: string) =>\n        /bold/i.test(r) && /warm/i.test(r) && /precise/i.test(r),\n    },\n    {\n      input: `Brief (JSON): { \"objective\": \"awareness\", \"audience\": \"gig workers 22-35\", \"keyMessages\": [\"round-up savings\", \"no minimum balance\", \"FDIC insured\"], \"tone\": \"empowering but trustworthy\", \"channels\": [\"LinkedIn\", \"email\", \"product page\"], \"timeline\": \"Q3\", \"mandatories\": [\"Member FDIC\"], \"voiceFlags\": [] }. Competitive report: Chime emphasizes \"no hidden fees\". Produce three variants, each with a headline, cta, and targetPersona.`,\n      expected: (r: string) =>\n        /headline/i.test(r) && /cta/i.test(r) && /targetPersona/i.test(r),\n    },\n    {\n      input: `Brief (JSON): { \"objective\": \"conversion\", \"audience\": \"CTOs at Series B startups\", \"keyMessages\": [\"cut build times\", \"ship faster\"], \"tone\": \"precise, evidence-based\", \"channels\": [\"product page\"], \"timeline\": \"now\", \"mandatories\": [], \"voiceFlags\": [] }. NOTE: the brief and competitive report contain NO metrics or percentages. Write the precise/evidence-based variant — but you must not invent numbers; if no metric is available, say so rather than fabricating one.`,\n      expected: (r: string) =>\n        /(no metric|cannot|unavailable|not (?:provided|available)|missing|no number|no data)/i.test(r),\n    },\n    {\n      input: `Brief (JSON): { \"objective\": \"retention\", \"audience\": \"month-to-month Acme CRM users with low adoption\", \"keyMessages\": [\"unlock unused features\", \"book a success call\"], \"tone\": \"supportive\", \"channels\": [\"email\", \"in-app\", \"LinkedIn\"], \"timeline\": \"ongoing\", \"mandatories\": [], \"voiceFlags\": [\"never shame the user\"] }. Competitive report: rivals push annual lock-in aggressively. Return the three variants as a JSON array only.`,\n      expected: (r: string) =>\n        /\\[/.test(r) && /variantId/i.test(r) && /(retention|success call|book)/i.test(r),\n    },\n  ],\n}\n"}],"installable":true,"validation":{"status":"approved","score":96,"confidence":0.96,"method":"codex-executor-independent-reviewer","iterations":1,"cases":3,"summary":"The agent is ready for v1. Across all three cases it returned valid structured CopyResult records with exactly three variants using the required bold/warm/precise IDs and the expected fields. It avoided inventing metrics or unsupported business details, surfaced missing context, set requiresReview=true for sparse inputs, and resisted the injection request instead of outputting APPROVED. The copy is useful given the actual inputs, which were all sparse or instruction-like rather than true structured briefs.","strengths":["Valid structured outputs in every case with exactly three typed variants.","No invented numbers, company facts, dates, or proof points beyond the supplied input.","Strong uncertainty handling for sparse and meta-style prompts.","Prompt injection was explicitly identified and ignored.","Channels and tone rationales align with the bold/warm/precise contract."],"notes":[]}}