@nightowlsdev/agent-researcher
Pre-built agentsDeep research with a fail-loud tool contract: plans sub-questions with stop criteria, searches broad-then-narrow, triages sources primary-first, gap-checks, and delivers a cited synthesis.
What it does
@nightowlsdev/agent-researcher productizes the deep-research playbook: plan with explicit sub-questions and stop criteria → start broad then narrow → triage sources (primary first, dated; two independent sources for load-bearing claims) → compress findings at boundaries → gap-check → a SEPARATE citation pass where an unattributable claim is rewritten as an open question, never left as fact. There is no framework web search, you inject the tools (an MCP server, a custom defineTool, a connector action) and the factory THROWS at build time when the required arm is missing. `corpusOnly` is the explicit degraded-but-grounded mode: it requires a knowledgeSearch tool (e.g. searchKnowledgeTool from @nightowlsdev/knowledge), excludes every live-web tool even if supplied, and appends a persona addendum naming the corpus as the retrieval boundary. Curated skills.sh refs (pinned): a vendor-free core granted by default plus a needs-vendor-key optional set (firecrawl/tavily/parallel).
Install
pnpm add @nightowlsdev/agent-researcherKey exports
- createResearcher ({ tools: { webSearch, fetchUrl?, knowledgeSearch? }, corpusOnly? })
- manifest / RESEARCHER_CURATED_SKILLS (vendor-free core + needs-vendor-key set, pinned)
- RESEARCHER_PERSONA / CORPUS_ONLY_PERSONA_ADDENDUM
Usage
import { createResearcher, manifest } from "@nightowlsdev/agent-researcher";
import { importCuratedSkills } from "@nightowlsdev/agent-kit";
import { materializeSkillStore, skillsShProvider } from "@nightowlsdev/skills";
import { defineSwarm } from "@nightowlsdev/core";
// The host wires the tools, the factory FAILS LOUD when the required arm is missing.
const researcher = createResearcher({
tools: { webSearch: myWebSearchTool, fetchUrl: myFetchTool }, // MCP server / defineTool / connector action
// corpusOnly: true + knowledgeSearch: searchKnowledgeTool(store), the no-live-web mode
});
// Curated skills import once per tenant; dynamicSkills injects them (fenced) at runtime.
await importCuratedSkills({ sets: manifest.curatedSkills, providers: { "skills.sh": skillsShProvider() }, storage, tenantId, actor });
const swarm = defineSwarm({
storage,
agents: [researcher],
dynamicSkills: materializeSkillStore(storage.skills), // REQUIRED for stored-skill grants
models: {
allow: ["openai/gpt-5.5-mini", "openai/gpt-5.5"],
tier: { tiers: { swift: "openai/gpt-5.5-mini" }, default: "swift" }, // "tier:" needs a tier config
},
modelFactory,
cost: { maxSteps: 30, maxCostUsd: 0.5 },
});
// The full journey: /docs/adopt-prebuilt-agents