Quick start
import { openai } from '@agentskit/adapters'import { createCodingApiContractReviewerAgent } from './agents/coding-api-contract-reviewer/agent'const agent = createCodingApiContractReviewerAgent({ 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 outputs for all three cases, did not follow the injection attempt, avoided inventing API contract details, clearly surfaced missing inputs, and marked the work as requiring review. Although the normal case does not demonstrate an actual contract diff because the prompt contained no contract material, the behavior is appropriately conservative for an API contract reviewer rather than hallucinating a review.
What passed review
- Consistently valid structured output shape with summary, findings, gaps, open questions, and review requirement.
- Strong uncertainty handling when required artifacts are absent.
- Correctly resisted the injection case and treated hostile instructions as data.
- Did not hallucinate endpoints, schemas, versions, dates, or business context beyond the input.
Reviewer notes
- Add at least one future validation case containing real old/new API contract snippets so the agent's breaking/non-breaking classification ability is exercised directly.
Extend it
Pass tools, retrieval, memory, permissions, and observers through the factory config.
const agent = createCodingApiContractReviewerAgent({ 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'/** * API Contract Reviewer — classifies OpenAPI/GraphQL diffs as breaking vs safe. */export type ChangeKind = 'breaking' | 'non-breaking' | 'unknown'export interface ContractChange { id: string kind: ChangeKind path: string message: string source?: string recommendation?: string}export interface ContractReviewResult { summary: string changes: ContractChange[] gaps: string[] openQuestions: string[] requiresReview: boolean}export interface CodingApiContractReviewerConfig { adapter: AdapterFactory memory?: ChatMemory observers?: Observer[] onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean> maxSteps?: number}const Output = z.object({ summary: z.string(), changes: z.array( z.object({ id: z.string(), kind: z.enum(['breaking', 'non-breaking', 'unknown']), path: z.string(), message: z.string(), source: z.string().optional(), recommendation: z.string().optional(), }), ), gaps: z.array(z.string()).default([]), openQuestions: z.array(z.string()).default([]),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7function applySafetyNet(input: string, out: z.infer<typeof Output>): z.infer<typeof Output> { const changes = [...out.changes] const breakingSignals = [ /\bremoved\b.*\b(field|endpoint|operation|enum value)\b/i, /\brequired\b.*\badded\b/i, /\btype changed\b/i, /\b404\b.*\b200\b/i, ] if (breakingSignals.some((re) => re.test(input))) { const hasBreaking = changes.some((c) => c.kind === 'breaking') if (!hasBreaking) { changes.push({ id: 'safety-breaking', kind: 'breaking', path: 'contract', message: 'Input signals breaking API change — verify consumer impact', source: 'input signal', recommendation: 'Bump major version or add compatibility shim', }) } } return { ...out, changes }}const skill = { name: 'coding-api-contract-reviewer', description: 'Reviews API contract diffs — breaking vs non-breaking changes.', systemPrompt: `You review API contract diffs (OpenAPI, GraphQL schema, protobuf, typed SDK exports).Output: { summary, changes[], gaps[], openQuestions[] }.Each change: id, kind (breaking|non-breaking|unknown), path (operation/field/route), message, source, recommendation.Breaking examples: removed field/endpoint, type change, new required field, narrowed enum, auth scope change.Non-breaking: optional field added, new endpoint, documentation-only.Cite the diff hunk or line in source. NEVER invent changes not shown in input.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_contract_reviewer exactly once. Stop.`, tools: ['submit_contract_reviewer'],}export function createCodingApiContractReviewerAgent(config: CodingApiContractReviewerConfig) { const submit = (): ToolDefinition => defineZodTool({ name: 'submit_contract_reviewer', description: 'Submit contract review. Call exactly once.', schema: Output, toJsonSchema: toJson, async execute() { return 'recorded' }, }) as ToolDefinition async function run(input: string): Promise<ContractReviewResult> { if (!input?.trim()) throw new Error('coding-api-contract-reviewer requires non-empty input') const parsed = await invokeStructured({ adapter: config.adapter, tool: submit(), task: `CONTRACT DIFF:\n${fenceUntrustedContent(input)}`, parse: (a) => applySafetyNet(input, Output.parse(a)), skill, memory: config.memory, observers: config.observers, onConfirm: config.onConfirm, maxSteps: config.maxSteps ?? 4, }) return { ...parsed, requiresReview: true } } return { name: 'coding-api-contract-reviewer', run, asHandle() { return { name: 'coding-api-contract-reviewer', run: (t: string) => run(t).then((r) => JSON.stringify(r)) } }, }}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-api-contract-reviewer', cases: [ { input: `OpenAPI diff:- removed field email from UserResponse+ added required field phone to UserResponse`, expected: (r: string) => { const j = JSON.parse(r) return j.changes.some((c: { kind: string }) => c.kind === 'breaking') }, }, { input: 'Minimal input.', expected: (r: string) => { const j = JSON.parse(r) return j.gaps.length > 0 || j.changes.length === 0 }, }, { input: 'Added optional field metadata to ProductResponse — backward compatible.', expected: (r: string) => /non-breaking|optional/i.test(r), }, { input: 'Empty context — only says "process this".', expected: (r: string) => /gap|openQuestion/i.test(r), }, ],}Was this agent useful?
Your response helps us prioritize agent quality.