Agent SDK

Embed the Nib agent in TypeScript and Python programs, with in-process tools, permission callbacks, hooks, structured output and spending limits.

The Agent SDK runs the Nib agent from your own code. You send a prompt and get a stream of typed messages back. Tools you define, hooks and permission decisions run in your process; the agent itself runs in the Nib daemon, so every SDK session is an ordinary Nib session that you can open in the desktop app or resume with nib --resume.

The API is small: query() with options, a typed message stream, and your own tools, hooks and permission callbacks.

Install @nibapp/agent-sdk from npm or nib-agent-sdk from PyPI. Both need the nib CLI on your PATH, or its path in NIB_EXECUTABLE.

A first query

import { query } from "@nibapp/agent-sdk";

for await (const message of query({ prompt: "Find and fix the flaky test" })) {
  if (message.type === "result") console.log(message.result, message.total_cost_usd);
}

query() streams these messages:

MessageContains
systemSession start (init) and compaction boundaries
assistantText, thinking and tool-use blocks
userTool results
stream_eventPartial deltas, when includePartialMessages is on
resultOne per prompt: the final text, cost, usage, permission denials, structured output and the id of your message

While a query runs, you can call interrupt(), setModel(), setPermissionMode() and rewindFiles() on it. Pass an async iterable of messages as the prompt to stream follow-ups.

For a back-and-forth conversation, use NibSDKClient: connect(), query(), receiveResponse(), interrupt() and disconnect(). In TypeScript it supports await using.

Approving tool calls from code

canUseTool decides any tool call that isn't already allowed by the permission mode or allowedTools. It also answers the agent's multiple-choice questions.

const run = query({
  prompt: "Run the test suite and fix the first failure",
  options: {
    allowedTools: ["Read", "Grep", "Glob", "Bash(bun test:*)"],
    canUseTool: async (toolName, input, { title }) =>
      (await confirm(`Allow ${title ?? toolName}?`))
        ? { behavior: "allow" }
        : { behavior: "deny", message: "The user declined" },
  },
});

canUseTool is one input to Nib's permission engine, not a way around it. Deny rules and protected paths still apply, and the sandbox still contains shell commands.

Your own tools

Define tools in your process and hand them to the agent as an in-process MCP server. The agent sees them as mcp__<server>__<tool>.

import { z } from "zod";
import { createSdkMcpServer, query, tool } from "@nibapp/agent-sdk";

const tracker = createSdkMcpServer({
  name: "tracker",
  tools: [
    tool("get_ticket", "Look up a ticket by key", { key: z.string() }, async ({ key }) => ({
      content: [{ type: "text", text: await lookUp(key) }],
    })),
  ],
});

for await (const message of query({
  prompt: "Fix TICKET-1",
  options: { mcpServers: { tracker }, allowedTools: ["mcp__tracker__get_ticket"] },
})) {
  if (message.type === "result") console.log(message.result);
}

Hooks

hooks takes callbacks for the same events as hooks in settings (PreToolUse, PostToolUse, Stop and so on), with the same input and output JSON. In Python, register them with HookMatcher(matcher="Bash", hooks=[...]).

Options

Option names are camelCase in TypeScript and snake_case in Python (allowed_tools, permission_mode, max_budget_usd…).

OptionDefaultMeaning
model, effortWorkspace defaultprovider:model or a model id, and reasoning effort
cwdCurrent directoryThe workspace
systemPromptNib's full agent prompt{ type: "preset", preset: "nib", append } adds to it; a string replaces it
permissionModedefaultacceptEdits, plan, auto, dontAsk or bypassPermissions
allowedTools, disallowedToolsRules such as Bash(npm test:*) or Edit(src/**)
canUseTool, hooksSee above
agentsSubagents: { name: { description, prompt, tools?, model? } }
mcpServersstdio, http or sse server configs, or in-process servers
settingSourcesAllWhich of user, project and local settings, memory and MCP config load. [] loads none
settingsExtra settings, as in settings.json
resume, continue, forkSession, titlePick or fork a session
maxTurnsStop after this many model turns, with error_max_turns
maxBudgetUsdStop when the query's spend reaches this, with error_max_budget_usd
outputFormat{ type: "json_schema", schema }; the result carries structured_output
includePartialMessagesfalseStream partial deltas
abortControllerAborting stops the run
executable$NIB_EXECUTABLE, then nibThe CLI to start
daemonUrl, daemonTokenFind or start oneConnect to a specific daemon

A program enters bypassPermissions only on its own word: it starts the query in that mode, and only such a query can switch to it later with setPermissionMode(). A person's acceptance of bypass in the terminal UI, -p or an editor doesn't open it, because an SDK program is code that someone else may have written, so it opts in in its own source.

How it works

query() starts nib -p --input-format stream-json --output-format stream-json and talks to it over standard input and output. Your in-process tools and hooks are served to the agent by the CLI on a loopback endpoint with a random port and token, and each call is forwarded to your process. Both SDKs speak the same protocol.

Behavior to know

  • The default system prompt is Nib's complete agent prompt, not a minimal one.
  • rewindFiles(userMessageId) rewinds the conversation as well as the files. The id comes from the result's user_message_id.

Next

On this page