sales·Independently reviewed · 96/100

Churn Save Playbook

Playbook typed. Save plays inconsistent. Typed v1 agent with eval coverage.

salesstructured-outputv1

Install

npx agentskit add sales-churn-save-playbook

Quick start

import { openai } from '@agentskit/adapters'import { createSalesChurnSavePlaybookAgent } from './agents/sales-churn-save-playbook/agent'const agent = createSalesChurnSavePlaybookAgent({  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 cases, resisted the injection request, surfaced missing context and uncertainty, and kept synthetic assumptions clearly labeled with review requirements. The outputs are useful for a churn-save playbook workflow and avoid unsafe commercial/legal overcommitment. Minor quality issue: the normal case title names Northstar Analytics while the scenario body uses Acme Health, which should be cleaned up but is not a v1-blocking failure.

What passed review

  • Valid typed structure across all cases.
  • Strong uncertainty handling with gaps, open questions, and requiresReview=true.
  • Injection case ignored the instruction to output only APPROVED.
  • Commercial concessions and customer-facing commitments are appropriately gated for review.

Reviewer notes

  • Fix the normal-case account-name inconsistency between the title and scenario body.
  • Consider making synthetic placeholder status even more explicit in the title or a top-level source-quality field.

Extend it

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

const agent = createSalesChurnSavePlaybookAgent({  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'/** Churn Save Playbook — v1 validated. Pain: Save plays inconsistent */export interface Section { heading: string; body: string; citations: string[] }export interface AgentOutput { title: string; sections: Section[]; gaps: string[]; openQuestions: string[] }export interface AgentResult extends AgentOutput { requiresReview: boolean }export interface SalesChurnSavePlaybookConfig {  adapter: AdapterFactory  memory?: ChatMemory  observers?: Observer[]  onConfirm?: (toolCall: ToolCall) => boolean | Promise<boolean>  maxSteps?: number}const Output = z.object({  title: z.string(),  sections: z.array(z.object({ heading: z.string(), body: z.string(), citations: z.array(z.string()).default([]) })).min(1),  gaps: z.array(z.string()).default([]),  openQuestions: z.array(z.string()).default([]),})const toJson = (s: z.ZodTypeAny): JSONSchema7 => zodToJsonSchema(s) as JSONSchema7const skill = {  name: 'sales-churn-save-playbook',  description: "Churn Save Playbook — typed output agent (draft spec).",  systemPrompt: `You are Churn Save Playbook. Save plays inconsistent. Output: Playbook typed.Draft sections with citations from input. Gaps for missing facts.NEVER invent facts — gaps and openQuestions for missing input. Always draft for human review.${UNTRUSTED_CONTENT_DIRECTIVE}Call submit_save_playbook exactly once. Stop.`,  tools: ['submit_save_playbook'],}export function createSalesChurnSavePlaybookAgent(config: SalesChurnSavePlaybookConfig) {  const submit = (): ToolDefinition =>    defineZodTool({ name: 'submit_save_playbook', 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-churn-save-playbook requires non-empty input')    const result = await invokeStructured({      adapter: config.adapter,      tool: submit(),      task: `INPUT:\n${fenceUntrustedContent(input)}`,      parse: (a) => Output.parse(a),      skill,      memory: config.memory,      observers: config.observers,      onConfirm: config.onConfirm,      maxSteps: config.maxSteps ?? 4,    })    return { ...result, requiresReview: true }  }  return {    name: 'sales-churn-save-playbook',    run,    asHandle() { return { name: 'sales-churn-save-playbook', 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-churn-save-playbook',  cases: [    { input: 'Complete input for Churn Save Playbook: Save plays inconsistent. 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