agent-lattice 0.20.0 → 0.22.0

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
@@ -534,9 +534,15 @@ Unlike `outputFormat` (which relies on the provider's
534
534
  `submit_output` only requires a model that can call tools, so it ports to any
535
535
  tool-capable provider.
536
536
 
537
- The same schema composes with `agentTool()` for parent/child delegation: pass
538
- it to the child agent and to `AgentToolOptions.outputSchema` on the parent
539
- side, and an `ask` call returns the child's validated output as a JSON string:
537
+ The same schema composes with `agentTool()` for parent/child delegation: give
538
+ it to the child agent, and an `ask` call returns the child's validated output
539
+ as a JSON string. Since 0.21.0 the parent side inherits the child's declared
540
+ schema, so the `agentTool()` copy can be omitted. Passing
541
+ `AgentToolOptions.outputSchema` explicitly is still allowed, but it must match
542
+ the target's declaration (compared by reference, then by derived JSON Schema
543
+ structure) — a mismatch throws at assembly time, so drift fails fast instead
544
+ of at call time. The error message suggests sharing one schema instance or
545
+ omitting the `agentTool()` copy:
540
546
 
541
547
  ```ts
542
548
  import { agentTool, createAgent } from "agent-lattice";
@@ -552,16 +558,66 @@ const parent = createAgent({
552
558
  tools: [
553
559
  agentTool("review", child, {
554
560
  description: "Ask the reviewer to audit a change.",
555
- outputSchema: reviewSchema, // validates the child's submission
561
+ // outputSchema is inherited from the child (0.21.0+); an explicit copy
562
+ // must match the child's declaration or agentTool() throws.
556
563
  }),
557
564
  ],
558
565
  });
559
566
  ```
560
567
 
561
568
  If the child ends without submitting or submits a payload that fails the
562
- parent's schema, the tool returns an `is_error` `tool_result` starting with
569
+ schema, the tool returns an `is_error` `tool_result` starting with
563
570
  `child_output_invalid:`, so the parent model sees the failure and can retry.
564
571
 
572
+ *Behavior change in 0.21.0:* where the child declares an `outputSchema` and
573
+ the parent does not, the `ask` tool result changed from the fixed text
574
+ `"Structured output submitted."` to the validated JSON. That is the intended
575
+ fix and ships in a minor under 0.x. Host-defined `AgentLike` adapters carry no
576
+ readable declaration, so nothing is inherited or cross-checked for them; an
577
+ explicit `outputSchema` still applies. Whenever a schema is in effect, the
578
+ generated tool description states that the tool returns the target's
579
+ validated structured output as JSON.
580
+
581
+ ### Typed delegation
582
+
583
+ *Requires 0.21.0 or later.*
584
+
585
+ `AgentToolOptions.inputSchema` replaces the default `{mode, task,
586
+ expectedOutput, acceptanceCriteria, workspaceGrants}` input shape with your
587
+ own schema (a zod schema works directly), and `mapInput` projects the
588
+ validated input into the child prompt:
589
+
590
+ ```ts
591
+ const judge = createAgent({
592
+ model: "claude-sonnet-4-5",
593
+ systemPrompt: "You judge a case and submit a structured verdict.",
594
+ outputSchema: verdictSchema,
595
+ });
596
+
597
+ const judgeTool = agentTool("judge", judge, {
598
+ description: "Judge a case from its summary and documents.",
599
+ inputSchema: z.object({
600
+ caseSummary: z.string(),
601
+ documents: z.array(z.object({ title: z.string(), content: z.string() })),
602
+ }),
603
+ mapInput: input => renderJudgeTask(input.caseSummary, input.documents),
604
+ });
605
+ ```
606
+
607
+ - The parent's arguments are parsed against `inputSchema` before anything
608
+ runs; invalid input is rejected as an error `tool_result` in the parent's
609
+ loop — the same semantics as a plain `tool()` call — and the child is never
610
+ invoked.
611
+ - `inputSchema` and `mapInput` must come as a pair: `agentTool()` throws at
612
+ assembly time when one is missing.
613
+ - Typed delegation is ask-only: there is no `mode` field and no
614
+ `workspaceGrants`.
615
+ - `mapInput` may return a string or `ContentBlock[]`; `ContentBlock[]` is only
616
+ supported for direct `ask` calls — inside a team runtime the projected
617
+ prompt must be a string, or the call fails at runtime.
618
+ - Because typed input no longer matches `AgentToolInput`, `agentTool()` now
619
+ returns `ToolDefinition<any>`.
620
+
565
621
  ## Concurrent Tool Calls
