diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..652dc02 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + typecheck: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run typecheck + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + - run: npm ci + - run: npm run build diff --git a/README.md b/README.md index db83423..cd2e44c 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,23 @@ Multi-agent system built on the Anthropic TypeScript SDK (`@anthropic-ai/sdk`). ## Architecture -A **coordinator** agent (Opus 4.8) orchestrates two specialist subagents via tool calls: +A **coordinator** agent (Opus 4.8) orchestrates three specialist subagents via tool calls: - `delegate_research(topic)` → research subagent (Haiku 4.5 + `web_search`) returns a JSON summary with sources. +- `delegate_fact_check(topic)` → fact-checker subagent (Haiku 4.5) verifies findings for contradictions, unsupported claims, and gaps. - `delegate_writing(topic)` → writer subagent (Opus 4.8 + adaptive thinking) produces the final markdown report. -Each subagent runs its own tool-runner loop. The coordinator decides when to fan out research and when to write. +Each subagent runs its own loop. The coordinator follows a research → verify → write workflow. ``` user request │ ▼ coordinator (Opus 4.8) - ├── delegate_research ──▶ researcher (Haiku 4.5 + web_search) - ├── delegate_research ──▶ researcher - └── delegate_writing ──▶ writer (Opus 4.8) + ├── delegate_research ──▶ researcher (Haiku 4.5 + web_search) + ├── delegate_research ──▶ researcher + ├── delegate_fact_check ──▶ fact-checker (Haiku 4.5) + └── delegate_writing ──▶ writer (Opus 4.8) ``` ## Setup @@ -44,6 +46,7 @@ npm run research -- "TypeScript 5.7 changes" - `src/client.ts` — shared Anthropic client + model IDs. - `src/agents/researcher.ts` — research subagent (web search + structured summary tool). +- `src/agents/fact-checker.ts` — fact-checker subagent (verifies findings before writing). - `src/agents/writer.ts` — writer subagent (adaptive thinking, no tools). - `src/coordinator.ts` — top-level coordinator exposing subagents as tools. - `src/index.ts` — CLI entry point. diff --git a/package-lock.json b/package-lock.json index da126fa..c89d29b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,22 +8,23 @@ "name": "agents", "version": "0.1.0", "dependencies": { - "@anthropic-ai/sdk": "^0.88.0", - "zod": "^3.23.8" + "@anthropic-ai/sdk": "^0.105.0", + "zod": "^4.4.3" }, "devDependencies": { - "@types/node": "^22.10.0", + "@types/node": "^26.0.0", "tsx": "^4.19.0", - "typescript": "^5.7.0" + "typescript": "^6.0.3" } }, "node_modules/@anthropic-ai/sdk": { - "version": "0.88.0", - "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.88.0.tgz", - "integrity": "sha512-QQOtB5U9ZBJQj6y1ICmDZl14LWa4JCiJRoihI+0yuZ4OjbONrakP0yLwPv4DJFb3VYCtQM31bTOpCBMs2zghPw==", + "version": "0.105.0", + "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.105.0.tgz", + "integrity": "sha512-sDyu+aM9cE6uZE+HgRjjHRb+qqb87GHZOx+8bE0YlWetdL1YcVLxn8h9ltxGOflyChTe6PMEo50kMQV4cw0hfg==", "license": "MIT", "dependencies": { - "json-schema-to-ts": "^3.1.1" + "json-schema-to-ts": "^3.1.1", + "standardwebhooks": "^1.0.0" }, "bin": { "anthropic-ai-sdk": "bin/cli" @@ -488,14 +489,20 @@ "node": ">=18" } }, + "node_modules/@stablelib/base64": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stablelib/base64/-/base64-1.0.1.tgz", + "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==", + "license": "MIT" + }, "node_modules/@types/node": { - "version": "22.19.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.20.tgz", - "integrity": "sha512-6tELRwSDYWW9EdZhbeZmYGZ1/7Djkt+Ah3/ScEYT9cDord7UJzasR/4D3VONg9tQI5CDp+/CZC1AXj2pCFOvpw==", + "version": "26.0.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.0.0.tgz", + "integrity": "sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~8.3.0" } }, "node_modules/esbuild": { @@ -540,6 +547,12 @@ "@esbuild/win32-x64": "0.28.0" } }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense" + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -568,6 +581,16 @@ "node": ">=16" } }, + "node_modules/standardwebhooks": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/standardwebhooks/-/standardwebhooks-1.0.0.tgz", + "integrity": "sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==", + "license": "MIT", + "dependencies": { + "@stablelib/base64": "^1.0.0", + "fast-sha256": "^1.3.0" + } + }, "node_modules/ts-algebra": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", @@ -594,9 +617,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -608,16 +631,16 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.3.0.tgz", + "integrity": "sha512-j375ScV60dom+YkPFIfTLcOiPxkN/buHz5GobjLhixFuANaNs3C9l4GmrWqejgXWJ7BbJcFYpTEUkS1Ge8bpZQ==", "dev": true, "license": "MIT" }, "node_modules/zod": { - "version": "3.25.76", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", - "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/package.json b/package.json index 6b4ed4e..b5a1fee 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,12 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@anthropic-ai/sdk": "^0.88.0", - "zod": "^3.23.8" + "@anthropic-ai/sdk": "^0.105.0", + "zod": "^4.4.3" }, "devDependencies": { - "@types/node": "^22.10.0", + "@types/node": "^26.0.0", "tsx": "^4.19.0", - "typescript": "^5.7.0" + "typescript": "^6.0.3" } } diff --git a/src/agents/fact-checker.ts b/src/agents/fact-checker.ts new file mode 100644 index 0000000..e099b9a --- /dev/null +++ b/src/agents/fact-checker.ts @@ -0,0 +1,26 @@ +import { client, FAST_MODEL } from "../client.js"; + +export async function factCheck( + topic: string, + findings: string[], +): Promise { + const response = await client.messages.create({ + model: FAST_MODEL, + max_tokens: 4000, + system: + "You are a fact-checker subagent. Given a topic and research findings, identify any contradictions, unsupported claims, or gaps. Return a JSON object with: { verified: string[], disputed: string[], gaps: string[] }. Be concise.", + messages: [ + { + role: "user", + content: `Topic: ${topic}\n\nFindings to verify:\n${findings + .map((f, i) => `[${i + 1}] ${f}`) + .join("\n\n")}\n\nAnalyze these findings now.`, + }, + ], + }); + + return response.content + .filter((b) => b.type === "text") + .map((b) => (b.type === "text" ? b.text : "")) + .join(""); +} diff --git a/src/coordinator.ts b/src/coordinator.ts index 915366d..7fff7f0 100644 --- a/src/coordinator.ts +++ b/src/coordinator.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { client, MODEL } from "./client.js"; import { research } from "./agents/researcher.js"; import { writeReport } from "./agents/writer.js"; +import { factCheck } from "./agents/fact-checker.js"; // Hand-off pattern: the coordinator exposes subagents as tools. // Each tool call delegates to a specialized agent and returns its output. @@ -23,6 +24,21 @@ const delegateResearch = betaZodTool({ }, }); +const delegateFactCheck = betaZodTool({ + name: "delegate_fact_check", + description: + "Delegate fact-checking to the fact-checker subagent. Call after research and before writing to verify findings. Returns verified/disputed/gaps analysis.", + inputSchema: z.object({ + topic: z.string(), + }), + run: async ({ topic }) => { + if (findings.length === 0) { + return "Error: call delegate_research at least once before fact-checking."; + } + return await factCheck(topic, findings); + }, +}); + const delegateWriting = betaZodTool({ name: "delegate_writing", description: @@ -44,12 +60,13 @@ export async function runCoordinator(userRequest: string): Promise { max_tokens: 32000, thinking: { type: "adaptive" }, output_config: { effort: "high" }, - system: `You are a coordinator agent orchestrating two subagents: + system: `You are a coordinator agent orchestrating three subagents: - delegate_research(topic): fan-out research queries; call multiple times in parallel for independent subtopics. -- delegate_writing(topic): once you have enough findings, produce the final report. +- delegate_fact_check(topic): after research, verify findings for contradictions, unsupported claims, and gaps. +- delegate_writing(topic): once findings are verified, produce the final report. -Plan first, then delegate. Return the final report verbatim.`, - tools: [delegateResearch, delegateWriting], +Workflow: research → fact-check → write. Return the final report verbatim.`, + tools: [delegateResearch, delegateFactCheck, delegateWriting], messages: [{ role: "user", content: userRequest }], });