Node SDK#
Install packages with the CLI, load tools, and inspect installed agents, skills, knowledge packages, memory blueprints, instruction profiles, and loops from Node.
Goal#
Install the Node SDK and use:
load()to call a published toolloadAgent()to inspect an installed agent package and its resolved packagesloadSkill()to inspect an installed skill package and its resolved toolsloadKnowledge()to inspect an installed knowledge package and its mode-specific metadataloadMemory()to inspect an installed memory blueprint package and its generated contractsloadProfile()to inspect an installed instruction profile package and its authored metadataloadLoop()to inspect an installed loop package and its authored orchestration metadata
1) Install a tool#
From your project with an agent manifest:
# declare and install a specific tool version
agentpm install @zack/summarize@0.1.3
# or: edit agent.json → tools[] then run agentpm installThis resolves and prepares the tool under: .agentpm/tools/<namespace>/<name>/<version>
You can also install a published agent package directly:
agentpm install @zack/support-agent@0.1.0That writes:
- the installed agent under
.agentpm/agents/<namespace>/<name>/<version> - its resolved knowledge packages under
.agentpm/knowledge/<namespace>/<name>/<version> - its resolved memory packages under
.agentpm/memory/<namespace>/<name>/<version> - its resolved profile packages under
.agentpm/profiles/<namespace>/<name>/<version> - its resolved loop package under
.agentpm/loops/<namespace>/<name>/<version> - its resolved skills under
.agentpm/skills/<namespace>/<name>/<version> - its resolved tools under
.agentpm/tools/<namespace>/<name>/<version>
You can also install a published skill package directly:
agentpm install @zack/incident-commander@0.1.0That writes:
- the installed skill under
.agentpm/skills/<namespace>/<name>/<version> - its resolved tools under
.agentpm/tools/<namespace>/<name>/<version>
You can also install a published knowledge package directly:
agentpm install @zack/python-docs@0.1.0That writes:
- the installed knowledge package under
.agentpm/knowledge/<namespace>/<name>/<version>
You can also install a published memory package directly:
agentpm install @zack/profile-memory@0.1.0That writes:
- the installed memory package under
.agentpm/memory/<namespace>/<name>/<version>
You can also install a published profile package directly:
agentpm install @zack/support-style@0.1.0That writes:
- the installed profile package under
.agentpm/profiles/<namespace>/<name>/<version>
You can also install a published loop package directly:
agentpm install @zack/incident-response-loop@1.0.0That writes:
- the installed loop package under
.agentpm/loops/<namespace>/<name>/<version>
2) Install the Node SDK#
pnpm add @agentpm/sdk
# or: npm i @agentpm/sdk3) The load() function (core API)#
load() resolves the tool and returns a callable bound to a managed subprocess.
Basic usage#
import { load } from '@agentpm/sdk';
// simplest form: just the spec
const summarize = await load('@zack/summarize@0.1.3');
const result = await summarize({
text: 'Cats are elegant, enigmatic creatures.',
});
console.log(result);Type signature (simplified)#
// without metadata
function load(spec: string, opts?: LoadOptions): Promise<(input: JsonValue) => Promise<JsonValue>>;
// with metadata
function load(spec: string, opts: LoadOptions & { withMeta: true }): Promise<{
func: (input: JsonValue) => Promise<JsonValue>;
meta: ToolMeta;
}>;Options#
type LoadOptions = {
withMeta?: boolean; // include manifest metadata in the return value
timeoutMs?: number; // hard cap per-invoke (default: 120_000 ms)
toolDirOverride?: string; // use a custom tool root (tests/local layouts)
env?: Record<string, string>; // merged into the subprocess environment
};Example with environment and timeout#
const capitalize = await load('@zack/capitalize@0.1.11', {
env: {
OPENAI_API_KEY: process.env.OPENAI_API_KEY!, // passed to the subprocess
},
timeoutMs: 30_000,
});
const out = await capitalize({
text:
'Cats are some of the most amazing creatures—loving, curious, and a little wild at heart.',
doKeywords: true,
doSentiment: true,
doSummary: true,
maxSummaryChars: 200,
});4) Loading with metadata#
Ask load() for manifest data you can hand to your agent runtime (for tool descriptions, IO schemas, etc.).
const { func: cap, meta } = await load('@zack/capitalize@0.1.11', { withMeta: true });
/*
meta: {
name: string;
version: string;
description?: string;
inputs?: JsonValue; // JSON Schema (Draft 2020-12)
outputs?: JsonValue; // JSON Schema (Draft 2020-12)
runtime?: { type: 'node'|'python'; version?: string };
}
*/
const out2 = await cap({ text: 'hi' });Using meta to configure an agent tool (example)#
import { load } from '@agentpm/sdk';
// Load two tools and convert to a generic agent tool spec
const tools = await Promise.all([
load('@zack/capitalize@0.1.11', { withMeta: true }),
load('@zack/summarize@0.1.3', { withMeta: true }),
]);
const agentTools = tools.map(({ func, meta }) => ({
name: meta.name,
description: meta.description ?? 'AgentPM tool',
// helpful: concatenate description + IO shapes for better tool selection
longDescription: `${meta.description ?? ''}\n\nInputs: ${JSON.stringify(meta.inputs)}\nOutputs: ${JSON.stringify(meta.outputs)}`,
// the callable your agent will invoke
call: func,
}));
// agentTools can be registered with your agent runtime’s tool loader5) Loading an installed agent package#
Use loadAgent() when you want metadata for a registry-installed agent package plus the concrete package refs it resolved to at install time.
import { load, loadAgent, loadSkill } from '@agentpm/sdk';
const agent = await loadAgent('@zack/support-agent@0.1.0');
const firstSkill = agent.resolvedSkills[0];
const skill = await loadSkill(`${firstSkill.name}@${firstSkill.version}`);
const firstTool = skill.resolvedTools[0];
const tool = await load(`${firstTool.name}@${firstTool.version}`);What loadAgent() returns:
- the installed agent manifest
- the installed agent root path
resolvedKnowledge, which are the exact knowledge package refs represented inagent.lockresolvedMemory, which are the exact memory package refs represented inagent.lockresolvedProfiles, which are the exact profile package refs represented inagent.lockresolvedLoop, which is the exact singular loop ref represented inagent.lock- reserved refs (
knowledge,memory,profiles) from the lockfile metadata path resolvedTools, which are the exact tool package refs represented inagent.lockresolvedSkills, which are the exact skill package refs represented inagent.lock
Compatibility note:
resolvedKnowledgeis populated from the modern first-classroot.knowledgeentries inagent.lock.reserved.knowledgeis legacy pass-through metadata from older lockfile shapes. For current installs, treatresolvedKnowledgeas authoritative and expectreserved.knowledgeto usually be empty.- If your workspace still has an older lockfile shape where Knowledge refs only exist under
reserved.knowledge, rerunagentpm installto rewrite the lockfile. resolvedLoopis populated from the modern first-classroot.loopentry inagent.lock.resolvedProfilesis populated from the modern first-classroot.profilesentries inagent.lock.reserved.profilesis legacy pass-through metadata from older lockfile shapes. For current installs, treatresolvedProfilesas authoritative and expectreserved.profilesto usually be empty.manifest.loopandmanifest.bindingspreserve the authored declarative metadata from the installedagent.json.
What it does not do:
- it does not execute the agent package
- it does not orchestrate the tools for you
- it does not load the local project
./agent.json
6) Loading an installed skill package#
Use loadSkill() when you want metadata for a registry-installed skill package plus the concrete tool refs it resolved to at install time.
import { loadSkill } from '@agentpm/sdk';
const skill = await loadSkill('@zack/incident-commander@0.1.0');
console.log(skill.entrypointPath);
console.log(skill.entrypointContent);
console.log(skill.references);
console.log(skill.scripts);
console.log(skill.resolvedTools);loadSkill() returns an inspectable Skill object. Skills are not runnable SDK objects by themselves.
7) Loading an installed knowledge package#
Use loadKnowledge() when you want metadata for a registry-installed knowledge package plus the canonical package-relative paths it declares.
import { loadKnowledge } from '@agentpm/sdk';
const knowledge = await loadKnowledge('@zack/python-docs@0.1.0');
console.log(knowledge.knowledge.mode);
console.log(knowledge.documentPaths);
console.log(knowledge.chunksPath);
console.log(knowledge.sourcesPath);
console.log(knowledge.vectorsPath);
console.log(knowledge.indexPaths);loadKnowledge() returns an inspectable Knowledge object. Knowledge packages are not runnable SDK objects by themselves.
8) Loading an installed memory package#
Use loadMemory() when you want authored Memory Blueprint metadata, parsed build metadata, the persisted contract index, and on-demand access to resolved contracts.
import { loadMemory, loadMemoryContract } from '@agentpm/sdk';
const memory = await loadMemory('@zack/profile-memory@0.1.0');
const contract = loadMemoryContract(memory, {
space: 'profile',
recordType: 'user_preference',
});
console.log(memory.memory.spaces);
console.log(memory.build);
console.log(memory.contractIndex);
console.log(memory.contracts);
console.log(contract);loadMemory() returns an inspectable Memory object. Memory packages are not runnable SDK objects by themselves and do not provide live record CRUD behavior.
9) Loading an installed profile package#
Use loadProfile() when you want authored Instruction Profile metadata from an installed registry package.
import { loadProfile } from '@agentpm/sdk';
const profile = await loadProfile('@zack/support-style@0.1.0');
console.log(profile.profile.identity.role);
console.log(profile.profile.objectives);
console.log(profile.profile.communication);loadProfile() returns an inspectable Profile object. Profiles are metadata only:
- the installed profile manifest
- the installed package root path
- parsed authored
profilemetadata
It does not compile prompts, interpret README content as instructions, enforce constraints, or execute runtime behavior.
10) Loading an installed loop package#
Use loadLoop() when you want authored Loop metadata from an installed registry package.
import { loadLoop } from '@agentpm/sdk';
const loop = await loadLoop('@zack/incident-response-loop@1.0.0');
console.log(loop.loop.entry_phase);
console.log(loop.loop.phases);
console.log(loop.loop.transitions);
console.log(loop.loop.error_policy);loadLoop() returns an inspectable Loop object containing:
- the installed loop manifest
- the installed package root path
- parsed authored
loopmetadata
It does not execute the graph, choose a model, resolve Agent phase bindings, validate Memory selectors against installed blueprints, start MCP, read consumer_context.file, or provide a harness runtime.
load() stays tool-only#
import { load } from '@agentpm/sdk';
await load('@zack/incident-commander@0.1.0');
// throws: use loadSkill("@zack/incident-commander@0.1.0") instead
await load('@zack/python-docs@0.1.0');
// throws: use loadKnowledge("@zack/python-docs@0.1.0") instead
await load('@zack/profile-memory@0.1.0');
// throws: use loadMemory("@zack/profile-memory@0.1.0") instead
await load('@zack/support-style@0.1.0');
// throws: use loadProfile("@zack/support-style@0.1.0") instead
await load('@zack/incident-response-loop@1.0.0');
// throws: use loadLoop("@zack/incident-response-loop@1.0.0") instead11) Execution contract (how tools must behave)#
For a tool to work with the SDK, it must follow this process protocol:
- STDIN: SDK writes one JSON object (tool inputs) to the subprocess stdin.
- STDOUT: Tool writes exactly one JSON object (tool outputs) to stdout (typically the last line).
- STDERR: Any logs/diagnostics go to stderr.
- Exit code:
0on success; non-zero indicates failure.
Example Node tool main (pattern):
async function readStdin(): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) chunks.push(chunk as Buffer);
return Buffer.concat(chunks).toString('utf8');
}
async function main() {
try {
const raw = await readStdin();
const input = raw.trim() ? JSON.parse(raw) : {};
// ... do work ...
const out = { ok: true };
// IMPORTANT: single JSON object to stdout
process.stdout.write(JSON.stringify(out));
} catch (e: any) {
console.error(e?.stack || String(e)); // stderr
process.exit(1);
}
}
if (import.meta.url === `file://${process.argv[1]}`) main();12) What load() enforces for safety#
- Interpreter allow-list
- Allowed:
node,nodejs,python,python3, or versioned likepython3.11. - Otherwise: throws
Unsupported agent.json.entrypoint.command ....
- Allowed:
- Interpreter on PATH
- Verifies the interpreter can be resolved (checks
PATH, attempts--version).
- Verifies the interpreter can be resolved (checks
- Runtime ↔ entrypoint match (if runtime present)
- Ensures
runtime.typeagrees withentrypoint.command(e.g.,node↔node).
- Ensures
- Timeouts
- Per-call hard cap (
timeoutMs, default 120s).
- Per-call hard cap (
- Environment merging
- Merges manifest
entrypoint.env+load(opts).env+ safe base env.
- Merges manifest
13) How the subprocess is spawned (high level)#
- Working dir (
cwd):entrypoint.cwd(default.) relative to the tool root. - Isolated run dirs: a unique temp
run/folder with dedicatedHOMEandTMPDIR. - Node memory cap: if the interpreter is Node and no cap is present, the SDK injects
--max-old-space-size=256to prevent runaway memory in long processes. - Environment: composed via
buildEnv(entry.env, opts.env, HOME, TMPDIR)to keep the tool isolated yet configurable.
If your tool needs more memory, you can add your own flag in entrypoint.args (e.g., --max-old-space-size=512 for Node).
14) Troubleshooting#
- Interpreter not found
- Ensure
node/pythonis onPATH. The error message prints the PATH searched.
- Ensure
- Timeouts
- Increase
timeoutMsinload()or setentrypoint.timeout_ms(SDK uses the per-call value).
- Increase
- Non-JSON output
- Make sure only one valid JSON object is written to stdout; logs go to stderr.
- Runtime mismatch
- Align
runtime.type(node/python) withentrypoint.command.
- Align
- Local testing / custom layouts
- Use
toolDirOverrideinload()to point to a local unpacked tool directory.
- Use
- Modern package loading requires a current
agent.lockloadAgent()andloadSkill()read modern lockfile shapes. If the lockfile is missing or too old, rerunagentpm install.
- Agent installed on disk but not in the lockfile
loadAgent()uses the installed agent directory and theagent.lockroot entry together. If the lockfile is missing the agent root, reinstall it.
- Skill installed on disk but not in the lockfile
loadSkill()uses the installed skill directory and theagent.lockroot entry together. If the lockfile is missing the skill root, reinstall it.
15) Best practices#
- Define strong IO schemas so agents construct correct arguments.
- Keep logs off stdout; stdout is reserved for the final JSON.
- Set sensible timeouts (
entrypoint.timeout_ms) and document required env vars in your tool README/manifest descriptions.