Node SDK#
Install a tool with the CLI, install the SDK in your Node app, load tools, and call them.
Goal#
Install the Node SDK and use load() to call a published tool from your app.
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>
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) 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();6) 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
7) 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.
Tip
If your tool needs more memory, you can add your own flag in entrypoint.args (e.g., --max-old-space-size=512 for Node).
8) 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
9) 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.