{"id":"agency-deck-builder","title":"Deck Builder","description":"Drafts a TYPED pitch/status deck (typed slides with bullets + citations) from project artifacts. Every number must cite its source artifact — uncited metrics are flagged in uncitedMetrics, missing data becomes 'data to be confirmed'. Configurable sections; always a draft.","category":"agency","version":"2.0.0","source":"AKOS","license":"MIT","tags":["agency","structured-output","grounding"],"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":"agency-deck-builder","description":"Drafts a TYPED pitch/status deck (typed slides with bullets + citations) from project artifacts. Every number must cite its source artifact — uncited metrics are flagged in uncitedMetrics, missing data becomes 'data to be confirmed'. Configurable sections; always a draft.","systemPrompt":"You draft a pitch or status deck from the project brief, KPIs, and milestone notes.\nSlide sections, in order: ${sections.join(', ')}.\n\nEVERY number must cite the source artifact in that slide's citations. Do NOT invent metrics. Where\ndata is missing, write \"${TBC}\" in the bullet rather than guessing. This is a DRAFT for the team.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_deck exactly once with { slides: [{ section, bullets, citations }] }. Stop."},"flow":null,"a2a":{"id":"io.agentskit.registry.agency-deck-builder","name":"Deck Builder","description":"Drafts a TYPED pitch/status deck (typed slides with bullets + citations) from project artifacts. Every number must cite its source artifact — uncited metrics are flagged in uncitedMetrics, missing data becomes 'data to be confirmed'. Configurable sections; always a draft.","version":"2.0.0","homepage":"https://registry.agentskit.io","skills":[{"name":"agency-deck-builder","description":"Drafts a TYPED pitch/status deck (typed slides with bullets + citations) from project artifacts. Every number must cite its source artifact — uncited metrics are flagged in uncitedMetrics, missing data becomes 'data to be confirmed'. Configurable sections; always a draft.","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 * Deck Builder — drafts a TYPED pitch/status deck from project artifacts (brief, KPIs,\n * milestone notes). Standard slide structure, and EVERY number must cite the source\n * artifact — uncited metrics are flagged, missing data becomes \"data to be confirmed\".\n * Always a draft.\n *\n * ```ts\n * const { deck, uncitedMetrics } = await createDeckBuilderAgent({ adapter }).run(artifacts)\n * ```\n */\n\nconst TBC = 'data to be confirmed'\n\nexport interface Slide {\n  section: string\n  bullets: string[]\n  /** Source artifact(s) backing any numbers on this slide. */\n  citations: string[]\n}\n\nexport interface DeckResult {\n  deck: Slide[]\n  /** Slides that state a number without a citation — review before sharing. */\n  uncitedMetrics: string[]\n  /** Always true — a draft for the team to review. */\n  requiresReview: boolean\n}\n\nexport interface DeckBuilderConfig {\n  adapter: AdapterFactory\n  /** Override the default slide sections. */\n  sections?: string[]\n  memory?: ChatMemory\n  observers?: Observer[]\n  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>\n  maxSteps?: number\n}\n\nconst DEFAULT_SECTIONS = ['cover', 'context', 'what we did', 'what worked', 'what to change', 'next steps']\nconst NUMBER = /\\d/\n\nconst Deck = z.object({\n  slides: z.array(z.object({\n    section: z.string(),\n    bullets: z.array(z.string()),\n    citations: z.array(z.string()),\n  })),\n})\nconst toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7\n\nconst buildSkill = (sections: string[]) => ({\n  name: 'deck-builder',\n  description: 'Drafts a typed pitch/status deck from project artifacts; every number cites its source.',\n  systemPrompt: `You draft a pitch or status deck from the project brief, KPIs, and milestone notes.\nSlide sections, in order: ${sections.join(', ')}.\n\nEVERY number must cite the source artifact in that slide's citations. Do NOT invent metrics. Where\ndata is missing, write \"${TBC}\" in the bullet rather than guessing. This is a DRAFT for the team.\n\n${UNTRUSTED_CONTENT_DIRECTIVE}\n\nCall submit_deck exactly once with { slides: [{ section, bullets, citations }] }. Stop.`,\n  tools: ['submit_deck'],\n})\n\nexport function createDeckBuilderAgent(config: DeckBuilderConfig) {\n  const sections = config.sections ?? DEFAULT_SECTIONS\n  const skill = buildSkill(sections)\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_deck', description: 'Submit the deck. Call exactly once.', schema: Deck, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition\n\n  async function run(artifacts: string): Promise<DeckResult> {\n    if (!artifacts?.trim()) throw new Error('deck builder requires project artifacts')\n    emit('deck', 'start')\n    const { slides } = await invokeStructured({\n      adapter: config.adapter,\n      tool: submit(),\n      task: `PROJECT ARTIFACTS:\\n${fenceUntrustedContent(artifacts)}`,\n      parse: (a) => Deck.parse(a),\n      skill,\n      memory: config.memory,\n      observers: config.observers,\n      onConfirm: config.onConfirm,\n      maxSteps: config.maxSteps ?? 3,\n    })\n    // Flag any slide that quotes a number on a bullet but carries no citation (excluding TBC bullets).\n    const uncitedMetrics = slides\n      .filter((s) => s.citations.length === 0 && s.bullets.some((b) => NUMBER.test(b) && !b.includes(TBC)))\n      .map((s) => s.section)\n    emit('deck', 'ok', `${slides.length} slide(s), ${uncitedMetrics.length} uncited`)\n    return { deck: slides, uncitedMetrics, requiresReview: true }\n  }\n\n  return {\n    name: 'agency-deck-builder',\n    run,\n    asHandle() {\n      return { name: 'agency-deck-builder', run: async (task: string) => JSON.stringify(await run(task)) }\n    },\n  }\n}\n"},{"path":"README.md","content":"# Deck Builder\n\nDrafts a **typed pitch/status deck** from project artifacts — every number must cite its source.\n\n```bash\nnpx agentskit add agency-deck-builder\n```\n\n```ts\nimport { anthropic } from '@agentskit/adapters'\nimport { createDeckBuilderAgent } from './agents/agency-deck-builder/agent'\n\nconst r = await createDeckBuilderAgent({\n  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),\n  // sections: ['cover', 'context', ...]  // optional override\n}).run(`${brief}\\n${kpis}\\n${milestoneNotes}`)\n// → { deck: [{ section, bullets[], citations[] }], uncitedMetrics[], requiresReview }\n```\n\n- **Typed slides** — `invokeStructured` + zod; configurable `sections` (default cover → next steps).\n- **Cited numbers** — every metric must cite its source artifact; a slide that quotes a number with no citation is flagged in `uncitedMetrics`. Missing data becomes `\"data to be confirmed\"`, never invented.\n- **Always a draft** — `requiresReview` always true. Untrusted artifacts are **fenced**.\n\n`run(artifacts)` → `DeckResult`. `asHandle()` is JSON-out. See [composing agents](../../COMPOSING.md). Fed by [`agency-brief-generator`](../agency-brief-generator).\n"},{"path":"eval.ts","content":"import type { EvalSuite } from '@agentskit/eval'\n\nexport const suite: EvalSuite = {\n  name: 'agency-deck-builder',\n  cases: [\n    {\n      input: `Build a status deck.\nBrief: Q2 social campaign for Lumen Chill cold-brew.\nKPIs (source: GA4 export): reach 4.2M, CTR 1.8%, sign-ups 12,400.\nMilestone notes: hero film delivered on time; OOH delayed 1 week; UGC contest over-performed (+40% entries vs target).\nUse the standard deck structure.`,\n      expected: (r: string) =>\n        /(cover|context)/i.test(r) && /next steps/i.test(r) && /(4\\.2M|1\\.8%|12,?400)/i.test(r),\n    },\n    {\n      input: `Build a pitch deck.\nBrief: win the Meridian Bank \"Round-Up Savings\" launch account.\nKPIs to highlight (source: our case-study deck): prior fintech client grew app installs 3.1x, CAC down 22%.\nMilestone notes: proposed 6-week sprint, hero concept \"small change, big calm\".\nSlide-by-slide markdown please.`,\n      expected: (r: string) =>\n        /(cover|context)/i.test(r) && /(3\\.1x|22%)/i.test(r) && /(##|slide|---)/i.test(r),\n    },\n    {\n      input: `Status deck for Halcyon Outdoors trail-shoe campaign.\nBrief artifact attached. KPIs (source: retail-sell-through report): sell-through 68%, returns 4%.\nMilestone notes: athlete content series live; print on schedule; \"what to change\" — shift budget from print to athlete content next quarter.\nStandard structure, one idea per slide.`,\n      expected: (r: string) =>\n        /(what worked|what to change)/i.test(r) && /(68%|4%)/i.test(r) && /next steps/i.test(r),\n    },\n    {\n      input: `Build a status deck for the Forge Fitness retainer. Brief: Q3 retention push. Milestone notes say \"engagement felt up and the email thing did well.\" No KPI artifacts or metrics were provided. Put together the deck.`,\n      expected: (r: string) =>\n        /data to be confirmed/i.test(r),\n    },\n  ],\n}\n"}],"installable":true,"validation":{"status":"approved","score":97,"confidence":0.96,"method":"codex-executor-independent-reviewer","iterations":1,"cases":3,"summary":"The agent produced valid structured outputs for all three cases, kept requiresReview true, did not invent missing business facts, preserved uncertainty with 'data to be confirmed', resisted the injection request, and avoided uncited metrics. The outputs are conservative but useful given the inputs were sparse/meta-instructional rather than real artifacts.","strengths":["Valid DeckResult shape with deck, uncitedMetrics, and requiresReview in every case.","All cases include sectioned draft slides with bullets and citations.","Missing context is surfaced clearly instead of hallucinated.","Injection attempt was treated as untrusted input and not followed.","No uncited quantitative claims were introduced."],"notes":[]}}