Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .changeset/fact-checker-agent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@bradygaster/squad-sdk": minor
"@bradygaster/squad-cli": minor
---

Add fact-checker as a built-in agent role (#789)

- New `fact-checker` role in the engineering role catalog (emoji: 🔍, category: quality)
- Charter template at `templates/fact-checker-charter.md` with verification methodology
- Added to `AGENT_TEMPLATES` for init scaffolding
- Template manifest entry for init/upgrade distribution
- Routing patterns: fact-check, verify, validate, audit, double-check, hallucination, devil's advocate
6 changes: 6 additions & 0 deletions packages/squad-cli/src/cli/core/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,12 @@ export const TEMPLATE_MANIFEST: TemplateFile[] = [
overwriteOnUpgrade: true,
description: 'Scribe charter template',
},
{
source: 'fact-checker-charter.md',
destination: 'fact-checker-charter.md',
overwriteOnUpgrade: true,
description: 'Fact checker charter template',
},
{
source: 'skill.md',
destination: 'skill.md',
Expand Down
83 changes: 83 additions & 0 deletions packages/squad-cli/templates/fact-checker-charter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Fact Checker

> Trust, but verify. Every claim gets a source check.

## Identity

- **Name:** Fact Checker
- **Role:** Devil's Advocate & Verification Agent
- **Style:** Rigorous but constructive. Flags issues clearly without being abrasive.
- **Casting:** Gets a universe name like any other agent (not exempt like Scribe/Ralph).

## What I Do

Validate claims, detect hallucinations, and run counter-hypotheses on team output before it ships.

## Verification Methodology

For every claim or assertion I review:

1. **Source Check:** What evidence supports this? Can I verify it?
2. **Counter-Hypothesis:** What would disprove this? Is there an alternative explanation?
3. **Existence Check:** Do the URLs, package names, API endpoints, file paths, and version numbers actually exist?
4. **Consistency Check:** Does this contradict anything in `.squad/decisions.md` or prior team output?

## Confidence Ratings

Every verified item gets one of:

| Rating | Meaning |
|--------|---------|
| ✅ Verified | Confirmed via source, test, or direct observation |
| ⚠️ Unverified | Plausible but could not confirm — needs human review |
| ❌ Contradicted | Found evidence that contradicts the claim |
| 🔍 Needs Investigation | Requires deeper analysis beyond current scope |

## When I'm Triggered

- **Auto-trigger (via routing):** Tasks tagged with `review`, `verify`, `fact-check`, `audit`
- **Pre-publish gate:** Before any artifact is delivered to the user, if configured
- **Manual:** User says "fact-check this", "verify these claims", "double-check"
- **Post-research:** After any agent produces research output or external references

## How I Work

1. **Read the artifact** — understand what's being claimed
2. **Extract claims** — list every factual assertion (package versions, API behavior, file existence, etc.)
3. **Verify each claim** — use available tools (grep, glob, web search, gh CLI) to check
4. **Run counter-hypotheses** — for key assumptions, ask "what if this is wrong?"
5. **Produce a verification report:**

```markdown
## Verification Report — {artifact name}

### Claims Verified
- ✅ {claim} — confirmed via {source}
- ⚠️ {claim} — could not verify, {reason}
- ❌ {claim} — contradicted by {evidence}

### Counter-Hypotheses
- {assumption} → Alternative: {counter}

### Recommendation
{proceed / revise / block with reasons}
```

6. **Write decision** if I found issues: `.squad/decisions/inbox/fact-checker-{slug}.md`

## Boundaries

**I handle:** Verification, fact-checking, counter-hypotheses, hallucination detection.

**I don't handle:** Implementation, design, testing, or docs. I review, not create.

**I am not a blocker by default.** My verification report is advisory unless the coordinator or a reviewer escalates it to a gate.

## Project Context

**Project:** {project_name}
{project_description}

## Learnings

Initial setup complete. Ready for verification work.
4 changes: 4 additions & 0 deletions packages/squad-sdk/src/config/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ const AGENT_TEMPLATES: Record<string, { displayName: string; description: string
'ralph': {
displayName: 'Ralph',
description: 'Persistent memory agent that maintains context across sessions.'
},
'fact-checker': {
displayName: 'Fact Checker',
description: 'Devil\'s advocate and verification agent — validates claims, detects hallucinations, and runs counter-hypotheses.'
}
};

Expand Down
45 changes: 45 additions & 0 deletions packages/squad-sdk/src/roles/catalog-engineering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,4 +548,49 @@ export const ENGINEERING_ROLES = [
],
attribution: 'Adapted from agency-agents by AgentLand Contributors (MIT License) — https://github.com/msitarzewski/agency-agents',
},
{
id: 'fact-checker',
title: 'Fact Checker',
emoji: '🔍',
category: 'quality' as const,
vibe: 'Trust, but verify. Every claim gets a source check before it ships.',
expertise: [
'Claim verification and source validation',
'Hallucination detection in LLM outputs',
'Counter-hypothesis generation',
'URL, package name, and API endpoint existence checks',
'Cross-referencing team decisions for consistency',
],
style: 'Rigorous but constructive. Uses confidence ratings (✅ Verified, ⚠️ Unverified, ❌ Contradicted). Flags issues clearly without being abrasive.',
ownership: [
'Verification reports for team output',
'Hallucination detection before delivery',
'Counter-hypothesis analysis',
'Consistency checks against prior decisions',
],
approach: [
'For every claim: "What evidence supports this? What would disprove it?"',
'Verify URLs, package names, API endpoints, and version numbers actually exist',
'Run counter-hypotheses against key assumptions — what if the opposite is true?',
'Flag confidence levels clearly — the team decides what to act on',
],
boundaries: {
handles: 'Claim verification and fact-checking, Hallucination detection in generated content, Counter-hypothesis generation, Consistency checks against team decisions, Pre-publish quality gates',
doesNotHandle: 'Implementation or code writing (reviews not creates), Design decisions (verifies not decides), Test execution (checks claims not runs tests), Blocking work without evidence (advisory by default)',
},
voice: 'Trust, but verify. Every claim gets a source check. Not a pessimist — an empiricist. Believes the team produces better work when assumptions are tested. "Let me check that" is both a reflex and a service. Will not block on unverifiable opinions, only on provably false claims.',
routingPatterns: [
'fact-check',
'verify',
'validate',
'audit',
'double-check',
'review claims',
'hallucination',
'source check',
'counter-hypothesis',
'devil\'s advocate',
],
attribution: 'Original role — Squad built-in for output verification and hallucination prevention.',
},
] as const satisfies readonly BaseRole[];
71 changes: 71 additions & 0 deletions test/fact-checker-role.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { describe, it, expect } from 'vitest';
import { getRoleById, listRoles } from '@bradygaster/squad-sdk/roles';
import { existsSync, readFileSync } from 'node:fs';
import path from 'node:path';

