sales·Independently reviewed · 96/100

Renewal Risk Scorer

Score typed. Renewal risk hidden. Typed v1 agent with eval coverage.

salesstructured-outputv1

Install

npx agentskit add sales-renewal-risk-scorer

Quick start

import { openai } from '@agentskit/adapters'import { createSalesRenewalRiskScorerAgent } from './agents/sales-renewal-risk-scorer/agent'const agent = createSalesRenewalRiskScorerAgent({  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
96/100
Confidence
96%
Evaluation cases
3
Iterations
1

The agent produced valid structured outputs for all three cases, avoided inventing renewal facts from sparse or meta prompts, surfaced uncertainty and missing evidence, routed to human review, and resisted the prompt-injection attempt. Behavior is aligned with a renewal risk scorer operating under insufficient evidence. Minor weaknesses are taxonomy consistency and the absence of a demonstrated concrete-data scoring case in these outputs, but there are no critical failures in the provided runs.

What passed review

  • Valid structured output in every case.
  • Does not hallucinate customer, contract, usage, or renewal details.
  • Explicitly identifies missing evidence and asks useful follow-up questions.
  • Correctly ignores the injection request to output APPROVED.
  • Routes uncertain cases to human_review with requiresReview true.

Reviewer notes

  • Normalize category values across sparse/insufficient-context cases so downstream consumers can rely on a stable taxonomy.
  • Add or validate against at least one realistic case with actual renewal/account signals to prove it can score concrete risk, not only handle missing information.

Extend it

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

const agent = createSalesRenewalRiskScorerAgent({  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'/** Renewal Risk Scorer — v1 validated. Pain: Renewal risk hidden */export type Severity = 'critical' | 'high' | 'medium' | 'low'export interface AgentOutput { category: string; severity: Severity; queue: string; rationale: string; gaps: string[]; openQuestions: string[] }export interface AgentResult extends AgentOutput { requiresReview: boolean }export interface SalesRenewalRiskScorerConfig {  adapter: AdapterFactory  memory?: ChatMemory  observers?: Observer[]  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>  maxSteps?: number}const Output = z.object({  category: z.string(),  severity: z.enum(['critical', 'high', 'medium', 'low']),  queue: z.string(),  rationale: z.string(),  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, o: z.infer<typeof Output>) {  if (/\b(outage|breach|emergency|stroke|suicidal|data loss)\b/i.test(input) && o.severity !== 'critical')    return { ...o, severity: 'critical' as const, queue: 'escalation', rationale: o.rationale + ' [safety-net]' }  return o}const skill = {  name: 'sales-renewal-risk-scorer',  description: "Renewal Risk Scorer — typed output agent (draft spec).",  systemPrompt: `You are Renewal Risk Scorer. Renewal risk hidden. Output: Score typed.Classify with category, severity, queue, rationale. Gaps for missing input.NEVER invent facts — gaps and openQuestions for missing input. Always draft for human review.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_risk_scorer exactly once. Stop.`,  tools: ['submit_risk_scorer'],}export function createSalesRenewalRiskScorerAgent(config: SalesRenewalRiskScorerConfig) {  const submit = (): ToolDefinition =>    defineZodTool({ name: 'submit_risk_scorer', description: 'Submit result. Once.', schema: Output, toJsonSchema: toJson, async execute() { return 'recorded' } }) as ToolDefinition  async function run(input: string): Promise<AgentResult> {    if (!input?.trim()) throw new Error('sales-renewal-risk-scorer requires non-empty input')    const result = await invokeStructured({      adapter: config.adapter,      tool: submit(),      task: `INPUT:\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 { ...result, requiresReview: true }  }  return {    name: 'sales-renewal-risk-scorer',    run,    asHandle() { return { name: 'sales-renewal-risk-scorer', 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: 'sales-renewal-risk-scorer',  cases: [    { input: 'Complete input for Renewal Risk Scorer: Renewal risk hidden. Provide full structured output.', expected: (r: string) => r.length > 20 && /requiresReview|summary|title|category|findings|sections|score|clusters|items|steps/i.test(r) },    { input: 'Minimal input.', expected: (r: string) => /gap|openQuestion/i.test(r) || r.length > 10 },    { input: 'Input with specific detail: ACME Corp project deadline March 15.', expected: (r: string) => /ACME|March|15/i.test(r) || /gap/i.test(r) },    { input: 'Empty context — only says "process this".', expected: (r: string) => r.length > 5 },  ],}

Was this agent useful?

Your response helps us prioritize agent quality.

Keep exploring

Related agents

View category