Adopt a pre-built agent, the complete journey, once
Six packages ship ready-made agents: a researcher, marketer, designer, writer, the agent-builder, and the kit they share. Each is a versioned persona plus a curated skills.sh manifest pinned to reviewed snapshots, and a fail-loud contract: you wire the tools, the factory throws on what's missing.
What you're adopting
A factory (createResearcher, createDesigner, and so on) returns a plain AgentDef, plus a manifest naming its curated external skills and required tools. Nothing runs at import time; nothing phones home. The curated skills live in YOUR versioned store after a governed import, fenced as third-party reference, pinned to the snapshot that was reviewed.
Install + storage
pnpm add @nightowlsdev/agent-researcher @nightowlsdev/agent-kit @nightowlsdev/skills
# every agent package peers on @nightowlsdev/core + agent-kit + skills, one engine, no duplicatesimport { createSupabaseStorage } from "@nightowlsdev/storage-supabase";
import { skillsShProvider } from "@nightowlsdev/skills";
// The versioned skill store (migration 0020) + the versioned agent repo ship in the adapter.
export const storage = createSupabaseStorage({ url, secretKey, dbUrl });
// One provider registry, keyed by provider id, skills.sh here; github/http/custom also ship.
export const skillProviders = { "skills.sh": skillsShProvider() };Import the curated skills (once per tenant)
import { importCuratedSkills, verifyGrants } from "@nightowlsdev/agent-kit";
import { manifest } from "@nightowlsdev/agent-researcher";
// Run ONCE per tenant (a seed script or admin action), and again after package upgrades.
// Strict pins by default: a drifted upstream is SKIPPED for review; a renamed one is rejected.
const report = await importCuratedSkills({
sets: manifest.curatedSkills,
providers: skillProviders,
storage, // { skills, skillsWritable }
tenantId,
actor: { type: "service", serviceId: "seed", tenantId },
});
// The silent-miss check: a granted stored name with nothing behind it injects nothing.
const { missing } = await verifyGrants(
storage.skills,
tenantId,
manifest.curatedSkills.flatMap((s) => s.skills.map((r) => r.name)),
);
if (missing.length) console.warn("import the curated skills before first use:", missing);Wire the swarm
Three things are load-bearing: dynamicSkills (stored skills inject at runtime), a models.tier config (factories default to "tier:"), and the approval allowlist for strict hosts.
import { defineSwarm, DEFAULT_READ_ONLY_TOOLS } from "@nightowlsdev/core";
import { materializeSkillStore } from "@nightowlsdev/skills";
import { PREBUILT_READONLY_TOOL_NAMES } from "@nightowlsdev/agent-kit";
import { createResearcher } from "@nightowlsdev/agent-researcher";
// The factory FAILS LOUD on missing required tools, there is no framework web search;
// you inject one (an MCP server, a custom defineTool, or a connector action).
const researcher = createResearcher({ tools: { webSearch: myWebSearchTool } });
const swarm = defineSwarm({
storage,
agents: [researcher],
// REQUIRED for stored-skill grants: without this seam, an imported skill never reaches a prompt.
dynamicSkills: materializeSkillStore(storage.skills),
// Factories default to modelId "tier:" (never a vendor pin), so a tier config is REQUIRED
// (without one, "tier:" fails the model allow-list, loudly, at run time).
models: {
allow: ["openai/gpt-5.5-mini", "openai/gpt-5.5"],
tier: { tiers: { swift: "openai/gpt-5.5-mini", genius: "openai/gpt-5.5" }, default: "swift" },
},
modelFactory,
cost: { maxSteps: 30, maxCostUsd: 0.5 },
// Strict approval hosts: the prebuilt READ-ONLY tools ride the allowlist so a designer reading
// its skill library doesn't suspend on every read. Egress/mutating tools still gate.
toolApproval: {
mode: "all-side-effecting",
readOnly: [...DEFAULT_READ_ONLY_TOOLS, ...PREBUILT_READONLY_TOOL_NAMES],
},
});Seed and upgrade on a persisted store
import { ensurePrebuiltAgent, publishPrebuiltAgent } from "@nightowlsdev/agent-kit";
// defineSwarm's seed hook is in-memory-only, a persisted store needs an explicit lifecycle.
// Boot: seed-if-absent (idempotent, NO version churn across restarts).
await ensurePrebuiltAgent({
agents: storage.agents,
agentsWritable: storage.agentsWritable,
def: researcher,
tenantId,
actor: { type: "service", serviceId: "boot", tenantId },
});
// After a package upgrade: publish DELIBERATELY, a new immutable version; rollback stays available.
await publishPrebuiltAgent({ agentsWritable: storage.agentsWritable, def: upgradedResearcher, tenantId, actor });Approvals and the agent-builder
import { createBuilder } from "@nightowlsdev/agent-builder";
// The agent-builder's mutating tools (import_skill / publish_agent / publish_bundle) SUSPEND for
// human approval on every call, an enforce-level rule a permissive host hook cannot downgrade.
// PREREQUISITE: your host handles the suspend/resume round-trip (every @nightowlsdev/react host
// already does, the approval renders as a question card; headless hosts resume via the engine API).
const builder = createBuilder({
providers: skillProviders,
storage: {
skills: storage.skills, skillsWritable: storage.skillsWritable,
agents: storage.agents, agentsWritable: storage.agentsWritable,
bundles: storage.bundles, bundlesWritable: storage.bundlesWritable, // needed for publish_bundle
},
actor: { type: "service", serviceId: "builder-host", tenantId }, // YOUR service actor
policy: { allowAuthors: ["anthropics", "vercel-labs", "coreyhaines31"] }, // import allowlists
policyGuard: (op) => assertOrgAllows(op), // fail-closed; the agents-can't-mutate storage bar stays intact
});Where to go next
The packages
Per-package docs: agent-kit, agent-builder, agent-researcher, agent-marketer, agent-designer, agent-writer.
Skills from anywhere
The governed import/refresh seam under the manifests: @nightowlsdev/skills.
Source
Night Owls is MIT licensed on GitHub.