Skip to content
Night Owls.dev
Jump to a page

@nightowlsdev/agent-researcher

Pre-built agents

Deep 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-researcher

Key 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

agent-researcher.ts
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