@nightowlsdev/agent-kit
Pre-built agentsThe shared foundation for the pre-built agent family: curated skills.sh manifests + governed batch import, fail-loud tool contracts, progressive-disclosure skill-library tools, and persisted-store lifecycle helpers.
What it does
@nightowlsdev/agent-kit is what the pre-built agent packages (builder/researcher/marketer/designer/writer) are made of, and what you use to build your own. A `CuratedSkillSet` manifest pins external skills.sh refs to reviewed snapshot hashes; `importCuratedSkills` batch-imports them through the governed skills seam (default `pinMode: 'strict'`: a drifted upstream is skipped for review, a renamed upstream is rejected in every mode, failures are isolated per ref) and `verifyGrants` makes the silent-miss mode legible (a granted stored name with nothing behind it injects nothing). `skillLibraryTools` solves the 18-skills-without-prompt-explosion problem: an agent holds a big curated library at ~zero prompt cost and reads one skill on demand as a FENCED tool result, scoped to an explicit name list (never whole-tenant). `assertToolRequirements` makes capability contracts fail loud at factory time. `ensurePrebuiltAgent` (boot-safe seed-if-absent) and `publishPrebuiltAgent` (the deliberate upgrade) speak core repo contracts only, the 'evolve once, upgrade downstream' lifecycle on a persisted store.
Install
pnpm add @nightowlsdev/agent-kitKey exports
- importCuratedSkills / verifyGrants (strict pins, rename rejection, per-ref isolation)
- skillLibraryTools (skill_library_list / skill_library_read, fenced, scoped)
- assertToolRequirements (fail-loud capability contracts)
- ensurePrebuiltAgent / publishPrebuiltAgent (persisted-store lifecycle)
- buildAgentSpec / PrebuiltAgentManifest / PrebuiltAgentOpts
- ALL_PREBUILT_TOOL_NAMES / PREBUILT_READONLY_TOOL_NAMES
Usage
import { importCuratedSkills, verifyGrants, ensurePrebuiltAgent } from "@nightowlsdev/agent-kit";
import { skillsShProvider } from "@nightowlsdev/skills";
import { createResearcher, manifest } from "@nightowlsdev/agent-researcher";
// 1. Import an agent package's curated manifest (strict pins: drift is skipped, renames rejected).
await importCuratedSkills({
sets: manifest.curatedSkills,
providers: { "skills.sh": skillsShProvider() },
storage, // { skills, skillsWritable }, createSupabaseStorage has both
tenantId,
actor: { type: "service", serviceId: "seed", tenantId },
});
// 2. Verify the grants resolve (a granted stored name with nothing behind it injects nothing).
const { missing } = await verifyGrants(storage.skills, tenantId, ["deep-research", "research-synthesis"]);
// 3. Seed the factory's agent on a persisted store (publishPrebuiltAgent is the deliberate upgrade).
const researcher = createResearcher({ tools: { webSearch: myWebSearchTool } });
await ensurePrebuiltAgent({
agents: storage.agents, agentsWritable: storage.agentsWritable,
def: researcher, tenantId, actor: { type: "service", serviceId: "boot", tenantId },
});
// The full journey (tier config, dynamicSkills, approvals): /docs/adopt-prebuilt-agentsWhat it provides
agent-kit is the shared toolkit under every pre-built agent package (builder/researcher/marketer/designer/writer/diagnostics) — and what you use to build your own. It gives you curated skills.sh manifests plus a governed batch import (importCuratedSkills / verifyGrants), fail-loud capability contracts (assertToolRequirements), progressive-disclosure skill-library tools (skillLibraryTools), persisted-store lifecycle helpers (ensurePrebuiltAgent / publishPrebuiltAgent), and the one manifest+persona→AgentSpec shape (buildAgentSpec / PrebuiltAgentManifest). It is not itself an agent; it is the machinery a pre-built agent is assembled from.
When to use it
- You are adopting any pre-built agent — you go through agent-kit to import its curated skills once per tenant and (on a persisted store) seed it.
- You are authoring your own pre-built agent package and want the same manifest shape, curated-import governance, and library tools the shipped ones use.
- You want to hold a large curated skill library at ~zero prompt cost and read one skill on demand, fenced and name-scoped.
- You want capability contracts that fail at factory time with a named missing tool rather than a silently half-configured agent at run time.
When not to
- You are defining agents inline with defineAgent and need neither external-skill import, a persisted catalog, nor the library-read pattern — core alone is enough.
- You want to author skill text yourself rather than import pinned external refs — reach for @nightowlsdev/skills directly.
- You just want a run to execute — agent-kit lands rows in the store; it does not inject them at runtime by itself.
Alternatives
- @nightowlsdev/skills directlyYou already have your own skill store and grant flow and just want the SkillProvider import primitives, without the curated-manifest, verify, and lifecycle helpers layered on top.
- defineAgent in @nightowlsdev/coreA single hand-authored agent with inline skills, no external-skill import, no persisted lifecycle, and no on-demand library — the kit's helpers would be unused.
Strengths
- Strict pins by default: importCuratedSkills runs pinMode 'strict' — a drifted upstream is skipped for review, a rename is rejected in every mode, and per-ref failures are isolated so one bad ref never sinks the batch.
- verifyGrants makes the silent-miss legible — a granted stored name with nothing behind it injects nothing, and this is the check that surfaces it before a run quietly under-performs.
- skillLibraryTools solves prompt explosion: a big library held at ~zero prompt cost, one skill read on demand as a FENCED tool result, scoped to an explicit name list (never whole-tenant).
- Fail-loud contracts: assertToolRequirements lists every missing tool with its purpose at factory time.
- Boot-safe lifecycle: ensurePrebuiltAgent seeds-if-absent without version churn; publishPrebuiltAgent is the deliberate immutable upgrade, speaking core repo contracts only.
Limits & trade-offs
- It does not inject skills at runtime — importCuratedSkills only lands rows; you still wire dynamicSkills: materializeSkillStore(...) on defineSwarm or the grants are inert.
- Library scoping filters by NAME only in v1 — there is no tag-based scoping, so you pass the explicit names you want in scope.
- The curated import is a per-tenant seed step, not a runtime call — it needs a live SkillProvider (e.g. skillsShProvider()) and a writable skill store.
- No turnkey eval suites yet (blocked on FR-018).
How it works
A CuratedSkillSet manifest pins external skills.sh refs to reviewed snapshot hashes — the packages ship refs plus pins, never third-party text. importCuratedSkills batch-imports them through the governed @nightowlsdev/skills seam under an actor, isolating per-ref failures and reporting name-mismatch / unpinned statuses; verifyGrants then confirms the granted names actually resolve to stored skills. buildAgentSpec merges a PrebuiltPersona with your PrebuiltAgentOpts into the AgentSpec each factory passes to defineAgent (modelId defaults to 'tier:'). On a persisted store, ensurePrebuiltAgent seeds the definition boot-safely and publishPrebuiltAgent cuts a new immutable version, both over core's repo contracts.
Examples
Import a package's curated skills, then verify the grants
Strict pins by default; verifyGrants surfaces a granted name with nothing behind it.
import { importCuratedSkills, verifyGrants } from "@nightowlsdev/agent-kit";
import { skillsShProvider } from "@nightowlsdev/skills";
import { manifest } from "@nightowlsdev/agent-researcher";
await importCuratedSkills({
sets: manifest.curatedSkills,
providers: { "skills.sh": skillsShProvider() },
storage, // { skills, skillsWritable }
tenantId,
actor: { type: "service", serviceId: "seed", tenantId },
});
const { missing } = await verifyGrants(storage.skills, tenantId, ["deep-research"]);
if (missing.length) throw new Error("granted names with nothing behind them: " + missing.join(", "));Hold a big library at ~zero prompt cost, read on demand
skill_library_list / skill_library_read, scoped to an explicit name list — never whole-tenant.
import { skillLibraryTools } from "@nightowlsdev/agent-kit";
import { defineAgent } from "@nightowlsdev/core";
const libraryTools = skillLibraryTools(storage.skills, {
names: ["impeccable", "ui-ux-pro-max", "design-taste-frontend"],
});
const agent = defineAgent({
slug: "designer",
role: "specialist",
personality: "Survey the library, then read only the skills the task needs.",
skills: libraryTools,
modelId: "tier:",
});Seed a factory's agent on a persisted store, boot-safe
ensurePrebuiltAgent is seed-if-absent (no version churn); publishPrebuiltAgent is the deliberate upgrade.
import { ensurePrebuiltAgent } from "@nightowlsdev/agent-kit";
import { createWriter } from "@nightowlsdev/agent-writer";
await ensurePrebuiltAgent({
agents: storage.agents,
agentsWritable: storage.agentsWritable,
def: createWriter(),
tenantId,
actor: { type: "service", serviceId: "boot", tenantId },
});Doing the parts it doesn't support
- Injecting the imported skills into a runimportCuratedSkills only lands rows in the store. Wire dynamicSkills: materializeSkillStore(storage.skills) (from @nightowlsdev/skills) on defineSwarm so a granted stored name resolves to instructions at runtime — without it the grant is inert.
- Tag-based library scopingTag-based scoping is not in v1 — SkillLibraryOpts takes only a names list. Scope skillLibraryTools by an explicit names list; build that list from the manifest's refs if you want a curated subset.
- Upgrading a seeded agent deliberatelyensurePrebuiltAgent is seed-if-absent. To cut a new immutable version call publishPrebuiltAgent, which leaves rollback available.
Related
- skills — The governed skill store + SkillProvider import primitives the curated import runs on; materializeSkillStore injects the grants.
- core — The defineAgent / defineSwarm primitives and repo contracts every kit helper speaks.
- adopt-prebuilt-agents — The full host-wiring journey (storage, tier config, approvals) the four steps expand into.
- capability-bundles — Package a whole crew (agents + grants) for reuse — the bundle layer above the kit.
- approval-modes — Why PREBUILT_READONLY_TOOL_NAMES go on toolApproval.readOnly so library reads don't suspend.