agent-lattice 0.9.23 → 0.9.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -230,9 +230,13 @@ const tracer = createLangSmithContextTracer({
230
230
  });
231
231
  ```
232
232
 
233
- LangSmith receives one root `chain` run per SDK query, child `llm` runs for
234
- model turns, child `tool` runs for SDK tool calls, and run events for auxiliary
235
- trace events.
233
+ LangSmith receives one root `chain` run per SDK query. For an `Agent` query,
234
+ model turns and SDK tool calls appear as child `llm` and `tool` runs. For a
235
+ `Team` query, the root represents the complete Team invocation; the initial
236
+ Lead run, delegated Member runs, and later Lead runs all appear beneath that
237
+ root and share one trace session. Each Agent keeps its own SDK session identity,
238
+ recorded as `agent_session_id` metadata, so tracing does not change Agent state
239
+ or returned SDK messages.
236
240
 
237
241
  Custom sinks can implement the same interface for SQLite, OpenTelemetry, object
238
242
  storage, or host-specific observability. The functions below are application
@@ -283,6 +287,88 @@ const result = await agent.prompt("What is 2+2?");
283
287
  console.log(result.result);
284
288
  ```
285
289
 
290
+ ## Concurrent Tool Calls
291
+
292
+ The model requests concurrency by returning multiple `tool_use` blocks in one
293
+ assistant message. The SDK makes the final safety decision. By default, only
294
+ tools whose parsed input passes `isConcurrencySafe(input)` run together:
295
+
296
+ ```ts
297
+ const search = tool(
298
+ "search",
299
+ "Search documents",
300
+ z.object({ query: z.string() }),
301
+ async ({ query }) => {
302
+ // App code: replace with your database or search client.
303
+ return { content: await documentIndex.search(query) };
304
+ },
305
+ { isConcurrencySafe: () => true },
306
+ );
307
+
308
+ const agent = createAgent({
309
+ model: "claude-sonnet-4-6",
310
+ tools: [search],
311
+ toolConcurrency: { mode: "safe", maxConcurrency: 8 },
312
+ });
313
+ ```
314
+
315
+ `safe` is the default mode, `maxConcurrency` defaults to `10`, and tools without
316
+ an `isConcurrencySafe` declaration stay sequential. Use `mode: "all"` only when
317
+ every tool in the Agent is safe to overlap. Use `mode: "sequential"` to disable
318
+ tool concurrency even for tools marked safe.
319
+
320
+ When concurrency is available, the SDK tells the model to batch independent
321
+ calls and to use separate assistant responses when a later call needs an earlier
322
+ result. Runtime safety checks and `toolBatchPolicy` remain authoritative.
323
+
324
+ The SDK waits for the complete batch before requesting the model again. Tools
325
+ may finish in any order, while the `tool_result` blocks sent to the model remain
326
+ in the original `tool_use` order. One tool failure does not discard the other
327
+ results. On abort, running handlers receive the shared `AbortSignal`, queued
328
+ handlers do not start, and the SDK waits for handlers that already started to
329
+ settle.
330
+
331
+ ## Tool Batch Policy
332
+
333
+ Use `toolBatchPolicy` when some tools must not run in the same model response.
334
+ The policy sees the complete batch before any tool executes. If it rejects the
335
+ batch, no tool runs and every tool call receives a structured `is_error: true`
336
+ result.
337
+
338
+ ```ts
339
+ const lead = createAgent({
340
+ model: "claude-sonnet-4-6",
341
+ tools: [incrementRevision],
342
+ toolBatchPolicy: {
343
+ validate({ toolCalls }) {
344
+ const incrementsRevision = toolCalls.find(
345
+ call => call.name === "incrementRevision",
346
+ );
347
+ const handoff = toolCalls.find(
348
+ call => call.kind === "agent_tool" &&
349
+ (call.input as { mode?: string }).mode === "handoff",
350
+ );
351
+ if (incrementsRevision && handoff) {
352
+ return {
353
+ allowed: false,
354
+ code: "invalid_tool_batch",
355
+ message: "Update the revision before delegating dependent work.",
356
+ conflictingToolCallIds: [incrementsRevision.id, handoff.id],
357
+ suggestedNextStep: "Run incrementRevision first, then hand off the new revision.",
358
+ };
359
+ }
360
+ return { allowed: true };
361
+ },
362
+ },
363
+ });
364
+ ```
365
+
366
+ The policy may be synchronous or asynchronous. If it throws, the SDK rejects
367
+ the whole batch with `tool_batch_policy_error`; no tool has executed. Without a
368
+ policy, tool execution is unchanged. A policy prevents known bad combinations
369
+ inside one model response, but it does not replace database transactions or
370
+ revision checks against concurrent external updates.
371
+
286
372
  ## Business Context For Tools
287
373
 
288
374
  Pass host application data through `context`. The SDK gives that context to
