Skip to main content

v3.0.0

Released: August 2, 2026

Highlights

  • Per-instance logger (toolpack-sdk) — Logger is now a class instantiated once per Toolpack. Multiple instances in the same process each write to their own log file and config independently.
  • Per-instance request counter (toolpack-sdk) — requestSeq moved from module-level to instance-level, eliminating sequence number collisions across concurrent instances.
  • ModeConfig.customTools and ModeConfig.toolsConfig (toolpack-sdk) — custom tools and tool behavior overrides are now scoped per agent via the mode, not registered globally at init. toolsConfig overrides are merged at request time.
  • Inline Toolpack.init() configuration (toolpack-sdk) — logging, hitl, and toolsConfig are now first-class init fields. customTools, modeOverrides, and configPath are removed.
  • AgentInput.signal and abort propagation (@toolpack-sdk/agents) — agents accept signal?: AbortSignal. Aborting a root agent cancels the entire delegation chain through delegate_to_agent and delegate_and_forget.

Breaking changes

RemovedReplacement
ToolpackInitConfig.customToolsModeConfig.customTools (per-agent) or sdk.loadToolProject() (global)
ToolpackInitConfig.modeOverridesConfigure modes via customModes / registerMode()
ToolpackInitConfig.configPathAll configuration is now passed inline to Toolpack.init()

toolpack.config.json is no longer read by the SDK

The SDK no longer discovers or reads toolpack.config.json at init time. All configuration — logging, HITL, tool behavior — must be passed explicitly to Toolpack.init().

If you were previously relying on a config file, move those values inline:

// Before (v2.x) — SDK read toolpack.config.json automatically
const sdk = await Toolpack.init({ provider: 'openai' });

// After (v3.0) — pass everything to init()
const sdk = await Toolpack.init({
provider: 'openai',
logging: { enabled: true, filePath: './app.log', level: 'info' },
hitl: { enabled: true, confirmationMode: 'all' },
toolsConfig: { maxToolRounds: 10 },
});

toolpack-sdk

Per-instance Logger

Logger is now a class. Each Toolpack instance creates its own logger from the logging field in Toolpack.init(). The previous module-level singleton meant all instances shared one log file and one config.

const tenantA = await Toolpack.init({
provider: 'anthropic',
logging: { enabled: true, filePath: './tenant-a.log', level: 'info' },
});

const tenantB = await Toolpack.init({
provider: 'anthropic',
logging: { enabled: true, filePath: './tenant-b.log', level: 'debug' },
});

LoggingConfig fields:

FieldTypeDefaultDescription
enabledbooleanfalseEnable file logging
filePathstringtoolpack-sdk.logLog file path (relative to CWD)
levelstringinfoerror | warn | info | debug | trace
consolebooleanfalseMirror output to stdout/stderr

Environment variables override programmatic config: TOOLPACK_SDK_LOG_ENABLED, TOOLPACK_SDK_LOG_FILE, TOOLPACK_SDK_LOG_LEVEL, TOOLPACK_SDK_LOG_CONSOLE.


ModeConfig.customTools and ModeConfig.toolsConfig

Custom tools and tool behavior overrides are now attached to a mode, keeping tool sets isolated between agents sharing the same Toolpack instance.

import { createMode } from 'toolpack-sdk';

const analystMode = createMode({
name: 'analyst',
systemPrompt: '...',
customTools: [...myDataTools.tools], // only available in this mode
toolsConfig: { maxToolRounds: 20 }, // overrides the global value for this mode
});

ModeConfig.toolsConfig is merged over the global toolsConfig from Toolpack.init() at request time — only specify the fields you want to override.

FieldTypeDefaultDescription
customToolsToolDefinition[][]Agent-specific tools, never shared globally
toolsConfigPartial<ToolsConfig>{}Per-agent tool behavior overrides

Inline ToolpackInitConfig configuration

logging, hitl, and toolsConfig replace the removed customTools, modeOverrides, and configPath fields. All configuration is now explicit at init time.

const sdk = await Toolpack.init({
provider: 'openai',
tools: true,
toolsConfig: {
maxToolRounds: 10,
additionalConfigurations: { MY_API_KEY: process.env.MY_API_KEY },
},
logging: { enabled: true, filePath: './app.log', level: 'debug' },
hitl: {
enabled: true,
confirmationMode: 'all',
bypass: { levels: ['medium'] },
},
onToolConfirm: async (tool) => {
const ok = await promptUser(`Allow ${tool.displayName}?`);
return ok ? 'allow' : 'deny';
},
});

HitlConfig fields:

FieldTypeDefaultDescription
enabledbooleanfalseEnable HITL. Auto-enabled when onToolConfirm is provided
confirmationModestring"all""off" | "high-only" | "all"
bypass.toolsstring[][]Tool names to skip
bypass.categoriesstring[][]Categories to skip
bypass.levelsstring[][]Risk levels to skip ("high" or "medium")

generate() abort check at tool-round boundaries

AIClient.generate() now checks request.signal?.aborted at the start of each tool-call round and exits immediately if the signal has fired.


@toolpack-sdk/agents

AgentInput.signal and AgentRunOptions.signal

Both types now accept signal?: AbortSignal. Pass an AbortController's signal to cancel an in-flight agent run.

const controller = new AbortController();

const result = await agent.invokeAgent({
message: 'Do something long-running',
signal: controller.signal,
});

controller.abort(); // cancel from a stop endpoint, UI button, or timeout

Web server stop pattern:

const activeRuns = new Map<string, AbortController>();

app.post('/api/chat', async (req, res) => {
const { sessionId, message } = req.body;
const controller = new AbortController();
activeRuns.set(sessionId, controller);
const result = await agent.invokeAgent({ message, signal: controller.signal });
activeRuns.delete(sessionId);
res.json(result);
});

app.post('/api/chat/stop', (req, res) => {
const controller = activeRuns.get(req.body.sessionId);
if (controller) { controller.abort(); activeRuns.delete(req.body.sessionId); }
res.json({ ok: true });
});

Abort propagation through delegation

delegate_to_agent and delegate_and_forget now forward the parent agent's abort signal into all sub-agent invocations. Aborting the root agent stops the entire delegation chain.

// Aborting executiveAgent also cancels every agent it delegates to
const controller = new AbortController();
await executiveAgent.invokeAgent({ message: '...', signal: controller.signal });
controller.abort();
note

The signal fires at tool-round boundaries, not mid-execution. A tool call that has already started finishes its current step before the abort is observed. This is standard cooperative-cancellation behavior.


Install

npm install toolpack-sdk@3.0.0
npm install @toolpack-sdk/agents@3.0.0