agent-lattice 0.16.0 → 0.17.1

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
@@ -55,6 +55,11 @@ const agent = createBareAgent({
55
55
  });
56
56
  ```
57
57
 
58
+ `Agent` is a type-only export — instances come from these factories (or
59
+ `AgentSpec.spawn()`), never from `new Agent()`, because the factories also
60
+ generate the session id and install the workspace. (Breaking in 0.17.0: the
61
+ `Agent` class constructor is no longer exported.)
62
+
58
63
  `agent.query()` yields `stream_event` messages while the model is still
59
64
  responding, so a host can render output incrementally:
60
65
 
@@ -135,10 +140,16 @@ const result = await agent.prompt("Solve this carefully.", {
135
140
 
136
141
  Use `{ type: "adaptive" }` for models that support adaptive thinking. Use
137
142
  `{ type: "enabled", budgetTokens }` for models that require a fixed budget, or
138
- `{ type: "disabled" }` to omit thinking from the provider request. A fixed
143
+ `{ type: "disabled" }` to turn thinking off. A fixed
139
144
  budget is capped at `maxTokens - 1` to satisfy the Anthropic API constraint.
140
145
  When omitted, the SDK does not send a thinking configuration.
141
146
 
147
+ `{ type: "disabled" }` is sent to the provider explicitly as
148
+ `thinking: { "type": "disabled" }` rather than omitted, because some
149
+ Anthropic-compatible providers (for example DeepSeek's
150
+ `https://api.deepseek.com/anthropic` endpoint) default thinking to on —
151
+ omitting the field would leave it enabled.
152
+
142
153
  For Kimi K3 through an Anthropic-compatible endpoint or gateway, use
143
154
  `reasoningEffort` to send the provider's top-level `reasoning_effort` parameter:
144
155
 