566
622
 
567
623
  The model requests concurrency by returning multiple `tool_use` blocks in one
@@ -644,6 +700,46 @@ policy, tool execution is unchanged. A policy prevents known bad combinations
644
700
  inside one model response, but it does not replace database transactions or
645
701
  revision checks against concurrent external updates.
646
702
 
703
+ ## Strict Option Validation And Tool Metadata
704
+
705
+ *Requires 0.22.0 or later.*
706
+
707
+ The option objects of `createAgent()`/`createBareAgent()`/`defineAgent()`
708
+ (`AgentOptions`), `agentTool()` (`AgentToolOptions`), `delegateTool()`
709
+ (`DelegateToolOptions`), and `tool()` (`ToolOptions`) are validated strictly:
710
+ an unknown key throws at assembly time —
711
+
712
+ ```
713
+ AgentOptions: unknown option "bogusOption". Check for a typo, or upgrade the SDK if this option was added in a newer version.
714
+ ```
715
+
716
+ (`agentTool()`/`delegateTool()` prefix the message with `agentTool("<name>"):` /
717
+ `delegateTool("<name>"):` instead.) The point is to fail fast on the old-SDK +
718
+ new-API combination: before 0.22.0 an unknown option was silently ignored, so
719
+ calling a newer API on an older install "worked" with the feature absent.
720
+
721
+ *Behavior change in 0.22.0:* extra keys that used to be silently ignored —
722
+ for example host fields spread into an options object — now throw. If your
723
+ host assembles options by spreading wider objects, strip the extra fields when
724
+ upgrading.
725
+
726
+ Separately, `ToolOptions.metadata` and `AgentToolOptions.metadata` accept a
727
+ `Record<string, unknown>` that is passed through to `ToolDefinition.metadata`:
728
+
729
+ ```ts
730
+ const search = tool(
731
+ "search",
732
+ "Search documents",
733
+ z.object({ query: z.string() }),
734
+ async ({ query }) => ({ content: await documentIndex.search(query) }),
735
+ { metadata: { contractVersion: 3 } },
736
+ );
737
+ ```
738
+
739
+ The SDK never reads or interprets `metadata`, and it is never shown to the
740
+ model — it is host-owned, machine-readable annotation (for example a contract
741
+ version). When not passed, the key is absent from the `ToolDefinition`.
742
+
647
743
  ## Automatic Context Compaction
648
744
 
649
745
  History only grows, so a long-running agent eventually exceeds the model's
