Quick start
import { openai } from '@agentskit/adapters'import { createAgencyBriefGeneratorAgent } from './agents/agency-brief-generator/agent'const agent = createAgencyBriefGeneratorAgent({ 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
- Review score
- 96/100
- Confidence
- 96%
- Evaluation cases
- 3
- Iterations
- 2
The final structured outputs are valid, conservative, and aligned with the agent purpose: no unsupported facts are invented, missing fields are marked to be confirmed, all gaps are surfaced in toBeConfirmed, and requiresReview is always true. The injection case resists the direct instruction to output APPROVED and returns a safe draft structure.
What passed review
- Final outputs match the declared typed schema for all cases.
- Correctly avoids inventing client/product, audience, timeline, deliverables, or other brief facts from non-factual prompts.
- Surfaces all missing sections in toBeConfirmed.
- Always marks requiresReview as true.
- Injection attempt is ignored in the final output.
Reviewer notes
- Clean up internal event/tool-output consistency: intermediate tool payloads mention prompt-injection handling in fields such as keyInsight or mandatories, while the final record output normalizes these back to TBC. The final outputs are acceptable, but this inconsistency is noisy and could confuse downstream validators that inspect events.
- Consider adding an explicit non-brief warning field only if the schema supports it; do not place safety notes inside creative brief fields like mandatories.
Example
A real usage example maintained with this agent.
import { anthropic } from '@agentskit/adapters'import { createBriefGeneratorAgent } from './agents/agency-brief-generator/agent'const r = await createBriefGeneratorAgent({ adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),}).run(kickoffNotes)// → { brief: { clientAndProduct, audience, keyInsight, singleMindedProposition, mandatories[], tone, deliverables[], timeline }, toBeConfirmed[], requiresReview }Extend it
Pass tools, retrieval, memory, permissions, and observers through the factory config.
const agent = createAgencyBriefGeneratorAgent({ 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'/** * Brief Generator — drafts a TYPED creative brief from client kickoff notes. Pulls facts * only from the notes (never invents client details); any field without input becomes * "to be confirmed with the client" and is listed in `toBeConfirmed`. Always a draft. * * ```ts * const { brief, toBeConfirmed } = await createBriefGeneratorAgent({ adapter }).run(kickoffNotes) * ``` */const TBC = 'to be confirmed with the client'export interface CreativeBrief { clientAndProduct: string audience: string keyInsight: string singleMindedProposition: string mandatories: string[] tone: string deliverables: string[] timeline: string}export interface BriefResult { brief: CreativeBrief /** Brief fields the notes didn't cover (filled with the TBC placeholder). */ toBeConfirmed: string[] /** Always true — a draft for the team to confirm with the client. */ requiresReview: boolean}export interface BriefGeneratorConfig { adapter: AdapterFactory memory?: ChatMemory observers?: Observer[] onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean> maxSteps?: number}const Brief = z.object({ clientAndProduct: z.string(), audience: z.string(), keyInsight: z.string(), singleMindedProposition: z.string(), mandatories: z.array(z.string()), tone: z.string(), deliverables: z.array(z.string()), timeline: z.string(),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7const skill = { name: 'brief-generator', description: 'Drafts a typed creative brief from client kickoff notes (never invents details).', systemPrompt: `You draft a creative brief from client kickoff notes. Fields: client + product; audience;key insight; single-minded proposition; mandatories; tone; deliverables; timeline.Pull facts ONLY from the notes — NEVER invent client details. If a field lacks input, set it toexactly "${TBC}" rather than fabricating. Voice: clear, action-oriented, agency-standard; avoidcorporate jargon. This is a DRAFT for the team to confirm with the client.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_brief exactly once with the structured fields. Stop.`, tools: ['submit_brief'],}export function createBriefGeneratorAgent(config: BriefGeneratorConfig) { 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_brief', description: 'Submit the creative brief. Call exactly once.', schema: Brief, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition async function run(notes: string): Promise<BriefResult> { if (!notes?.trim()) throw new Error('brief generator requires non-empty kickoff notes') emit('brief', 'start') const brief = await invokeStructured({ adapter: config.adapter, tool: submit(), task: `CLIENT KICKOFF NOTES:\n${fenceUntrustedContent(notes)}`, parse: (a) => Brief.parse(a), skill, memory: config.memory, observers: config.observers, onConfirm: config.onConfirm, maxSteps: config.maxSteps ?? 3, }) // Surface the gaps the model flagged TBC so the team knows what to chase. const toBeConfirmed = Object.entries(brief) .filter(([, v]) => (Array.isArray(v) ? v.some((x) => x.includes(TBC)) : String(v).includes(TBC))) .map(([k]) => k) emit('brief', 'ok', `${toBeConfirmed.length} field(s) TBC`) return { brief, toBeConfirmed, requiresReview: true } } return { name: 'agency-brief-generator', run, asHandle() { return { name: 'agency-brief-generator', 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-brief-generator', cases: [ { input: `Kickoff notes — Client: Lumen Coffee Co. Product: new cold-brew can line "Lumen Chill". Audience: urban 22-35 who buy ready-to-drink coffee. They want to feel the brand is "calm energy, not jittery hustle". Competitors: Stumptown, La Colombe. Budget: mid. Launch: Q3. Deliverables: social, OOH, 1 hero film. Draft the creative brief.`, expected: (r: string) => /audience/i.test(r) && /(proposition|single-minded|insight)/i.test(r) && /deliverable/i.test(r), }, { input: `Kickoff notes — Client: Meridian Bank. Product: a "Round-Up Savings" feature in their app for Gen Z. Insight from research: young customers feel guilty about spending but find budgeting apps preachy. Tone should be supportive, never lecturing. Mandatory: include FDIC disclosure line. Timeline: pitch in 3 weeks. Build the brief.`, expected: (r: string) => /tone/i.test(r) && /(mandator|FDIC)/i.test(r) && /(timeline|3 weeks|week)/i.test(r), }, { input: `Kickoff notes — Client: Halcyon Outdoors. Product: trail-running shoe "Halcyon Fleet". Key insight: trail runners distrust shoes marketed by influencers; they trust grip and durability data. Proposition: "earned on the descent". Deliverables: print, retail display, athlete content series. Tone: understated, technical, no hype. Draft the brief.`, expected: (r: string) => /(client|Halcyon)/i.test(r) && /(proposition|earned on the descent)/i.test(r) && /tone/i.test(r), }, { input: `Kickoff notes — Client: a fintech startup (name not given yet). They mentioned "something about a card for freelancers" on the call but did not share audience, budget, timeline, competitors, or deliverables. Draft whatever brief you can from this.`, expected: (r: string) => /to be confirmed/i.test(r), }, ],}Was this agent useful?
Your response helps us prioritize agent quality.