@netscript/ai/skills
Load validated Agent Skills with progressive disclosure and optional semantic matching. A
skill is a SKILL.md document — a small frontmatter block plus Markdown instructions — that an
agent can discover cheaply by tags (or embeddings) and only fully disclose when it is actually
selected. This subpath is a library surface only: it has no CLI verbs and takes no
@netscript/* runtime dependency. For the full package index return to the
@netscript/ai reference or the reference overview.
Installed NetScript skills
netscript agent init installs five first-party skills and a symptom playbook in the canonical
.agents/skills/ tree and adds a marked NetScript section to AGENTS.md for every host. The
Claude Code host integration is the fallback default when no host is selected or detected from
project markers or the invoking environment. It writes .mcp.json, the single discovery bridge
.claude/skills/repo-skills/SKILL.md, and a CLAUDE.md import of @AGENTS.md (merged
idempotently into existing content); the VS Code editor writes .vscode/mcp.json; Zed writes native
context_servers into .zed/settings.json; OpenCode gets opencode.json and discovers
.agents/skills/ natively. The AGENTS.md section names each skill by its registered name
("call the netscript-build skill"). Hosts that register those names use their skill tool; on Claude
Code, call repo-skills with the requested skill name. Its bridge instructions permit reading the
canonical .agents/skills/<name>/SKILL.md and bundled references. Select an editor with
netscript agent init --editor none|zed|vscode, including on a cloned existing project. They share
one vocabulary with the
netscript CLI and the NetScript MCP tools,
so an agent routes to a workflow and then reaches for the matching CLI verb or MCP tool.
| Skill | Role |
|---|---|
netscript |
Router. Dispatches a task to the build or operate skill (or to the Aspire skill for orchestration); it is not useful on its own. |
netscript-build |
Scaffold and build with real netscript CLI verbs: init, the contract-first flow, the database lifecycle, adding and syncing plugins, adding services and UI, and generating registries. |
netscript-operate |
Monitor, debug, and analyze a running app through the bounded MCP tools: health, recent runs, one execution end to end, recent errors, job results, service and database performance, and doc search. |
aspire |
Diagnose orchestration and runtime state from symptoms with resource descriptions, console logs, structured logs, spans, traces, and targeted resource control. |
deno |
Diagnose runtime and toolchain problems with focused checks, module-resolution inspection, permissions, test sanitizers, cache evidence, and API docs. |
These are the skills named in the bundle manifest and installed as an atomic set. The companion
.agents/skills/help.md playbook starts from symptoms—including broken plugin wiring—and routes to
the first diagnostic command; Claude Code reaches the same canonical playbook through its bridge;
re-running netscript agent init is idempotent. See
Agent tooling for the CLI × skills × MCP combo.
The upstream Aspire workflow skills (aspire-init, aspire-orchestration, aspire-monitoring,
aspire-deployment) can be installed beside NetScript's aspire diagnostic skill by explicit
name; they never overwrite aspire/SKILL.md. Agent initialization uses only Aspire's standard
.agents/skills/ location. It preserves existing legacy mirrors and custom Claude skills, and warns
when known legacy skill directories remain because they are no longer refreshed. Legacy mirror
cleanup and divergent-file reporting remain follow-up work.
Agents connect to the Aspire MCP server with aspire agent mcp, or with the dashboard-only form
aspire agent mcp --dashboard-url <url> (plus an optional --api-key). Aspire 13.5.3 exposes a
14-tool MCP baseline: list_resources, list_console_logs, list_structured_logs,
list_traces, list_trace_structured_logs, execute_resource_command, list_apphosts,
select_apphost, list_integrations, list_docs, search_docs, get_doc, doctor, and
refresh_tools. (get_integration_docs is documented upstream on aspire.dev but was not observed
at 13.5.3.)
import {
createInMemorySkillContentSource,
createSkillLoader,
} from "jsr:@netscript/ai@0.0.8/skills";
The subpath is published as the ./skills export of @netscript/ai (version
0.0.8). Nothing here reads the environment or performs IO on its own: content
enters through an injected SkillContentSource, and semantic ranking is only performed when you
supply a SkillEmbeddingProvider.
The skill document
parseSkillMarkdown is the single validator for the blessed SKILL.md shape. The frontmatter
must be a closed --- block whose only allowed keys are the four required fields; the body is the
Markdown that follows and must be non-empty.
| Frontmatter field | Rule |
|---|---|
id |
Non-empty; matches ^[a-z0-9][a-z0-9._-]*$ (case-insensitive). |
name |
Non-empty human-readable label. |
tags |
A YAML list with at least one tag; drives tag matching. |
description |
Non-empty short summary used for discovery and semantic matching. |
Unknown keys, duplicate keys, a missing required key, an empty body, or a malformed frontmatter
line each throw a TypeError. A minimal document:
---
id: review
name: Code review
tags: [review, quality]
description: Reviews a change for correctness.
---
Inspect the diff and report actionable findings.
Progressive disclosure
The design keeps cheap metadata enumeration separate from full-content reads. A
SkillContentSource lists validated SkillSummary rows (id, name, tags, description — no body)
and loads the raw SKILL.md for one id on demand. A SkillLoaderPort composes over a source: it
matches summaries first, and only load(id) parses and validates the full SkillDocument
(summary + body). This is the same two-phase shape the runtime's skills capability port speaks.
Surface
Composition
| Symbol | Kind | Signature | Description |
|---|---|---|---|
createSkillLoader |
function | createSkillLoader(source: SkillContentSource, options?: SkillLoaderOptions): SkillLoaderPort |
Compose a loader from an injected content source and optional embeddings. |
createInMemorySkillContentSource |
function | createInMemorySkillContentSource(entries: readonly SkillSourceEntry[]): SkillContentSource |
Build an effect-free source from caller-supplied SKILL.md strings (rejects duplicate or mismatched ids). |
parseSkillMarkdown |
function | parseSkillMarkdown(markdown: string): SkillDocument |
Parse and validate one SKILL.md into a full document, or throw TypeError. |
Matching primitives
The loader delegates to two pure ranking functions, also exported directly for callers that hold their own summaries. Both return results sorted by descending score.
| Symbol | Kind | Signature | Description |
|---|---|---|---|
matchByTags |
function | matchByTags(skills: readonly SkillSummary[], triggers: readonly string[]): readonly SkillMatch[] |
Case-insensitive exact (score 1) and substring (score 0.5) tag triggers. |
matchBySemantic |
function | matchBySemantic(skills: readonly SkillSummary[], query: string, embeddings: SkillEmbeddingProvider, model?: string): Promise<readonly SkillMatch[]> |
Cosine-similarity ranking over name/description/tags text via an injected embedding provider. |
SKILL_MATCH_MODES |
variable | readonly ["tag", "semantic"] |
The trigger modes a SkillMatch can report. |
Types
| Symbol | Kind | Description |
|---|---|---|
SkillLoaderPort |
interface | Discovery + two-phase loading: list(), load(id), matchByTag(tags), matchByQuery(query, options?). |
SkillContentSource |
interface | Source boundary: list() of summaries and load(id) of raw content. |
SkillSummary |
interface | Cheap metadata (id, name, tags, description) with no body. |
SkillDocument |
interface | SkillSummary extended with the non-empty body. |
SkillMatch |
interface | A ranked result: skill (summary), score (0–1), and contributing modes. |
SkillMatchMode |
type alias | "tag" | "semantic". |
SkillQueryOptions |
interface | matchByQuery options: tags, semantic, minScore, limit. |
SkillLoaderOptions |
interface | Loader composition options: embeddings, embeddingModel. |
SkillEmbeddingProvider |
interface | Minimal embed(input, options?) structural view used for semantic matching. |
SkillSourceEntry |
interface | Input to the in-memory source: { id, markdown }. |
Default runtime port
The runtime's skills capability is a SkillLoaderPort. When AiRuntimeConfig.skills is omitted,
createAiRuntime injects a no-op loader that lists and matches nothing — so agent code can call
the port unconditionally before any skills are wired.
AiRuntimeConfig field |
Port | Default when omitted |
|---|---|---|
skills |
SkillLoaderPort |
No-op returning no skills (createNoopSkillLoader from @netscript/ai/ports). |
Provide a real loader by wiring one built here into the runtime config; see the runtime capability ports table on the package page.
Examples
Build a loader and match by tag
import {
createInMemorySkillContentSource,
createSkillLoader,
} from "@netscript/ai/skills";
const source = createInMemorySkillContentSource([
{
id: "review",
markdown: `---
id: review
name: Code review
tags: [review, quality]
description: Reviews a change for correctness.
---
Inspect the diff and report actionable findings.
`,
},
]);
const loader = createSkillLoader(source);
const matches = await loader.matchByTag(["review"]);
if (matches.length > 0) {
const skill = await loader.load(matches[0].skill.id); // full body disclosed here
console.log(skill?.body);
}
Add semantic ranking
Pass a SkillEmbeddingProvider at composition time and set semantic: true on the query. The
@netscript/ai/openai-embeddings provider satisfies the structural embed shape.
import "@netscript/ai/openai-embeddings"; // self-registers the provider
import { getEmbeddingProvider } from "@netscript/ai";
import { createSkillLoader } from "@netscript/ai/skills";
const embeddings = getEmbeddingProvider("openai-embeddings", {
apiKey: Deno.env.get("OPENAI_API_KEY"),
});
const loader = createSkillLoader(source, { embeddings });
const ranked = await loader.matchByQuery("find bugs in my patch", {
semantic: true,
limit: 5,
});
Rank summaries you already hold
matchByTags and matchBySemantic are pure and usable without a loader:
import { matchByTags } from "@netscript/ai/skills";
const summaries = await source.list();
const results = matchByTags(summaries, ["quality"]);
// results: [{ skill, score, modes: ["tag"] }, ...] sorted by descending score
Back to the @netscript/ai reference or the
reference overview.