@@ -221,9 +232,9 @@ for await (const event of team.query("Ask engineering to investigate.", {
221
232
 
222
233
  ## LangSmith Context Tracing
223
234
 
224
- Pass LangSmith's `RunTree` constructor to the SDK tracer. The SDK depends on
225
- `langsmith` directly and uses its official `RunTree` / `RunTreeConfig` types for
226
- this adapter.
235
+ The SDK depends on `langsmith` directly and uses its official `RunTree` /
236
+ `RunTreeConfig` types for this adapter, so the tracer works out of the box —
237
+ no constructor wiring needed.
227
238
 
228
239
  Configure LangSmith with its standard environment variables:
229
240
 
@@ -237,7 +248,6 @@ LANGSMITH_WORKSPACE_ID=<your-langsmith-workspace-id>
237
248
  ```
238
249
 
239
250
  ```ts
240
- import { RunTree } from "langsmith/run_trees";
241
251
  import {
242
252
  createAgent,
243
253
  createCompositeContextTracer,
@@ -248,7 +258,6 @@ import {
248
258
  const tracer = createCompositeContextTracer([
249
259
  createJsonlContextTracer({ path: ".agent-runs/session.jsonl" }),
250
260
  createLangSmithContextTracer({
251
- RunTree,
252
261
  projectName: process.env.LANGSMITH_PROJECT,
253
262
  workspaceId: process.env.LANGSMITH_WORKSPACE_ID,
254
263
  tags: ["local-debug"],
@@ -277,11 +286,9 @@ tracer. `workspaceId` is optional and only selects a LangSmith workspace; it is
277
286
  not the tracing project name.
278
287
 
279
288
  ```ts
280
- import { RunTree } from "langsmith/run_trees";
281
289
  import { createLangSmithContextTracer } from "agent-lattice";
282
290
 
283
291
  const tracer = createLangSmithContextTracer({
284
- RunTree,
285
292
  apiKey: process.env.LANGSMITH_API_KEY,
286
293
  apiUrl: process.env.LANGSMITH_ENDPOINT,
287
294
  projectName: process.env.LANGSMITH_PROJECT,
@@ -290,6 +297,9 @@ const tracer = createLangSmithContextTracer({
290
297
  });
291
298
  ```
292
299
 
300
+ `RunTree` defaults to the bundled langsmith constructor since 0.17.0; pass
301
+ `RunTree` or `runTree` only to inject a custom runtime or a test fake.
302
+
293
303
  LangSmith receives one root `chain` run per SDK query. For an `Agent` query,
294
304
  model turns and SDK tool calls appear as child `llm` and `tool` runs. For a
295
305
  `Team` query, the root represents the complete Team invocation; the initial
@@ -299,15 +309,21 @@ recorded as `agent_session_id` metadata, so tracing does not change Agent state
299
309
  or returned SDK messages.
300
310
 
301
311
  Custom sinks can implement the same interface for SQLite, OpenTelemetry, object
302
- storage, or host-specific observability. The functions below are application
303
- code you provide, not SDK exports:
312
+ storage, or host-specific observability. A `ContextTracer` port object exposes
313
+ methods only — `failOnError` is bound when the factory creates the tracer, not
314
+ set as a field afterwards. Implement a custom sink with `defineContextTracer()`.
315
+ *Requires 0.17.0 or later.* (Breaking in 0.17.0: the public `failOnError` field
316
+ was removed from `ContextTracer`; custom tracers created before 0.17.0 must go
317
+ through `defineContextTracer()`.)
304
318
 
305
319
  ```ts
306
- const tracer = {
320
+ import { defineContextTracer } from "agent-lattice";
321
+
322
+ const tracer = defineContextTracer({
307
323
  async onEvent(event) {
308
324
  // TODO: Replace with your own storage/logging code.
309
325
  },
310
- };
326
+ });
311
327
  ```
312
328
 
313
329
  ## DeepSeek Anthropic-compatible API
@@ -730,13 +746,15 @@ const mcp = await connectMCPStreamableHTTPServer("https://mcp.example.com/mcp",
730
746
  type AgentLike<TContext = unknown> = {
731
747
  query(prompt, options?): AsyncGenerator<SDKMessage | TeamRunnerMessage>;
732
748
  prompt(prompt, options?): Promise<SDKResultMessage>;
733
- interrupt(): void;
749
+ interrupt(): boolean;
734
750
  };
735
751
  ```
736
752
 
737
753
  `interrupt()` ends the in-flight model request with an `"interrupted"` result
738
754
  (see [Interrupting A Query](#interrupting-a-query)); on a `Team` or
739
- `TeamRunner` it delegates to the lead/root agent. *Requires 0.16.0 or later.*
755
+ `TeamRunner` it delegates to the lead/root agent. It returns `true` when a
756
+ query was interrupted and `false` when idle. *Requires 0.16.0 or later; the
757
+ `boolean` return requires 0.17.0 or later.*
740
758
 
741
759
  That means a team can be used anywhere a callable agent is expected. From the
742
760
  outside, a team is an agent; inside, it can contain a whole organization.
@@ -1170,8 +1188,42 @@ The store contract is three methods — `load()`, `append(message)`, and
1170
1188
  The JSONL store's `load()` skips malformed lines rather than failing, so a
1171
1189
  torn final write does not lose the rest of the transcript. By default a
1172
1190
  failing store is swallowed and the conversation continues in memory only,
1173
- mirroring the tracer's failure semantics; set `failOnError: true` on the store
1174
- to propagate store errors out of `query()` instead.
1191
+ mirroring the tracer's failure semantics; bind `failOnError: true` when the
1192
+ store is created (via `createJsonlHistoryStore()` options or
1193
+ `defineHistoryStore()`) to propagate store errors out of `query()` instead.
1194
+
1195
+ Implement a custom store with `defineHistoryStore()`, which validates the
1196
+ three methods and binds `failOnError`; the returned port object exposes
1197
+ methods only. *Requires 0.17.0 or later.* (Breaking in 0.17.0: the public
1198
+ `failOnError` field was removed from `HistoryStore`; custom stores created
1199
+ before 0.17.0 must go through `defineHistoryStore()`.)
1200
+
1201
+ ```ts
1202
+ import { defineHistoryStore } from "agent-lattice";
1203
+
1204
+ const historyStore = defineHistoryStore({
1205
+ load: () => loadMessagesFromYourDatabase(),
1206
+ append: message => appendMessageToYourDatabase(message),
1207
+ replace: messages => replaceMessagesInYourDatabase(messages),
1208
+ });
1209
+ ```
1210
+
1211
+ To rewrite the history from the host instead of from compaction, use
1212
+ `agent.replaceHistory(messages)`. *Requires 0.17.0 or later.* It is idle-only:
1213
+ calling it while a query is running throws `ConcurrentQueryError`, the same
1214
+ guard as a concurrent `query()`. When a `historyStore` is configured the store
1215
+ is replaced too, so persistence stays in sync, and the replacement also
1216
+ suppresses the lazy `load()` — seeding never overwrites what the host just
1217
+ installed. The SDK does not validate the content: the host owns it, and the
1218
+ history must be well-formed (e.g. no dangling `tool_use` without its matching
1219
+ `tool_result`).
1220
+
1221
+ ```ts
1222
+ await agent.replaceHistory([
1223
+ { role: "user", content: "My name is Ada." },
1224
+ { role: "assistant", content: [{ type: "text", text: "Nice to meet you, Ada." }] },
1225
+ ]);
1226
+ ```
1175
1227
 
1176
1228
  ## Deadlines
1177
1229
 
@@ -1204,7 +1256,7 @@ loop indefinitely. Losing that race abandons the call rather than cancelling it.
1204
1256
 
1205
1257
  ## Interrupting A Query
1206
1258
 
1207
- *Requires 0.16.0 or later.*
1259
+ *Requires 0.16.0 or later; `interrupt()` returns `boolean` from 0.17.0.*
1208
1260
 
1209
1261
  `agent.interrupt()` ends the current query without tearing the conversation
1210
1262
  down. Where `QueryOptions.signal` terminates the query with `"error_abort"`,
@@ -1225,8 +1277,10 @@ await agent.prompt("Actually, skip 0.15.x and cover 0.16.0 only.");
1225
1277
 
1226
1278
  An interrupt that lands while a tool batch is executing takes effect once the
1227
1279
  batch completes: its tool results are written to history first, and the query
1228
- ends `"interrupted"` before the next model call. `interrupt()` is a no-op when
1229
- no query is running.
1280
+ ends `"interrupted"` before the next model call. `interrupt()` returns `true`
1281
+ when a query was running and is now interrupted, and `false` when idle — a
1282
+ `false` tells the host there is nothing to wait for, so it can send its next
1283
+ query directly.
1230
1284
 
1231
1285
  ## Token Usage And Truncation
1232
1286
 
package/dist/index.d.ts CHANGED
@@ -34,8 +34,12 @@ export type AgentLikeEvent = SDKMessage | TeamRunnerMessage;
34
34
  export type AgentLike<TContext = unknown> = {
35
35
  query(prompt: string | ContentBlock[], options?: QueryOptions<TContext>): AsyncGenerator<AgentLikeEvent>;
36
36
  prompt(prompt: string | ContentBlock[], options?: QueryOptions<TContext>): Promise<SDKResultMessage>;
37
- /** End the in-flight model request with an "interrupted" result; follow up with a new query(). No-op when idle. */
38
- interrupt(): void;
37
+ /**
38
+ * End the in-flight model request with an "interrupted" result; follow up
39
+ * with a new query(). Returns `true` when a query was interrupted, `false`
40
+ * when idle — a `false` means the host can send its next query directly.
41
+ */
42
+ interrupt(): boolean;
39
43
  };
40
44
  export type DelegateWaitMode = "result" | "accepted";
41
45
  export type AgentRuntimeSource = {
@@ -117,7 +121,6 @@ export type ContextTraceEvent = {
117
121
  data: Record<string, unknown>;
118
122
  };
119
123
  export type ContextTracer = {
120
- failOnError?: boolean;
121
124
  onEvent(event: ContextTraceEvent): Promise<void> | void;
122
125
  flush?(): Promise<void>;
123
126
  close?(): Promise<void>;
@@ -135,7 +138,9 @@ export type LangSmithRunTreeLike = RunTree;
135
138
  export type LangSmithRunTreeConstructor = new (config: LangSmithRunTreeConfig) => LangSmithRunTreeLike;
136
139
  export type LangSmithWriteReplicaConfig = NonNullable<LangSmithRunTreeConfig["replicas"]>[number];
137
140
  export type LangSmithContextTracerOptions = {
141
+ /** Defaults to the bundled langsmith RunTree; inject a constructor for custom runtimes. */
138
142
  RunTree?: LangSmithRunTreeConstructor;
143
+ /** Factory form of RunTree, mainly for tests. Takes precedence over RunTree. */
139
144
  runTree?: (config: LangSmithRunTreeConfig) => LangSmithRunTreeLike;
140
145
  projectName?: string;
141
146
  name?: string;
@@ -159,11 +164,18 @@ export type ModelMessage = {
159
164
  * whole history — a store must support full replacement. Methods may be
160
165
  * synchronous or return a promise; the Agent awaits them to preserve order.
161
166
  * Like `ContextTracer`, a failing store is swallowed by default and the
162
- * conversation continues in memory only; set `failOnError` to propagate store
163
- * errors out of `query()` instead.
167
+ * conversation continues in memory only; pass `failOnError` to the factory
168
+ * that creates the store (`createJsonlHistoryStore`, `defineHistoryStore`)
169
+ * to propagate store errors out of `query()` instead.
170
+ *
171
+ * The objects passed to `append()` and `replace()` are guaranteed to carry at
172
+ * least `role` and `content`. Runtime objects may carry extra fields (`usage`,
173
+ * `stopReason`, `providerResponseId`, `model`) that are not part of the
174
+ * contract and must not be relied on; hosts that need response metadata for
175
+ * reconciliation or analytics should use the ContextTracer's
176
+ * `assistant_message` events instead.
164
177
  */
165
178
  export type HistoryStore = {
166
- failOnError?: boolean;
167
179
  load(): ModelMessage[] | Promise<ModelMessage[]>;
168
180
  append(message: ModelMessage): void | Promise<void>;
169
181
  replace(messages: ModelMessage[]): void | Promise<void>;
@@ -489,8 +501,8 @@ export type Team = {
489
501
  drain(options?: TeamDrainOptions): Promise<TeamDrainResult>;
490
502
  query(prompt: string | ContentBlock[], options?: QueryOptions): AsyncGenerator<TeamRunnerMessage>;
491
503
  prompt(prompt: string | ContentBlock[], options?: QueryOptions): Promise<SDKResultMessage>;
492
- /** Interrupts the lead agent's in-flight model request. */
493
- interrupt(): void;
504
+ /** Interrupts the lead agent's in-flight model request; returns `true` when a query was interrupted, `false` when idle. */
505
+ interrupt(): boolean;
494
506
  };
495
507
  export type TeamDrainOptions = {
496
508
  maxRounds?: number;
@@ -513,8 +525,8 @@ export type TeamRunner = {
513
525
  mailbox: TeamMailbox;
514
526
  query(prompt: string | ContentBlock[], options?: QueryOptions): AsyncGenerator<TeamRunnerMessage>;
515
527
  prompt(prompt: string | ContentBlock[], options?: QueryOptions): Promise<SDKResultMessage>;
516
- /** Interrupts the root agent's in-flight model request. */
517
- interrupt(): void;
528
+ /** Interrupts the root agent's in-flight model request; returns `true` when a query was interrupted, `false` when idle. */
529
+ interrupt(): boolean;
518
530
  };
519
531
  export type PermissionRequest = {
520
532
  toolName: string;
@@ -774,6 +786,15 @@ export type AgentSpec<TContext = unknown> = {
774
786
  export declare function defineAgent<TContext = unknown>(options: AgentOptions<TContext>): AgentSpec<TContext>;
775
787
  export declare function isAgentSpec(target: AgentLike<any> | AgentSpec<any>): target is AgentSpec<any>;
776
788
  export declare function createBuiltinTools(options?: AgentWorkspaceToolsOptions): Array<ToolDefinition<any, any>>;
789
+ /**
790
+ * Entry point for implementing a custom trace sink. Validates that the
791
+ * required `onEvent` method exists and binds `failOnError` at creation time —
792
+ * a `ContextTracer` exposes methods only; the flag travels with the returned
793
+ * object as internal metadata the runtime reads when a call throws.
794
+ */
795
+ export declare function defineContextTracer(impl: ContextTracer & {
796
+ failOnError?: boolean;
797
+ }): ContextTracer;
777
798
  export declare function createJsonlContextTracer(options: JsonlContextTracerOptions): ContextTracer;
778
799
  /**
779
800
  * Chains hooks in array order: each one sees the previous one's output, so
@@ -782,7 +803,7 @@ export declare function createJsonlContextTracer(options: JsonlContextTracerOpti
782
803
  */
783
804
  export declare function createCompositeAgentHooks<TContext = unknown>(hooks: Array<AgentHooks<TContext> | undefined | null>): AgentHooks<TContext>;
784
805
  export declare function createCompositeContextTracer(tracers: Array<ContextTracer | undefined | null>): ContextTracer;
785
- export declare function createLangSmithContextTracer(options: LangSmithContextTracerOptions): ContextTracer;
806
+ export declare function createLangSmithContextTracer(options?: LangSmithContextTracerOptions): ContextTracer;
786
807
  export declare function skill(input: SkillInput): SkillDefinition;
787
808
  export declare function loadSkill(path: string): Promise<SkillDefinition>;
788
809
  export declare function createMCPTools(client: MCPClient, options?: MCPToolsOptions): Promise<Array<ToolDefinition<Record<string, unknown>>>>;
@@ -796,6 +817,16 @@ export declare function connectMCPStreamableHTTPServer(url: string | URL, option
796
817
  * Writes are serialized through an internal queue, so callers may fire them
797
818
  * without waiting for ordering.
798
819
  */
820
+ /**
821
+ * Entry point for implementing a custom history store. Validates that the
822
+ * required `load`/`append`/`replace` methods exist and binds `failOnError` at
823
+ * creation time — a `HistoryStore` exposes methods only; the flag travels
824
+ * with the returned object as internal metadata the runtime reads when a call
825
+ * throws.
826
+ */
827
+ export declare function defineHistoryStore(impl: HistoryStore & {
828
+ failOnError?: boolean;
829
+ }): HistoryStore;
799
830
  export declare function createJsonlHistoryStore(options: JsonlHistoryStoreOptions): HistoryStore;
800
831
  export declare function teamMember(input: TeamMemberInput): TeamMemberDefinition;
801
832
  export declare function createMemoryMailbox(): TeamMailbox;
@@ -812,7 +843,13 @@ export declare function query<TContext = unknown>(params: AgentOptions<TContext>
812
843
  signal?: AbortSignal;
813
844
  context?: TContext;
814
845
  }): AsyncGenerator<SDKMessage>;
815
- export declare class Agent<TContext = unknown> {
846
+ /**
847
+ * An Agent owns one conversation: history, workspace, and session identity.
848
+ * Create instances with `createAgent()` or `createBareAgent()` (or spawn from
849
+ * an `AgentSpec`) — the constructor is intentionally not exported, because the
850
+ * factories also generate the session id and install workspace tools.
851
+ */
852
+ declare class Agent<TContext = unknown> {
816
853
  private readonly options;
817
854
  private readonly modelClient;
818
855
  private readonly messages;
@@ -833,14 +870,17 @@ export declare class Agent<TContext = unknown> {
833
870
  * "interrupted". Completed turns stay in history; follow up with a new
834
871
  * query() to continue the conversation. Unlike `QueryOptions.signal`, which
835
872
  * terminates the query as an error, an interrupt is normal control flow.
836
- * A no-op when no query is running.
873
+ * Returns `true` when a query was running and is now interrupted, `false`
874
+ * when idle — a `false` tells the host it can send its next query directly
875
+ * instead of waiting for an "interrupted" result.
837
876
  */
838
- interrupt(): void;
877
+ interrupt(): boolean;
839
878
  /**
840
879
  * The store is read once per Agent lifetime, lazily on the first query or
841
880
  * getHistory() call — the constructor cannot be async. A failed load follows
842
- * the same rule as a failed write: swallowed unless `failOnError` is set, in
843
- * which case the error propagates and the Agent starts empty.
881
+ * the same rule as a failed write: swallowed unless `failOnError` was bound
882
+ * by the store's factory, in which case the error propagates and the Agent
883
+ * starts empty.
844
884
  */
845
885
  private ensureHistoryLoaded;
846
886
  private loadHistory;
@@ -854,6 +894,16 @@ export declare class Agent<TContext = unknown> {
854
894
  * mutating the result cannot corrupt the live conversation.
855
895
  */
856
896
  getHistory(): Promise<ModelMessage[]>;
897
+ /**
898
+ * Replace the conversation history. Idle only: throws ConcurrentQueryError
899
+ * while a query is running. When a historyStore is configured the store is
900
+ * replaced too, so persistence stays in sync.
901
+ *
902
+ * The host owns the content: the SDK does not validate the messages, so the
903
+ * replacement must be a well-formed history — e.g. no dangling `tool_use`
904
+ * without its matching `tool_result`.
905
+ */
906
+ replaceHistory(messages: ModelMessage[]): Promise<void>;
857
907
  addTools(tools: Array<ToolDefinition<any, TContext>>): void;
858
908
  private initMessage;
859
909
  private resultMessage;
@@ -878,7 +928,7 @@ export declare class Agent<TContext = unknown> {
878
928
  private executeToolBatch;
879
929
  private isToolCallConcurrencySafe;
880
930
  }
931
+ export type { Agent };
881
932
  type InferInput<TSchema> = TSchema extends {
882
933
  parse(input: unknown): infer TInput;
883
934
  } ? TInput : Record<string, unknown>;
884
- export {};