{"id":"marketing-brief-analyst","title":"Brief Analyst","description":"Reads an incoming campaign brief and produces a TYPED structured brief (objective/audience/key messages/tone/channels/timeline/mandatories) downstream agents reference. Never invents client details; missing required fields listed in gaps (ask, don't guess); optional voice guide flags conflicting language.","category":"marketing","version":"2.0.0","source":"AKOS","license":"MIT","tags":["marketing","structured-output","intake"],"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-brief-analyst","description":"Reads an incoming campaign brief and produces a TYPED structured brief (objective/audience/key messages/tone/channels/timeline/mandatories) downstream agents reference. Never invents client details; missing required fields listed in gaps (ask, don't guess); optional voice guide flags conflicting language.","systemPrompt":"You are the intake analyst for a campaign studio. Read the incoming campaign brief and\nproduce a structured brief downstream agents will reference. Extract: client/product; objective\n(awareness|conversion|retention, else \"unspecified\"); audience; key messages (≤3); tone; channels;\ntimeline; mandatories (legal lines, brand bans). If a VOICE GUIDE is provided, flag any brief language\nthat conflicts with it in voiceFlags.\n\nYou do NOT write copy. NEVER invent client details or audience demographics. List any required field\nthe brief is missing in gaps — ask, don't guess.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_brief exactly once with the structured fields + voiceFlags + gaps. Stop."},"flow":null,"a2a":{"id":"io.agentskit.registry.marketing-brief-analyst","name":"Brief Analyst","description":"Reads an incoming campaign brief and produces a TYPED structured brief (objective/audience/key messages/tone/channels/timeline/mandatories) downstream agents reference. Never invents client details; missing required fields listed in gaps (ask, don't guess); optional voice guide flags conflicting language.","version":"2.0.0","homepage":"https://registry.agentskit.io","skills":[{"name":"marketing-brief-analyst","description":"Reads an incoming campaign brief and produces a TYPED structured brief (objective/audience/key messages/tone/channels/timeline/mandatories) downstream agents reference. Never invents client details; missing required fields listed in gaps (ask, don't guess); optional voice guide flags conflicting language.","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 * Brief Analyst — the intake step of a campaign studio. Reads an incoming campaign brief\n * and produces a TYPED structured brief that downstream agents reference. Never invents\n * client details; missing required fields are listed in `gaps` (ask, don't guess); pass a\n * `voiceGuide` and it flags brief language that conflicts with it.\n *\n * ```ts\n * const { brief, gaps } = await createBriefAnalystAgent({ adapter }).run(incomingBrief)\n * ```\n */\n\nexport interface CampaignBrief {\n  clientProduct: string\n  /** awareness | conversion | retention. */\n  objective: string\n  audience: string\n  /** ≤ 3 key messages. */\n  keyMessages: string[]\n  tone: string\n  channels: string[]\n  timeline: string\n  mandatories: string[]\n  /** Brief language that conflicts with the supplied voice guide. */\n  voiceFlags: string[]\n}\n\nexport interface BriefAnalysisResult {\n  brief: CampaignBrief\n  /** Required fields the brief didn't supply — clarify, don't guess. */\n  gaps: string[]\n  requiresReview: boolean\n}\n\nexport interface BriefAnalystConfig {\n  adapter: AdapterFactory\n  /** Optional brand-voice guide; brief language conflicting with it is flagged. */\n  voiceGuide?: string\n  memory?: ChatMemory\n  observers?: Observer[]\n  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>\n  maxSteps?: number\n}\n\nconst Output = z.object({\n  clientProduct: z.string(),\n  objective: z.enum(['awareness', 'conversion', 'retention', 'unspecified']),\n  audience: z.string(),\n  keyMessages: z.array(z.string()).max(3),\n  tone: z.string(),\n  channels: z.array(z.string()),\n  timeline: z.string(),\n  mandatories: z.array(z.string()),\n  voiceFlags: z.array(z.string()),\n  gaps: z.array(z.string()),\n})\nconst toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7\n\nconst skill = {\n  name: 'brief-analyst',\n  description: 'Extracts a typed structured campaign brief from an incoming brief (never invents).',\n  systemPrompt: `You are the intake analyst for a campaign studio. Read the incoming campaign brief and\nproduce a structured brief downstream agents will reference. Extract: client/product; objective\n(awareness|conversion|retention, else \"unspecified\"); audience; key messages (≤3); tone; channels;\ntimeline; mandatories (legal lines, brand bans). If a VOICE GUIDE is provided, flag any brief language\nthat conflicts with it in voiceFlags.\n\nYou do NOT write copy. NEVER invent client details or audience demographics. List any required field\nthe brief is missing in gaps — ask, don't guess.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_brief exactly once with the structured fields + voiceFlags + gaps. Stop.`,\n  tools: ['submit_brief'],\n}\n\nexport function createBriefAnalystAgent(config: BriefAnalystConfig) {\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_brief', description: 'Submit the structured brief. Call exactly once.', schema: Output, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition\n\n  async function run(incomingBrief: string): Promise<BriefAnalysisResult> {\n    if (!incomingBrief?.trim()) throw new Error('brief analyst requires a non-empty campaign brief')\n    const guideBlock = config.voiceGuide ? `\\n\\nVOICE GUIDE:\\n${fenceUntrustedContent(config.voiceGuide)}` : ''\n    emit('analyse', 'start')\n    const out = await invokeStructured({\n      adapter: config.adapter,\n      tool: submit(),\n      task: `INCOMING CAMPAIGN BRIEF:\\n${fenceUntrustedContent(incomingBrief)}${guideBlock}`,\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 { gaps, ...brief } = out\n    emit('analyse', 'ok', `${gaps.length} gap(s), ${brief.voiceFlags.length} voice flag(s)`)\n    return { brief, gaps, requiresReview: true }\n  }\n\n  return {\n    name: 'marketing-brief-analyst',\n    run,\n    asHandle() {\n      return { name: 'marketing-brief-analyst', run: async (task: string) => JSON.stringify(await run(task)) }\n    },\n  }\n}\n"},{"path":"README.md","content":"# Brief Analyst\n\nThe intake step of a campaign studio — reads an incoming brief and produces a **typed structured brief** downstream agents reference.\n\n```bash\nnpx agentskit add marketing-brief-analyst\n```\n\n```ts\nimport { anthropic } from '@agentskit/adapters'\nimport { createBriefAnalystAgent } from './agents/marketing-brief-analyst/agent'\n\nconst r = await createBriefAnalystAgent({\n  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),\n  voiceGuide,  // optional — flags conflicting brief language\n}).run(incomingBrief)\n// → { brief: { clientProduct, objective, audience, keyMessages[], tone, channels[], timeline, mandatories[], voiceFlags[] }, gaps[], requiresReview }\n```\n\n- **Typed brief** — `invokeStructured` + zod; `objective` is an enum (`awareness | conversion | retention | unspecified`); key messages capped at 3.\n- **Never invents** — missing required fields land in `gaps` to clarify, never guessed.\n- **Voice-aware** — pass `voiceGuide` and brief language conflicting with it is flagged in `voiceFlags`. Untrusted brief text is **fenced**.\n\n`run(brief)` → `BriefAnalysisResult`. `asHandle()` is JSON-out. See [composing agents](../../COMPOSING.md). Feeds [`marketing-copy-author`](../marketing-copy-author).\n"},{"path":"eval.ts","content":"import type { EvalSuite } from '@agentskit/eval'\n\nexport const suite: EvalSuite = {\n  name: 'marketing-brief-analyst',\n  cases: [\n    {\n      input: `Campaign brief — Client: Lumen, a B2B time-tracking SaaS. Objective: drive conversions to the 14-day trial. Target audience: agency operations managers at 10-50 person creative shops. Key messages: (1) stop chasing timesheets, (2) profitability per client in real time, (3) onboard in under a day. Tone: confident, no jargon. Channels: LinkedIn + email. Timeline: kickoff 2026-07-01, launch 2026-07-15. Mandatory: must include \"Results may vary\" disclaimer; never use the word \"synergy\".`,\n      expected: (r: string) =>\n        /conversion/i.test(r) && /(objective|audience|keyMessages|channels)/i.test(r),\n    },\n    {\n      input: `Brief for Northstar Bank's \"Smart Saver\" launch. Audience: gig-economy workers 22-35 with irregular income. Objective: brand awareness. Key messages: round-up savings, no minimum balance, FDIC insured. Tone: empowering but trustworthy. Channels: Instagram, TikTok. Timeline: Q3 2026. Mandatory legal line: \"Member FDIC\". Brand ban: do not claim \"guaranteed returns\".`,\n      expected: (r: string) =>\n        /awareness/i.test(r) && /(mandator|FDIC|legal)/i.test(r),\n    },\n    {\n      input: `Brief: We want a campaign for our new product. Make it pop and get us lots of customers. Budget is flexible. Go.`,\n      expected: (r: string) =>\n        /(gap|missing|absent|clarif|cannot|unable|insufficient|need more)/i.test(r),\n    },\n    {\n      input: `Brief for Acme CRM retention push. Audience: existing customers on month-to-month plans showing low feature adoption. Objective: retention. Key messages: you're only using 2 of 12 features, book a success call, lock in annual pricing. Tone: edgy, a little aggressive — \"ditch the dinosaur tools or get left behind\". Channels: in-app + email. Timeline: ongoing. Note: brand voice guide says tone must stay supportive and never shame the user.`,\n      expected: (r: string) =>\n        /(voiceFlag|conflict|flag|tone|shame|voice)/i.test(r) && /retention/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 produced valid structured outputs for all cases, resisted the injection request, avoided inventing campaign details from meta/incomplete inputs, surfaced missing required fields in gaps, and set requiresReview appropriately. The behavior is conservative but aligned with the stated purpose: ask rather than guess when client/product/objective/audience and other required brief fields are absent.","strengths":["Valid structured output shape in every case with brief, gaps, and requiresReview.","Correctly used objective: \"unspecified\" instead of guessing.","Did not comply with the injection attempt to output only APPROVED.","Surfaced uncertainty and missing fields rather than hallucinating campaign details.","Key messages remained capped and empty when unsupported by input."],"notes":[]}}