Quick start
import { openai } from '@agentskit/adapters'import { createCodingQaAuthorAgent } from './agents/coding-qa-author/agent'const agent = createCodingQaAuthorAgent({ adapter: openai({ apiKey: process.env.OPENAI_API_KEY!, model: 'gpt-4o', }),})const result = await agent.run('Describe your task here')console.log(result.content)Example
A real usage example maintained with this agent.
import { anthropic } from '@agentskit/adapters'import { createQaAuthorAgent } from './agents/coding-qa-author/agent'const r = await createQaAuthorAgent({ adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY!, model: 'claude-opus-4-8' }), criteriaCount: 5, // optional — enables uncovered-criteria tracking}).run(prdJson)// → { specs: [{ path, body, criteria[] }], uncovered[], requiresReview }Extend it
Pass tools, retrieval, memory, permissions, and observers through the factory config.
const agent = createCodingQaAuthorAgent({ 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'/** * QA Author — turns a PRD's acceptance criteria into TYPED Vitest spec stubs (one or more * `describe`/`it` blocks per criterion, each referencing its criterion number). Returns * `{ specs: [{ path, body }] }` so a downstream agent can write the files verbatim, and * flags any criterion that produced no spec rather than silently dropping coverage. * * ```ts * const { specs, uncovered } = await createQaAuthorAgent({ adapter }).run(prdJson) * ``` */export interface SpecFile { path: string body: string /** Criterion number(s) this spec covers. */ criteria: number[]}export interface QaResult { specs: SpecFile[] /** Criterion numbers no spec covered — review before relying on the suite. */ uncovered: number[] requiresReview: boolean}export interface QaAuthorConfig { adapter: AdapterFactory /** Total criteria in the PRD, used to compute `uncovered`. Optional. */ criteriaCount?: number memory?: ChatMemory observers?: Observer[] onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean> maxSteps?: number}const Output = z.object({ specs: z.array(z.object({ path: z.string(), body: z.string(), criteria: z.array(z.number().int()), })),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7const skill = { name: 'qa-author', description: 'Turns PRD acceptance criteria into typed Vitest spec stubs (one+ per criterion).', systemPrompt: `You write Vitest test suites for a TypeScript monorepo. For EACH acceptance criterion inthe input PRD, produce one or more spec stubs using describe and it blocks that read like executabledocumentation. Each it block must reference the criterion by number, contain a meaningful assertion(or a clear assertion comment), and compile without error. Match the project's file-naming pattern.Return specs as { path, body, criteria: [criterionNumbers] }. Do not skip a criterion silently.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_specs exactly once with { specs }. Stop.`, tools: ['submit_specs'],}export function createQaAuthorAgent(config: QaAuthorConfig) { 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_specs', description: 'Submit the spec stubs. Call exactly once.', schema: Output, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition async function run(prd: string): Promise<QaResult> { if (!prd?.trim()) throw new Error('qa author requires a PRD') emit('specs', 'start') const { specs } = await invokeStructured({ adapter: config.adapter, tool: submit(), task: `PRD:\n${fenceUntrustedContent(prd)}`, parse: (a) => Output.parse(a), skill, memory: config.memory, observers: config.observers, onConfirm: config.onConfirm, maxSteps: config.maxSteps ?? 3, }) // Flag criteria with no spec (only when the caller told us how many to expect). const covered = new Set(specs.flatMap((s) => s.criteria)) const uncovered = config.criteriaCount ? Array.from({ length: config.criteriaCount }, (_, i) => i + 1).filter((n) => !covered.has(n)) : [] emit('specs', 'ok', `${specs.length} spec(s), ${uncovered.length} uncovered`) return { specs, uncovered, requiresReview: true } } return { name: 'coding-qa-author', run, asHandle() { return { name: 'coding-qa-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-qa-author', cases: [ { input: `PRD JSON: {"criteria":["1. calculateTax(amount, region) returns the correct VAT for 'EU' at 21%","2. calculateTax throws on an unknown region"]}. The project uses Vitest and names spec files *.test.ts next to the source. Produce the spec stubs.`, expected: (r: string) => /describe\(/.test(r) && /\bit\(/.test(r) && /\.test\.ts/i.test(r), }, { input: `PRD JSON: {"criteria":["1. The /health endpoint returns 200 with { status: 'ok' } when the database is reachable"]}. Produce a Vitest spec stub referencing criterion 1.`, expected: (r: string) => /criterion 1/i.test(r) && /expect\(/.test(r), }, { input: `PRD JSON: {"criteria":["1. debounce(fn, ms) only invokes fn once after rapid successive calls within the window"]}. Generate the spec stub; you may use expect.assertions and todo placeholders but no it.skip.`, expected: (r: string) => /describe\(/.test(r) && !/it\.skip/.test(r), }, { input: `PRD JSON: {"criteria":[]}. The acceptance criteria list is empty. Produce the Vitest spec stubs.`, expected: (r: string) => /(no (criteria|criterion)|empty|nothing to|cannot|missing|escalat|need)/i.test(r), }, ],}Was this agent useful?
Your response helps us prioritize agent quality.