agent-lattice 0.9.21 → 0.9.24

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
@@ -283,6 +283,47 @@ const result = await agent.prompt("What is 2+2?");
283
283
  console.log(result.result);
284
284
  ```
285
285
 
286
+ ## Tool Batch Policy
287
+
288
+ Use `toolBatchPolicy` when some tools must not run in the same model response.
289
+ The policy sees the complete batch before any tool executes. If it rejects the
290
+ batch, no tool runs and every tool call receives a structured `is_error: true`
291
+ result.
292
+
293
+ ```ts
294
+ const lead = createAgent({
295
+ model: "claude-sonnet-4-6",
296
+ tools: [incrementRevision],
297
+ toolBatchPolicy: {
298
+ validate({ toolCalls }) {
299
+ const incrementsRevision = toolCalls.find(
300
+ call => call.name === "incrementRevision",
301
+ );
302
+ const handoff = toolCalls.find(
303
+ call => call.kind === "agent_tool" &&
304
+ (call.input as { mode?: string }).mode === "handoff",
305
+ );
306
+ if (incrementsRevision && handoff) {
307
+ return {
308
+ allowed: false,
309
+ code: "invalid_tool_batch",
310
+ message: "Update the revision before delegating dependent work.",
311
+ conflictingToolCallIds: [incrementsRevision.id, handoff.id],
312
+ suggestedNextStep: "Run incrementRevision first, then hand off the new revision.",
313
+ };
314
+ }
315
+ return { allowed: true };
316
+ },
317
+ },
318
+ });
319
+ ```
320
+
321
+ The policy may be synchronous or asynchronous. If it throws, the SDK rejects
322
+ the whole batch with `tool_batch_policy_error`; no tool has executed. Without a
323
+ policy, tool execution is unchanged. A policy prevents known bad combinations
324
+ inside one model response, but it does not replace database transactions or
325
+ revision checks against concurrent external updates.
326
+
286
327
  ## Business Context For Tools
287
328
 
288
329
  Pass host application data through `context`. The SDK gives that context to
@@ -533,6 +574,13 @@ delivery: the runtime waits for the member's upstream reply, feeds it back to
533
574
  the lead, and keeps going until the root lead returns the final result or the
534
575
  run terminates.
535
576
 
577
+ After one model response queues one or more handoffs, the SDK does not call the
578
+ lead model again immediately. It first runs those members, collects their
579
+ completed or failed reports, and only then calls the lead again. All handoffs
580
+ from the current tool batch are queued before the lead pauses. The receipt keeps
581
+ `status: "accepted"` for compatibility and also includes `phase: "queued"`,
582
+ `completion_pending: true`, `message_id`, `work_item_id`, and `thread_id`.
583
+
536
584
  Accepted handoffs use work-item failure isolation by default. If one member
537
585
  returns an agent error such as `MaxTurnsError` or `APIError`, the runtime marks
538
586
  that work item `failed`, sends a failure report to the lead, and continues the
@@ -541,6 +589,25 @@ together and decides whether to retry, revise the task, accept a partial result,
541
589
  or finish. A run-wide `AbortError` still stops the runner; the runtime marks the
542
590
  current and remaining accepted work `cancelled` before propagating the abort.
543
591
 
592
+ Handoff work is serial by default. Set a bounded concurrency limit on the team
593
+ when independent members should run at the same time:
594
+
595
+ ```ts
596
+ const team = createTeam({
597
+ name: "research",
598
+ lead,
599
+ members,
600
+ runner: { maxConcurrentWorkItems: 4 },
601
+ });
602
+ ```
603
+
604
+ You can also pass `maxConcurrentWorkItems` to `createTeamRunner()`. The limit
605
+ must be a positive integer and defaults to `1`. It applies across different
606
+ member mailboxes; work addressed to the same mailbox remains serial because an
607
+ Agent may keep mutable conversation state. Runtime events are emitted as work
608
+ actually progresses, while the reports injected back into the lead stay in the
609
+ original handoff order.
610
+
544
611
  Team member tools can also request explicit shared workspace write grants:
545
612
 
546
613
  ```ts
package/dist/index.d.ts CHANGED
@@ -100,6 +100,7 @@ export type AgentRuntimeContext = {
100
100
  permissions: RuntimePermissions;
101
101
  delegate(input: AgentRuntimeDelegateInput): Promise<AgentRuntimeDelegateResult>;
102
102
  emit(message: TeamRunnerMessage): void;
103
+ shouldPauseAfterToolBatch?(): boolean;
103
104
  };