package/dist/index.d.ts CHANGED
@@ -177,6 +177,32 @@ export interface ModelClient {
177
177
  export type ToolResult = {
178
178
  content: string | ContentBlock[];
179
179
  };
180
+ export type ToolKind = "tool" | "agent_tool";
181
+ export type ToolBatchCall = {
182
+ id: string;
183
+ name: string;
184
+ input: unknown;
185
+ kind: ToolKind;
186
+ };
187
+ export type ToolBatchPolicyContext<TContext = unknown> = {
188
+ source?: AgentRuntimeSource;
189
+ toolCalls: ToolBatchCall[];
190
+ context?: TContext;
191
+ signal?: AbortSignal;
192
+ };
193
+ export type ToolBatchPolicyRejection = {
194
+ allowed: false;
195
+ code: string;
196
+ message: string;
197
+ conflictingToolCallIds?: string[];
198
+ suggestedNextStep?: string;
199
+ };
200
+ export type ToolBatchPolicyResult = {
201
+ allowed: true;
202
+ } | ToolBatchPolicyRejection;
203
+ export type ToolBatchPolicy<TContext = unknown> = {
204
+ validate(context: ToolBatchPolicyContext<TContext>): ToolBatchPolicyResult | Promise<ToolBatchPolicyResult>;
205
+ };
180
206
  export type ToolExecutionContext<TContext = unknown> = {
181
207
  signal?: AbortSignal;
182
208
  toolUseId: string;
@@ -185,13 +211,23 @@ export type ToolExecutionContext<TContext = unknown> = {
185
211
  permissions?: RuntimePermissions;
186
212
  };
187
213
  export type ToolHandler<TInput = unknown, TContext = unknown> = (input: TInput, context: ToolExecutionContext<TContext>) => Promise<ToolResult> | ToolResult;
214
+ export type ToolOptions<TInput = unknown> = {
215
+ isConcurrencySafe?: (input: TInput) => boolean;
216
+ };
188
217
  export type ToolDefinition<TInput = unknown, TContext = unknown> = {
189
218
  name: string;
190
219
  description: string;
220
+ kind?: ToolKind;
191
221
  inputSchema: unknown;
192
222
  jsonSchema: Record<string, unknown>;
193
223
  parse(input: unknown): TInput;
194
224
  handler: ToolHandler<TInput, TContext>;
225
+ isConcurrencySafe?: (input: TInput) => boolean;
226
+ };
227
+ export type ToolConcurrencyMode = "safe" | "all" | "sequential";
228
+ export type ToolConcurrencyOptions = {
229
+ mode?: ToolConcurrencyMode;
230
+ maxConcurrency?: number;
195
231
  };
196
232
  export type SkillDefinition = {
197
233
  name: string;
@@ -386,6 +422,8 @@ export type AgentOptions<TContext = unknown> = {
386
422
  maxTurns?: number;
387
423
  thinkingConfig?: ThinkingConfig;
388
424
  tools?: Array<ToolDefinition<any, TContext>>;
425
+ toolBatchPolicy?: ToolBatchPolicy<TContext>;
426
+ toolConcurrency?: ToolConcurrencyOptions;
389
427
  skills?: SkillDefinition[];
390
428
  workspace?: AgentWorkspaceOptions;
391
429
  permission?: (request: PermissionRequest) => Promise<PermissionDecision> | PermissionDecision;
@@ -490,12 +528,16 @@ export declare class MaxTurnsError extends AgentSDKError {
490
528
  }
491
529
  export declare class AbortError extends AgentSDKError {
492
530
  }
531
+ export declare class ToolBatchRejectedError extends AgentSDKError {
532
+ readonly rejection: ToolBatchPolicyRejection;
533
+ constructor(rejection: ToolBatchPolicyRejection);
534
+ }
493
535
  export declare class ToolPermissionDeniedError extends AgentSDKError {
494
536
  readonly denial: PermissionDenial;
495
537
  constructor(denial: PermissionDenial);
496
538
  }
497
- export declare function tool<TContext = unknown>(): <TSchema>(name: string, description: string, inputSchema: TSchema, handler: ToolHandler<InferInput<TSchema>, TContext>) => ToolDefinition<InferInput<TSchema>, TContext>;
498
- export declare function tool<TSchema, TContext = unknown>(name: string, description: string, inputSchema: TSchema, handler: ToolHandler<InferInput<TSchema>, TContext>): ToolDefinition<InferInput<TSchema>, TContext>;
539
+ export declare function tool<TContext = unknown>(): <TSchema>(name: string, description: string, inputSchema: TSchema, handler: ToolHandler<InferInput<TSchema>, TContext>, options?: ToolOptions<InferInput<TSchema>>) => ToolDefinition<InferInput<TSchema>, TContext>;
540
+ export declare function tool<TSchema, TContext = unknown>(name: string, description: string, inputSchema: TSchema, handler: ToolHandler<InferInput<TSchema>, TContext>, options?: ToolOptions<InferInput<TSchema>>): ToolDefinition<InferInput<TSchema>, TContext>;
499
541
  export type DelegateToolOptions = {
500
542
  wait?: DelegateWaitMode;
501
543
  targetMailboxId?: string;
@@ -559,6 +601,7 @@ export declare class Agent<TContext = unknown> {
559
601
  private readonly modelClient;
560
602
  private readonly messages;
561
603
  private readonly sessionId;
604
+ private readonly toolConcurrency;
562
605
  constructor(options: AgentOptions<TContext>);
563
606
  query(prompt: string | ContentBlock[], options?: QueryOptions<TContext>): AsyncGenerator<SDKMessage>;
564
607
  prompt(prompt: string | ContentBlock[], options?: QueryOptions<TContext>): Promise<SDKResultMessage>;
@@ -569,6 +612,8 @@ export declare class Agent<TContext = unknown> {
569
612
  private messagesForModel;
570
613
  private selectSkills;
571
614
  private runTool;
615
+ private executeToolBatch;
616
+ private isToolCallConcurrencySafe;
572
617
  }
573
618
  type InferInput<TSchema> = TSchema extends {
574
619
  parse(input: unknown): infer TInput;