Quick start
import { openai } from '@agentskit/adapters'import { createCodingPrdAuthorAgent } from './agents/coding-prd-author/agent'const agent = createCodingPrdAuthorAgent({ 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
- 1
The agent produced valid structured PRD outputs in all cases, kept requiresReview true, provided 3 acceptance criteria each time, surfaced missing context as open questions, and resisted the injection attempt without following it. It also avoided inventing concrete users, dates, business logic, or feature behavior from sparse/meta inputs. The main weakness is that for underspecified inputs the acceptance criteria become meta-criteria about the PRD output rather than product-level criteria, but that is a reasonable safe fallback given the explicit no-invention requirement.
What passed review
- Valid structured outputs with expected prd shape and requiresReview true for every case.
- Consistently avoided hallucinating product requirements from absent context.
- Handled prompt injection by treating override text as untrusted input.
- Produced useful open questions that would unblock a real PRD draft.
- Maintained 3 testable criteria in each case.
Example
A real usage example maintained with this agent.
import { anthropic } from '@agentskit/adapters'import { createPrdAuthorAgent } from './agents/coding-prd-author/agent'const r = await createPrdAuthorAgent({ adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }),}).run(productDescription)// → { prd: { problem, users[], criteria[], outOfScope[], openQuestions[] }, requiresReview }Extend it
Pass tools, retrieval, memory, permissions, and observers through the factory config.
const agent = createCodingPrdAuthorAgent({ 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'/** * PRD Author — transforms a free-form product description into a TYPED PRD engineers can * act on: problem, users, 3–5 testable acceptance criteria, out-of-scope, open questions. * Never invents business logic — anything absent from the input becomes an open question. * * ```ts * const { prd } = await createPrdAuthorAgent({ adapter }).run(productDescription) * ``` */export interface PRD { problem: string users: string[] /** 3–5 testable acceptance criteria. */ criteria: string[] outOfScope: string[] openQuestions: string[]}export interface PrdResult { prd: PRD requiresReview: boolean}export interface PrdAuthorConfig { adapter: AdapterFactory memory?: ChatMemory observers?: Observer[] onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean> maxSteps?: number}const Prd = z.object({ problem: z.string(), users: z.array(z.string()), criteria: z.array(z.string()).min(1).max(8), outOfScope: z.array(z.string()), openQuestions: z.array(z.string()),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7const skill = { name: 'prd-author', description: 'Transforms a product description into a typed, testable PRD (never invents logic).', systemPrompt: `You are a senior PM in a TypeScript monorepo. Transform a free-form product descriptioninto a PRD engineers can act on without ambiguity. Fields: problem statement; target users; acceptancecriteria (3–5 TESTABLE items); out-of-scope items; open questions.NEVER invent business logic absent from the input. Anything the input doesn't specify becomes an openquestion, not a guess.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_prd exactly once with { problem, users, criteria, outOfScope, openQuestions }. Stop.`, tools: ['submit_prd'],}export function createPrdAuthorAgent(config: PrdAuthorConfig) { 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_prd', description: 'Submit the PRD. Call exactly once.', schema: Prd, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition async function run(description: string): Promise<PrdResult> { if (!description?.trim()) throw new Error('prd author requires a non-empty product description') emit('prd', 'start') const prd = await invokeStructured({ adapter: config.adapter, tool: submit(), task: `PRODUCT DESCRIPTION:\n${fenceUntrustedContent(description)}`, parse: (a) => Prd.parse(a), skill, memory: config.memory, observers: config.observers, onConfirm: config.onConfirm, maxSteps: config.maxSteps ?? 3, }) emit('prd', 'ok', `${prd.criteria.length} criteria, ${prd.openQuestions.length} open`) return { prd, requiresReview: true } } return { name: 'coding-prd-author', run, asHandle() { return { name: 'coding-prd-author', 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: 'coding-prd-author', cases: [ { input: `We want to let users export their dashboard data as CSV. They should be able to pick a date range and download a file. It needs to handle large datasets without timing out.`, expected: (r: string) => /"criteria"/i.test(r) && /"openQuestions"/i.test(r) && /"outOfScope"/i.test(r), }, { input: `Build a referral program: existing users get a unique link, and when a new user signs up via that link both get a 10 dollar credit. Fraud prevention should stop self-referrals.`, expected: (r: string) => /"problem"/i.test(r) && /"users"/i.test(r) && /referral/i.test(r), }, { input: `Add real-time presence indicators to the chat so users can see who is online. Use whatever realtime tech makes sense.`, expected: (r: string) => /"openQuestions"/i.test(r) && /(realtime|websocket|transport|tech)/i.test(r), }, { input: `Make the app better and increase engagement.`, expected: (r: string) => /"openQuestions"/i.test(r) && /(vague|unclear|undefined|ambiguous|what|which|specif)/i.test(r), }, ],}Was this agent useful?
Your response helps us prioritize agent quality.