104
105
  export type ContextTraceEventType = "run_start" | "user_message" | "model_request" | "assistant_message" | "tool_use" | "tool_result" | "team_message" | "result" | "error";
105
106
  export type ContextTraceEvent = {
@@ -176,6 +177,32 @@ export interface ModelClient {
176
177
  export type ToolResult = {
177
178
  content: string | ContentBlock[];
178
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
+ };
179
206
  export type ToolExecutionContext<TContext = unknown> = {
180
207
  signal?: AbortSignal;
181
208
  toolUseId: string;
@@ -187,6 +214,7 @@ export type ToolHandler<TInput = unknown, TContext = unknown> = (input: TInput,
187
214
  export type ToolDefinition<TInput = unknown, TContext = unknown> = {
188
215
  name: string;
189
216
  description: string;
217
+ kind?: ToolKind;
190
218
  inputSchema: unknown;
191
219
  jsonSchema: Record<string, unknown>;
192
220
  parse(input: unknown): TInput;
@@ -312,12 +340,17 @@ export type SQLiteMailboxOptions = {
312
340
  database: SQLiteDatabaseLike;
313
341
  tableName?: string;
314
342
  };
343
+ export type TeamRunnerConfig = {
344
+ maxDelegateDepth?: number;
345
+ maxConcurrentWorkItems?: number;
346
+ };
315
347
  export type TeamOptions = {
316
348
  name: string;
317
349
  lead: Agent<any>;
318
350
  members: TeamMemberDefinition[];
319
351
  mailbox?: TeamMailbox;
320
352
  exposeLeadMailboxTools?: boolean;
353
+ runner?: TeamRunnerConfig;
321
354
  };
322
355
  export type Team = {
323
356
  name: string;
@@ -326,6 +359,7 @@ export type Team = {
326
359
  mailbox: TeamMailbox;
327
360
  tools: Array<ToolDefinition<any, any>>;
328
361
  memberTools: Record<string, Array<ToolDefinition<any, any>>>;
362
+ readonly runnerOptions?: TeamRunnerConfig;
329
363
  send(from: string, to: string, content: string, options?: TeamSendOptions): Promise<TeamMessage>;
330
364
  drain(options?: TeamDrainOptions): Promise<TeamDrainResult>;
331
365
  query(prompt: string | ContentBlock[], options?: QueryOptions): AsyncGenerator<TeamRunnerMessage>;
@@ -341,12 +375,11 @@ export type TeamDrainResult = {
341
375
  failed: number;
342
376
  rounds: number;
343
377
  };
344
- export type TeamRunnerOptions = {
378
+ export type TeamRunnerOptions = TeamRunnerConfig & {
345
379
  team?: Team;
346
380
  root?: AgentLike<any>;
347
381
  mailbox?: TeamMailbox;
348
382
  source?: AgentRuntimeSource;
349
- maxDelegateDepth?: number;
350
383
  };
351
384
  export type TeamRunner = {
352
385
  root: AgentLike<any>;
@@ -380,6 +413,7 @@ export type AgentOptions<TContext = unknown> = {
380
413
  maxTurns?: number;
381
414
  thinkingConfig?: ThinkingConfig;
382
415
  tools?: Array<ToolDefinition<any, TContext>>;
416
+ toolBatchPolicy?: ToolBatchPolicy<TContext>;
383
417
  skills?: SkillDefinition[];
384
418
  workspace?: AgentWorkspaceOptions;
385
419
  permission?: (request: PermissionRequest) => Promise<PermissionDecision> | PermissionDecision;
@@ -484,6 +518,10 @@ export declare class MaxTurnsError extends AgentSDKError {
484
518
  }
485
519
  export declare class AbortError extends AgentSDKError {
486
520
  }
521
+ export declare class ToolBatchRejectedError extends AgentSDKError {
522
+ readonly rejection: ToolBatchPolicyRejection;
523
+ constructor(rejection: ToolBatchPolicyRejection);
524
+ }
487
525
  export declare class ToolPermissionDeniedError extends AgentSDKError {
488
526
  readonly denial: PermissionDenial;
489
527
  constructor(denial: PermissionDenial);