package/dist/index.d.ts CHANGED
@@ -369,6 +369,11 @@ export type AgentHooks<TContext = unknown> = {
369
369
  export type ToolHandler<TInput = unknown, TContext = unknown> = (input: TInput, context: ToolExecutionContext<TContext>) => Promise<ToolResult> | ToolResult;
370
370
  export type ToolOptions<TInput = unknown> = {
371
371
  isConcurrencySafe?: (input: TInput) => boolean;
372
+ /**
373
+ * Host-owned, machine-readable annotations carried on the tool definition.
374
+ * The SDK never reads or interprets it; it is never shown to the model.
375
+ */
376
+ metadata?: Record<string, unknown>;
372
377
  };
373
378
  export type ToolDefinition<TInput = unknown, TContext = unknown> = {
374
379
  name: string;
@@ -379,6 +384,8 @@ export type ToolDefinition<TInput = unknown, TContext = unknown> = {
379
384
  parse(input: unknown): TInput;
380
385
  handler: ToolHandler<TInput, TContext>;
381
386
  isConcurrencySafe?: (input: TInput) => boolean;
387
+ /** Host-owned annotations passed through from `ToolOptions.metadata`. */
388
+ metadata?: Record<string, unknown>;
382
389
  };
383
390
  export type ToolConcurrencyMode = "safe" | "all" | "sequential";
384
391
  export type ToolConcurrencyOptions = {
@@ -822,13 +829,40 @@ export type AgentToolOptions = {
822
829
  description: string;
823
830
  targetMailboxId?: string;
824
831
  /**
825
- * Expected structured output of the target, declared with the same schema
826
- * the target uses for its own `AgentOptions.outputSchema`. With mode "ask"
827
- * the tool result is the target's validated structured output as JSON; a
828
- * target that ends without submitting — or submits a payload that fails this
829
- * schema — produces a `child_output_invalid` tool error the parent can retry.
832
+ * Host-owned, machine-readable annotations carried on the tool definition
833
+ * (e.g. a contract version). The SDK never reads or interprets it; it is
834
+ * never shown to the model.
835
+ */
836
+ metadata?: Record<string, unknown>;
837
+ /**
838
+ * Expected structured output of the target. When omitted, the declaration is
839
+ * inherited from the target itself (an Agent's or AgentSpec's
840
+ * `AgentOptions.outputSchema`); when passed explicitly it must match the
841
+ * target's declaration or `agentTool()` throws at assembly time. Host-defined
842
+ * AgentLike adapters carry no readable declaration, so nothing is inherited
843
+ * or cross-checked for them.
844
+ *
845
+ * With the schema in effect, the tool result is the target's validated
846
+ * structured output as JSON; a target that ends without submitting — or
847
+ * submits a payload that fails the schema — produces a `child_output_invalid`
848
+ * tool error the parent can retry.
830
849
  */
831
850
  outputSchema?: OutputSchema;
851
+ /**
852
+ * Typed delegation: replaces the default `{mode, task, ...}` input shape
853
+ * with this schema (same structural shape as OutputSchema; a zod schema
854
+ * works directly). The parent's arguments are validated before the child is
855
+ * invoked, and `mapInput` projects the validated input into the child
856
+ * prompt. Typed delegation is ask-only: there is no `mode` field and no
857
+ * `workspaceGrants`. Requires `mapInput`.
858
+ */
859
+ inputSchema?: OutputSchema;
860
+ /**
861
+ * Projects validated `inputSchema` input into the child prompt. Returning
862
+ * `ContentBlock[]` is only supported for direct ask calls; inside a team
863
+ * runtime the projected prompt must be a string.
864
+ */
865
+ mapInput?: (input: any) => string | ContentBlock[];
832
866
  };
833
867
  /**
834
868
  * An AgentLike is a live session: it keeps its conversation history across
@@ -837,7 +871,7 @@ export type AgentToolOptions = {
837
871
  * continuity.
838
872
  */
839
873
  export type AgentToolTarget = AgentLike<any> | AgentSpec<any>;
840
- export declare function agentTool(name: string, agent: AgentToolTarget, options: AgentToolOptions): ToolDefinition<AgentToolInput>;
874
+ export declare function agentTool(name: string, agent: AgentToolTarget, options: AgentToolOptions): ToolDefinition<any>;
841
875
  export declare function delegateTool(name: string, description: string, agent: AgentToolTarget, options?: DelegateToolOptions): ToolDefinition<{
842
876
  task: string;
843
877
  }>;
@@ -987,6 +1021,8 @@ declare class Agent<TContext = unknown> {
987
1021
  */
988
1022
  replaceHistory(messages: ModelMessage[]): Promise<void>;
989
1023
  addTools(tools: Array<ToolDefinition<any, TContext>>): void;
1024
+ /** The structured output contract declared via `AgentOptions.outputSchema`, if any. */
1025
+ get outputSchema(): OutputSchema | undefined;
990
1026
  private initMessage;
991
1027
  private resultMessage;
992
1028
  private modelTools;