agency·Independently reviewed · 97/100

Deck Builder

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.

agencystructured-outputgrounding

Install

npx agentskit add agency-deck-builder

Quick start

import { openai } from '@agentskit/adapters'import { createAgencyDeckBuilderAgent } from './agents/agency-deck-builder/agent'const agent = createAgencyDeckBuilderAgent({  adapter: openai({    apiKey: process.env.OPENAI_API_KEY!,    model: 'gpt-4o',  }),})const result = await agent.run('Describe your task here')console.log(result.content)

Independent reviewer approved

Validation evidence

How validation works
Review score
97/100
Confidence
96%
Evaluation cases
3
Iterations
1

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.

What passed review

  • 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.

Example

A real usage example maintained with this agent.

import { anthropic } from '@agentskit/adapters'import { createDeckBuilderAgent } from './agents/agency-deck-builder/agent'const r = await createDeckBuilderAgent({  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),  // sections: ['cover', 'context', ...]  // optional override}).run(`${brief}\n${kpis}\n${milestoneNotes}`)// → { deck: [{ section, bullets[], citations[] }], uncitedMetrics[], requiresReview }

Extend it

Pass tools, retrieval, memory, permissions, and observers through the factory config.

const agent = createAgencyDeckBuilderAgent({  adapter,  tools,  retriever,  memory,  onConfirm: (call) => approve(call),  observers: [tracer],})
View agent factory source
import type { AdapterFactory, ChatMemory, Observer, ToolCall, ToolDefinition } from '@agentskit/core'import { fenceUntrustedContent, UNTRUSTED_CONTENT_DIRECTIVE } from '@agentskit/core/security'import { invokeStructured } from '@agentskit/runtime'import { defineZodTool } from '@agentskit/tools'import { z } from 'zod'import { zodToJsonSchema } from 'zod-to-json-schema'import type { JSONSchema7 } from 'json-schema'/** * Deck Builder — drafts a TYPED pitch/status deck from project artifacts (brief, KPIs, * milestone notes). Standard slide structure, and EVERY number must cite the source * artifact — uncited metrics are flagged, missing data becomes "data to be confirmed". * Always a draft. * * ```ts * const { deck, uncitedMetrics } = await createDeckBuilderAgent({ adapter }).run(artifacts) * ``` */const TBC = 'data to be confirmed'export interface Slide {  section: string  bullets: string[]  /** Source artifact(s) backing any numbers on this slide. */  citations: string[]}export interface DeckResult {  deck: Slide[]  /** Slides that state a number without a citation — review before sharing. */  uncitedMetrics: string[]  /** Always true — a draft for the team to review. */  requiresReview: boolean}export interface DeckBuilderConfig {  adapter: AdapterFactory  /** Override the default slide sections. */  sections?: string[]  memory?: ChatMemory  observers?: Observer[]  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>  maxSteps?: number}const DEFAULT_SECTIONS = ['cover', 'context', 'what we did', 'what worked', 'what to change', 'next steps']const NUMBER = /\d/const Deck = z.object({  slides: z.array(z.object({    section: z.string(),    bullets: z.array(z.string()),    citations: z.array(z.string()),  })),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7const buildSkill = (sections: string[]) => ({  name: 'deck-builder',  description: 'Drafts a typed pitch/status deck from project artifacts; every number cites its source.',  systemPrompt: `You draft a pitch or status deck from the project brief, KPIs, and milestone notes.Slide sections, in order: ${sections.join(', ')}.EVERY number must cite the source artifact in that slide's citations. Do NOT invent metrics. Wheredata is missing, write "${TBC}" in the bullet rather than guessing. This is a DRAFT for the team.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_deck exactly once with { slides: [{ section, bullets, citations }] }. Stop.`,  tools: ['submit_deck'],})export function createDeckBuilderAgent(config: DeckBuilderConfig) {  const sections = config.sections ?? DEFAULT_SECTIONS  const skill = buildSkill(sections)  const emit = (label: string, status: 'start' | 'ok' | 'skip' | 'error', detail?: string) => {    for (const o of config.observers ?? []) void o.on({ type: 'progress', label, status, detail })  }  const submit = (): ToolDefinition =>    defineZodTool({ name: 'submit_deck', description: 'Submit the deck. Call exactly once.', schema: Deck, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition  async function run(artifacts: string): Promise<DeckResult> {    if (!artifacts?.trim()) throw new Error('deck builder requires project artifacts')    emit('deck', 'start')    const { slides } = await invokeStructured({      adapter: config.adapter,      tool: submit(),      task: `PROJECT ARTIFACTS:\n${fenceUntrustedContent(artifacts)}`,      parse: (a) => Deck.parse(a),      skill,      memory: config.memory,      observers: config.observers,      onConfirm: config.onConfirm,      maxSteps: config.maxSteps ?? 3,    })    // Flag any slide that quotes a number on a bullet but carries no citation (excluding TBC bullets).    const uncitedMetrics = slides      .filter((s) => s.citations.length === 0 && s.bullets.some((b) => NUMBER.test(b) && !b.includes(TBC)))      .map((s) => s.section)    emit('deck', 'ok', `${slides.length} slide(s), ${uncitedMetrics.length} uncited`)    return { deck: slides, uncitedMetrics, requiresReview: true }  }  return {    name: 'agency-deck-builder',    run,    asHandle() {      return { name: 'agency-deck-builder', run: async (task: string) => JSON.stringify(await run(task)) }    },  }}
View evaluation contract

Replay these cases with the provider and model you plan to deploy.

import type { EvalSuite } from '@agentskit/eval'export const suite: EvalSuite = {  name: 'agency-deck-builder',  cases: [    {      input: `Build a status deck.Brief: Q2 social campaign for Lumen Chill cold-brew.KPIs (source: GA4 export): reach 4.2M, CTR 1.8%, sign-ups 12,400.Milestone notes: hero film delivered on time; OOH delayed 1 week; UGC contest over-performed (+40% entries vs target).Use the standard deck structure.`,      expected: (r: string) =>        /(cover|context)/i.test(r) && /next steps/i.test(r) && /(4\.2M|1\.8%|12,?400)/i.test(r),    },    {      input: `Build a pitch deck.Brief: win the Meridian Bank "Round-Up Savings" launch account.KPIs to highlight (source: our case-study deck): prior fintech client grew app installs 3.1x, CAC down 22%.Milestone notes: proposed 6-week sprint, hero concept "small change, big calm".Slide-by-slide markdown please.`,      expected: (r: string) =>        /(cover|context)/i.test(r) && /(3\.1x|22%)/i.test(r) && /(##|slide|---)/i.test(r),    },    {      input: `Status deck for Halcyon Outdoors trail-shoe campaign.Brief artifact attached. KPIs (source: retail-sell-through report): sell-through 68%, returns 4%.Milestone notes: athlete content series live; print on schedule; "what to change" — shift budget from print to athlete content next quarter.Standard structure, one idea per slide.`,      expected: (r: string) =>        /(what worked|what to change)/i.test(r) && /(68%|4%)/i.test(r) && /next steps/i.test(r),    },    {      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.`,      expected: (r: string) =>        /data to be confirmed/i.test(r),    },  ],}

Was this agent useful?

Your response helps us prioritize agent quality.

Keep exploring

Related agents

View category