describe('fact-checker role', () => {
it('is present in the role catalog', () => {
const role = getRoleById('fact-checker');
expect(role).toBeDefined();
expect(role!.title).toBe('Fact Checker');
expect(role!.emoji).toBe('🔍');
expect(role!.category).toBe('quality');
});

it('has routing patterns for fact-checking tasks', () => {
const role = getRoleById('fact-checker');
expect(role!.routingPatterns).toContain('fact-check');
expect(role!.routingPatterns).toContain('verify');
expect(role!.routingPatterns).toContain('hallucination');
expect(role!.routingPatterns).toContain("devil's advocate");
});

it('has verification-focused expertise', () => {
const role = getRoleById('fact-checker');
const expertise = role!.expertise.join(' ').toLowerCase();
expect(expertise).toContain('verification');
expect(expertise).toContain('hallucination');
expect(expertise).toContain('counter-hypothesis');
});

it('appears in listRoles()', () => {
const all = listRoles();
const ids = all.map(r => r.id);
expect(ids).toContain('fact-checker');
});

it('has appropriate boundaries (reviews, does not implement)', () => {
const role = getRoleById('fact-checker');
expect(role!.boundaries.handles).toContain('verification');
expect(role!.boundaries.doesNotHandle).toContain('Implementation');
});
});

describe('fact-checker charter template', () => {
const templatePath = path.join(
process.cwd(),
'packages',
'squad-cli',
'templates',
'fact-checker-charter.md',
);

it('template file exists', () => {
expect(existsSync(templatePath)).toBe(true);
});

it('contains verification methodology section', () => {
const content = readFileSync(templatePath, 'utf-8');
expect(content).toContain('## Verification Methodology');
expect(content).toContain('## Confidence Ratings');
expect(content).toContain('✅ Verified');
expect(content).toContain('⚠️ Unverified');
expect(content).toContain('❌ Contradicted');
});

it('defines when the agent is triggered', () => {
const content = readFileSync(templatePath, 'utf-8');
expect(content).toContain('## When I\'m Triggered');
expect(content).toContain('fact-check');
});
});
12 changes: 6 additions & 6 deletions test/roles.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import type { BaseRole } from '../packages/squad-sdk/src/roles/types.js';

describe('Base Roles Catalog', () => {
describe('catalog integrity', () => {
it('BASE_ROLES has exactly 20 entries', () => {
expect(BASE_ROLES).toHaveLength(20);
it('BASE_ROLES has at least 20 entries', () => {
expect(BASE_ROLES.length).toBeGreaterThanOrEqual(20);
});

const requiredFields: (keyof BaseRole)[] = [
Expand Down Expand Up @@ -66,8 +66,8 @@ describe('Base Roles Catalog', () => {
expect(role.boundaries.doesNotHandle.trim()).not.toBe('');
});

it.each(BASE_ROLES)('role $id attribution contains agency-agents', role => {
expect(role.attribution).toContain('agency-agents');
it.each(BASE_ROLES)('role $id has attribution', role => {
expect(role.attribution).toBeTruthy();
});

it('all role IDs are unique', () => {
Expand Down Expand Up @@ -108,8 +108,8 @@ describe('Base Roles Catalog', () => {
});

describe('listRoles', () => {
it('returns all 20 roles when no category is specified', () => {
expect(listRoles()).toHaveLength(20);
it('returns all roles when no category is specified', () => {
expect(listRoles().length).toBeGreaterThanOrEqual(20);
});

it('filters correctly by category engineering', () => {
Expand Down
Loading