v3.0.0
Released: August 2, 2026
Highlights
- Per-instance logger (
toolpack-sdk) —Loggeris now a class instantiated once perToolpack. Multiple instances in the same process each write to their own log file and config independently. - Per-instance request counter (
toolpack-sdk) —requestSeqmoved from module-level to instance-level, eliminating sequence number collisions across concurrent instances. ModeConfig.customToolsandModeConfig.toolsConfig(toolpack-sdk) — custom tools and tool behavior overrides are now scoped per agent via the mode, not registered globally at init.toolsConfigoverrides are merged at request time.- Inline
Toolpack.init()configuration (toolpack-sdk) —logging,hitl, andtoolsConfigare now first-class init fields.customTools,modeOverrides, andconfigPathare removed. AgentInput.signaland abort propagation (@toolpack-sdk/agents) — agents acceptsignal?: AbortSignal. Aborting a root agent cancels the entire delegation chain throughdelegate_to_agentanddelegate_and_forget.
Breaking changes
| Removed | Replacement |
|---|---|
ToolpackInitConfig.customTools | ModeConfig.customTools (per-agent) or sdk.loadToolProject() (global) |
ToolpackInitConfig.modeOverrides | Configure modes via customModes / registerMode() |
ToolpackInitConfig.configPath | All 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:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable file logging |
filePath | string | toolpack-sdk.log | Log file path (relative to CWD) |
level | string | info | error | warn | info | debug | trace |
console | boolean | false | Mirror 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.
| Field | Type | Default | Description |
|---|---|---|---|
customTools | ToolDefinition[] | [] | Agent-specific tools, never shared globally |
toolsConfig | Partial<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:
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable HITL. Auto-enabled when onToolConfirm is provided |
confirmationMode | string | "all" | "off" | "high-only" | "all" |
bypass.tools | string[] | [] | Tool names to skip |
bypass.categories | string[] | [] | Categories to skip |
bypass.levels | string[] | [] | 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();
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