agent-lattice 0.9.12
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 +652 -0
- package/dist/index.d.ts +529 -0
- package/dist/index.js +7 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,652 @@
|
|
|
1
|
+
# AgentLattice
|
|
2
|
+
|
|
3
|
+
AgentLattice is a TypeScript framework for building coordinated agent systems
|
|
4
|
+
with tools, skills, tracing, supervisor delegation, and mailbox-backed teams.
|
|
5
|
+
|
|
6
|
+
Install it from npm as `agent-lattice`. It works with Anthropic and
|
|
7
|
+
Anthropic-compatible providers such as DeepSeek, without installing the Claude
|
|
8
|
+
Code CLI runtime.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install agent-lattice zod
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Minimal Usage
|
|
17
|
+
|
|
18
|
+
The examples use DeepSeek's Anthropic-compatible endpoint.
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { createAgent } from "agent-lattice";
|
|
22
|
+
|
|
23
|
+
const agent = createAgent({
|
|
24
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
25
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
26
|
+
model: "deepseek-v4-flash",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
for await (const message of agent.query("Say hello")) {
|
|
30
|
+
console.log(message);
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pass `{ stream: false }` to disable model streaming for a query:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
const result = await agent.prompt("Say hello", { stream: false });
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Multimodal Input
|
|
41
|
+
|
|
42
|
+
Pass Anthropic-compatible content blocks for image or document prompts:
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
const result = await agent.prompt([
|
|
46
|
+
{ type: "text", text: "Summarize this screenshot." },
|
|
47
|
+
{
|
|
48
|
+
type: "image",
|
|
49
|
+
source: {
|
|
50
|
+
type: "base64",
|
|
51
|
+
media_type: "image/png",
|
|
52
|
+
data: imageBase64,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
console.log(result.result);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## JSONL Context Tracing
|
|
61
|
+
|
|
62
|
+
Pass a `ContextTracer` to observe an agent run without changing the agent loop.
|
|
63
|
+
The built-in JSONL tracer writes one structured event per line:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
import { createAgent, createJsonlContextTracer } from "agent-lattice";
|
|
67
|
+
|
|
68
|
+
const tracer = createJsonlContextTracer({
|
|
69
|
+
path: ".agent-runs/session.jsonl",
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const agent = createAgent({
|
|
73
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
74
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
75
|
+
model: "deepseek-v4-flash",
|
|
76
|
+
tracer,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
await agent.prompt("Remember that my name is Ada.");
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Each JSONL entry includes `session_id`, `run_id`, `seq`, `source`, `type`, and
|
|
83
|
+
`data`. Agent runs record transcript and context events such as `run_start`,
|
|
84
|
+
`user_message`, `model_request`, `assistant_message`, `tool_use`,
|
|
85
|
+
`tool_result`, and `result`. For team runners, pass the tracer per query to
|
|
86
|
+
propagate it into delegated agents:
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
for await (const event of team.query("Ask engineering to investigate.", {
|
|
90
|
+
tracer,
|
|
91
|
+
})) {
|
|
92
|
+
console.log(event);
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## LangSmith Context Tracing
|
|
97
|
+
|
|
98
|
+
Pass LangSmith's `RunTree` constructor to the SDK tracer. The SDK depends on
|
|
99
|
+
`langsmith` directly and uses its official `RunTree` / `RunTreeConfig` types for
|
|
100
|
+
this adapter.
|
|
101
|
+
|
|
102
|
+
Configure LangSmith with its standard environment variables:
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
LANGSMITH_TRACING=true
|
|
106
|
+
LANGSMITH_ENDPOINT=https://api.smith.langchain.com
|
|
107
|
+
LANGSMITH_API_KEY=<your-langsmith-api-key>
|
|
108
|
+
LANGSMITH_PROJECT=<your-langsmith-project>
|
|
109
|
+
# Required only for org-scoped or multi-workspace API keys.
|
|
110
|
+
LANGSMITH_WORKSPACE_ID=<your-langsmith-workspace-id>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { RunTree } from "langsmith/run_trees";
|
|
115
|
+
import {
|
|
116
|
+
createAgent,
|
|
117
|
+
createCompositeContextTracer,
|
|
118
|
+
createJsonlContextTracer,
|
|
119
|
+
createLangSmithContextTracer,
|
|
120
|
+
} from "agent-lattice";
|
|
121
|
+
|
|
122
|
+
const tracer = createCompositeContextTracer([
|
|
123
|
+
createJsonlContextTracer({ path: ".agent-runs/session.jsonl" }),
|
|
124
|
+
createLangSmithContextTracer({
|
|
125
|
+
RunTree,
|
|
126
|
+
projectName: process.env.LANGSMITH_PROJECT,
|
|
127
|
+
workspaceId: process.env.LANGSMITH_WORKSPACE_ID,
|
|
128
|
+
tags: ["local-debug"],
|
|
129
|
+
}),
|
|
130
|
+
]);
|
|
131
|
+
|
|
132
|
+
const agent = createAgent({
|
|
133
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
134
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
135
|
+
model: "deepseek-v4-flash",
|
|
136
|
+
tracer,
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
If you prefer explicit values over environment variables, pass them to the SDK
|
|
141
|
+
tracer. `workspaceId` is optional and only selects a LangSmith workspace; it is
|
|
142
|
+
not the tracing project name.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { RunTree } from "langsmith/run_trees";
|
|
146
|
+
import { createLangSmithContextTracer } from "agent-lattice";
|
|
147
|
+
|
|
148
|
+
const tracer = createLangSmithContextTracer({
|
|
149
|
+
RunTree,
|
|
150
|
+
apiKey: process.env.LANGSMITH_API_KEY,
|
|
151
|
+
apiUrl: process.env.LANGSMITH_ENDPOINT,
|
|
152
|
+
projectName: process.env.LANGSMITH_PROJECT,
|
|
153
|
+
// Optional: only when LangSmith requires an explicit workspace.
|
|
154
|
+
workspaceId: process.env.LANGSMITH_WORKSPACE_ID,
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
LangSmith receives one root `chain` run per SDK query, child `llm` runs for
|
|
159
|
+
model turns, child `tool` runs for SDK tool calls, and run events for auxiliary
|
|
160
|
+
trace events.
|
|
161
|
+
|
|
162
|
+
Custom sinks can implement the same interface for SQLite, OpenTelemetry, object
|
|
163
|
+
storage, or host-specific observability. The functions below are application
|
|
164
|
+
code you provide, not SDK exports:
|
|
165
|
+
|
|
166
|
+
```ts
|
|
167
|
+
const tracer = {
|
|
168
|
+
async onEvent(event) {
|
|
169
|
+
// TODO: Replace with your own storage/logging code.
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## DeepSeek Anthropic-compatible API
|
|
175
|
+
|
|
176
|
+
DeepSeek exposes an Anthropic-compatible endpoint. Configure `baseURL` and use a
|
|
177
|
+
DeepSeek model name:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
const agent = createAgent({
|
|
181
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
182
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
183
|
+
model: "deepseek-v4-flash",
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Custom Tool
|
|
188
|
+
|
|
189
|
+
```ts
|
|
190
|
+
import { createAgent, tool } from "agent-lattice";
|
|
191
|
+
import { z } from "zod/v4";
|
|
192
|
+
|
|
193
|
+
const agent = createAgent({
|
|
194
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
195
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
196
|
+
model: "deepseek-v4-flash",
|
|
197
|
+
tools: [
|
|
198
|
+
tool(
|
|
199
|
+
"calculator",
|
|
200
|
+
"Evaluate a simple arithmetic expression",
|
|
201
|
+
z.object({ expr: z.string() }),
|
|
202
|
+
async input => ({ content: String(Function(`return ${input.expr}`)()) }),
|
|
203
|
+
),
|
|
204
|
+
],
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
const result = await agent.prompt("What is 2+2?");
|
|
208
|
+
console.log(result.result);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Permission Callback
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
const agent = createAgent({
|
|
215
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
216
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
217
|
+
model: "deepseek-v4-flash",
|
|
218
|
+
tools: [dangerousTool],
|
|
219
|
+
permission: async request => {
|
|
220
|
+
if (request.toolName === "danger") {
|
|
221
|
+
return { behavior: "deny", message: "Blocked by policy" };
|
|
222
|
+
}
|
|
223
|
+
return { behavior: "allow" };
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Denied tools are returned to Claude as error `tool_result` blocks so the model
|
|
229
|
+
can explain or choose another path.
|
|
230
|
+
|
|
231
|
+
## Skills
|
|
232
|
+
|
|
233
|
+
Skills are reusable instruction bundles. They are lighter than Claude Code
|
|
234
|
+
runtime plugins: the SDK reads skill instructions and injects matching skills
|
|
235
|
+
into the model request, but it does not depend on the Claude Code runtime.
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
import { createAgent, loadSkill, skill } from "agent-lattice";
|
|
239
|
+
|
|
240
|
+
const codeReview = skill({
|
|
241
|
+
name: "code-review",
|
|
242
|
+
description: "Review code changes and pull requests",
|
|
243
|
+
instructions: "Always list bugs and risks before summaries.",
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
const pdf = await loadSkill("./skills/pdf");
|
|
247
|
+
|
|
248
|
+
const agent = createAgent({
|
|
249
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
250
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
251
|
+
model: "deepseek-v4-flash",
|
|
252
|
+
skills: [codeReview, pdf],
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
`loadSkill(path)` expects a `SKILL.md` file:
|
|
257
|
+
|
|
258
|
+
```md
|
|
259
|
+
---
|
|
260
|
+
name: pdf
|
|
261
|
+
description: Read and inspect PDF documents
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
Render pages before claiming layout is correct.
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
## MCP Tools
|
|
268
|
+
|
|
269
|
+
The SDK can expose MCP server tools as agent tools. The first version supports
|
|
270
|
+
stdio MCP servers, remote Streamable HTTP servers, OAuth providers, and a
|
|
271
|
+
generic `MCPClient` adapter.
|
|
272
|
+
|
|
273
|
+
```ts
|
|
274
|
+
import {
|
|
275
|
+
connectMCPStdioServer,
|
|
276
|
+
createAgent,
|
|
277
|
+
} from "agent-lattice";
|
|
278
|
+
|
|
279
|
+
const mcp = await connectMCPStdioServer(
|
|
280
|
+
{
|
|
281
|
+
command: "node",
|
|
282
|
+
args: ["./mcp-server.js"],
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
namePrefix: "docs",
|
|
286
|
+
},
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
const agent = createAgent({
|
|
290
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
291
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
292
|
+
model: "deepseek-v4-flash",
|
|
293
|
+
tools: mcp.tools,
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const result = await agent.prompt("Search the docs for installation steps.");
|
|
298
|
+
console.log(result.result);
|
|
299
|
+
} finally {
|
|
300
|
+
await mcp.close();
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Use `createMCPTools(client)` if your host application already manages an MCP
|
|
305
|
+
client connection.
|
|
306
|
+
|
|
307
|
+
Connect a remote Streamable HTTP MCP server:
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import {
|
|
311
|
+
connectMCPStreamableHTTPServer,
|
|
312
|
+
createAgent,
|
|
313
|
+
} from "agent-lattice";
|
|
314
|
+
|
|
315
|
+
const mcp = await connectMCPStreamableHTTPServer("https://mcp.example.com/mcp", {
|
|
316
|
+
namePrefix: "remote",
|
|
317
|
+
requestInit: {
|
|
318
|
+
headers: {
|
|
319
|
+
"X-Workspace": "demo",
|
|
320
|
+
},
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const agent = createAgent({
|
|
325
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
326
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
327
|
+
model: "deepseek-v4-flash",
|
|
328
|
+
tools: mcp.tools,
|
|
329
|
+
});
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Pass an official MCP `OAuthClientProvider` when the remote server requires OAuth:
|
|
333
|
+
|
|
334
|
+
```ts
|
|
335
|
+
const mcp = await connectMCPStreamableHTTPServer("https://mcp.example.com/mcp", {
|
|
336
|
+
authProvider,
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## AgentLike Composition
|
|
341
|
+
|
|
342
|
+
`Agent` and `Team` satisfy the same `AgentLike` shape:
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
type AgentLike = {
|
|
346
|
+
query(prompt, options?): AsyncGenerator<SDKMessage | TeamRunnerMessage>;
|
|
347
|
+
prompt(prompt, options?): Promise<SDKResultMessage>;
|
|
348
|
+
};
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
That means a team can be used anywhere a callable agent is expected. From the
|
|
352
|
+
outside, a team is an agent; inside, it can contain a whole organization.
|
|
353
|
+
|
|
354
|
+
## Team Mailbox Collaboration
|
|
355
|
+
|
|
356
|
+
Use `createTeam()` when you want to talk to one `AgentLike` while it coordinates
|
|
357
|
+
with named members internally. The team automatically injects member
|
|
358
|
+
`agentTool()` tools and drives the mailbox runtime when you call `team.query()`
|
|
359
|
+
or `team.prompt()`.
|
|
360
|
+
|
|
361
|
+
```ts
|
|
362
|
+
import {
|
|
363
|
+
createAgent,
|
|
364
|
+
createMemoryMailbox,
|
|
365
|
+
createTeam,
|
|
366
|
+
teamMember,
|
|
367
|
+
} from "agent-lattice";
|
|
368
|
+
|
|
369
|
+
const researcher = createAgent({
|
|
370
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
371
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
372
|
+
model: "deepseek-v4-flash",
|
|
373
|
+
systemPrompt: "You research agent SDK architecture and report concise findings.",
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
const team = createTeam({
|
|
377
|
+
name: "engineering",
|
|
378
|
+
lead: createAgent({
|
|
379
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
380
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
381
|
+
model: "deepseek-v4-flash",
|
|
382
|
+
systemPrompt: "You lead engineering work. Delegate research tasks to researcher.",
|
|
383
|
+
}),
|
|
384
|
+
members: [
|
|
385
|
+
teamMember({
|
|
386
|
+
name: "researcher",
|
|
387
|
+
role: "executor",
|
|
388
|
+
focus: "Research agent architecture",
|
|
389
|
+
agent: researcher,
|
|
390
|
+
}),
|
|
391
|
+
],
|
|
392
|
+
mailbox: createMemoryMailbox(),
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
for await (const event of team.query("Ask the researcher to inspect the SDK design.")) {
|
|
396
|
+
console.log(event);
|
|
397
|
+
}
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
`team.query()` streams both the lead agent's normal SDK messages and team
|
|
401
|
+
runtime events such as `team_message`, `team_agent`, and nested `agent_message`
|
|
402
|
+
events. `team.prompt()` consumes that stream and returns only the final result.
|
|
403
|
+
|
|
404
|
+
`createTeam()` injects member AgentLike tools into the lead. Those tools expose
|
|
405
|
+
an explicit action contract: `mode: "ask"` waits for the member result,
|
|
406
|
+
`mode: "handoff"` returns an acceptance receipt to the lead while the team
|
|
407
|
+
runtime continues the accepted mailbox work, and `mode: "observe"` reports
|
|
408
|
+
unsupported unless a host runtime provides observation support. In the default
|
|
409
|
+
`team.query()` and `team.prompt()` path, a handoff receipt is not the final
|
|
410
|
+
delivery: the runtime waits for the member's upstream reply, feeds it back to
|
|
411
|
+
the lead, and keeps going until the root lead returns the final result or the
|
|
412
|
+
run terminates.
|
|
413
|
+
|
|
414
|
+
Team member tools can also request explicit shared workspace write grants:
|
|
415
|
+
|
|
416
|
+
```ts
|
|
417
|
+
for await (const event of team.query(
|
|
418
|
+
"Ask backend to implement the API in the shared repo.",
|
|
419
|
+
{
|
|
420
|
+
permissions: {
|
|
421
|
+
workspaceGrants: [{
|
|
422
|
+
root: "/work/shared/txt-notebook-app",
|
|
423
|
+
access: ["write"],
|
|
424
|
+
reason: "Project shared workspace",
|
|
425
|
+
}],
|
|
426
|
+
},
|
|
427
|
+
},
|
|
428
|
+
)) {
|
|
429
|
+
console.log(event);
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
When the lead calls a member tool, it may include `workspaceGrants` scoped to
|
|
434
|
+
that member, for example `/work/shared/txt-notebook-app/backend`. The runtime
|
|
435
|
+
only accepts write grants that are covered by the caller's current permissions. The
|
|
436
|
+
accepted grants are written to mailbox metadata, included in the child agent's
|
|
437
|
+
task/system context, and enforced by the built-in write tools. Read-only tools
|
|
438
|
+
such as `Read`, `LS`, `Glob`, and `Grep` can inspect any path the host process
|
|
439
|
+
can read and do not require workspace grants. If a write tool is denied, the
|
|
440
|
+
model receives a structured `permission_denied` tool result with the requested
|
|
441
|
+
path, allowed roots, and a deterministic suggested next step.
|
|
442
|
+
Grant `access` values are operation categories, not tool names; `write` covers
|
|
443
|
+
`Write`, `Edit`, and obvious Bash writes.
|
|
444
|
+
|
|
445
|
+
Managers should choose one workspace strategy explicitly when delegating:
|
|
446
|
+
ask the member to write deliverables in its own private workspace and report
|
|
447
|
+
paths, or provide `workspaceGrants: [{ root, access: ["write"], reason }]` for
|
|
448
|
+
every shared or manager-owned root named as a write destination.
|
|
449
|
+
|
|
450
|
+
Advanced mailbox controls remain available through `team.send()`,
|
|
451
|
+
`team.drain()`, and `team.mailbox`. Member agents that can accept tools receive
|
|
452
|
+
`team_send`, `team_inbox`, `team_read`, `team_reply`, `team_followup`, and
|
|
453
|
+
`team_status` so they can process assigned mailbox work. The lead does not
|
|
454
|
+
receive raw mailbox tools by default; pass `exposeLeadMailboxTools: true` only
|
|
455
|
+
when the lead should manually operate the team mailbox.
|
|
456
|
+
|
|
457
|
+
For durable local storage, pass a SQLite-like database. `better-sqlite3` works
|
|
458
|
+
without the SDK taking a hard dependency on it:
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import Database from "better-sqlite3";
|
|
462
|
+
import {
|
|
463
|
+
createAgent,
|
|
464
|
+
createSQLiteMailbox,
|
|
465
|
+
createTeam,
|
|
466
|
+
} from "agent-lattice";
|
|
467
|
+
|
|
468
|
+
const mailbox = createSQLiteMailbox({
|
|
469
|
+
database: new Database("team-mailbox.db"),
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
const team = createTeam({
|
|
473
|
+
name: "engineering",
|
|
474
|
+
lead: createAgent({
|
|
475
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
476
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
477
|
+
model: "deepseek-v4-flash",
|
|
478
|
+
}),
|
|
479
|
+
members: [],
|
|
480
|
+
mailbox,
|
|
481
|
+
});
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Hosts can also provide their own `TeamMailbox` adapter for Redis, Cloudflare D1,
|
|
485
|
+
Durable Objects, or another queue/storage backend.
|
|
486
|
+
|
|
487
|
+
### Nested teams
|
|
488
|
+
|
|
489
|
+
Because `teamMember().agent` accepts any `AgentLike`, a `Team` can be a member
|
|
490
|
+
of another `Team`:
|
|
491
|
+
|
|
492
|
+
```ts
|
|
493
|
+
import {
|
|
494
|
+
createAgent,
|
|
495
|
+
createTeam,
|
|
496
|
+
teamMember,
|
|
497
|
+
} from "agent-lattice";
|
|
498
|
+
|
|
499
|
+
const createDeepSeekAgent = (systemPrompt: string) => createAgent({
|
|
500
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
501
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
502
|
+
model: "deepseek-v4-flash",
|
|
503
|
+
systemPrompt,
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
const ceoAgent = createDeepSeekAgent(
|
|
507
|
+
[
|
|
508
|
+
"You are the CEO agent.",
|
|
509
|
+
"Clarify product goals, decide which department owns the work, and ask for concise progress reports.",
|
|
510
|
+
"Do not implement engineering details yourself.",
|
|
511
|
+
].join("\n"),
|
|
512
|
+
);
|
|
513
|
+
const engineeringHeadAgent = createDeepSeekAgent(
|
|
514
|
+
[
|
|
515
|
+
"You are the engineering head agent.",
|
|
516
|
+
"Break engineering goals into backend and frontend work, route tasks to the right executor, and report outcomes upstream.",
|
|
517
|
+
"Keep architecture decisions explicit.",
|
|
518
|
+
].join("\n"),
|
|
519
|
+
);
|
|
520
|
+
const backendAgent = createDeepSeekAgent(
|
|
521
|
+
[
|
|
522
|
+
"You are the backend executor agent.",
|
|
523
|
+
"Handle APIs, data models, storage, integrations, and server-side correctness.",
|
|
524
|
+
"Escalate product or UI decisions instead of guessing.",
|
|
525
|
+
].join("\n"),
|
|
526
|
+
);
|
|
527
|
+
const frontendAgent = createDeepSeekAgent(
|
|
528
|
+
[
|
|
529
|
+
"You are the frontend executor agent.",
|
|
530
|
+
"Handle UI flows, client state, accessibility, and browser behavior.",
|
|
531
|
+
"Escalate API contract questions instead of inventing them.",
|
|
532
|
+
].join("\n"),
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
const engineeringTeam = createTeam({
|
|
536
|
+
name: "engineering",
|
|
537
|
+
lead: engineeringHeadAgent,
|
|
538
|
+
members: [
|
|
539
|
+
teamMember({ name: "backend", role: "executor", agent: backendAgent }),
|
|
540
|
+
teamMember({ name: "frontend", role: "executor", agent: frontendAgent }),
|
|
541
|
+
],
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const companyTeam = createTeam({
|
|
545
|
+
name: "company",
|
|
546
|
+
lead: ceoAgent,
|
|
547
|
+
members: [
|
|
548
|
+
teamMember({
|
|
549
|
+
name: "engineering",
|
|
550
|
+
role: "head",
|
|
551
|
+
focus: "Own engineering delivery",
|
|
552
|
+
agent: engineeringTeam,
|
|
553
|
+
}),
|
|
554
|
+
],
|
|
555
|
+
});
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Use this pattern to model CEO -> Head Team -> Executor Agent without hard-coding
|
|
559
|
+
that hierarchy into the SDK.
|
|
560
|
+
|
|
561
|
+
### Routing loops
|
|
562
|
+
|
|
563
|
+
The SDK does not block routing loops by default. A task can move from a manager
|
|
564
|
+
to a member, back to the manager for context, and then back to the same member.
|
|
565
|
+
That is normal organizational flow, not necessarily a runtime error.
|
|
566
|
+
|
|
567
|
+
Use `maxTurns`, permission callbacks, mailbox status, and host-level monitoring
|
|
568
|
+
to control cost and risk. If your application needs a strict hierarchy, expose
|
|
569
|
+
only the allowed members at each layer and enforce routing with permission
|
|
570
|
+
callbacks or a host-level policy.
|
|
571
|
+
|
|
572
|
+
### Team runtime drain
|
|
573
|
+
|
|
574
|
+
Mailbox routing is explicit: a pending message belongs to its `to` mailbox and
|
|
575
|
+
must be handled by that member's agent. `claimNext(mailboxId)` only claims one
|
|
576
|
+
pending message for that mailbox and marks it `processing`.
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
const message = await team.mailbox.claimNext("engineering::researcher");
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
Use `team.drain()` to let the runtime advance already-routed work:
|
|
583
|
+
|
|
584
|
+
```ts
|
|
585
|
+
const result = await team.drain({
|
|
586
|
+
maxRounds: 5,
|
|
587
|
+
maxMessages: 20,
|
|
588
|
+
});
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
`drain()` iterates members, claims pending messages from each member's own
|
|
592
|
+
mailbox, and prompts that member agent. It does not re-route work. The member
|
|
593
|
+
must call `team_reply` for a final result or `team_followup` for progress. If a
|
|
594
|
+
member ends without either, the runtime marks the original message `failed` and
|
|
595
|
+
sends a diagnostic follow-up to the upstream mailbox.
|
|
596
|
+
|
|
597
|
+
## Agent Workspace Tools
|
|
598
|
+
|
|
599
|
+
AgentLattice includes an opt-in set of workspace tools:
|
|
600
|
+
|
|
601
|
+
- `Read`
|
|
602
|
+
- `Write`
|
|
603
|
+
- `Edit`
|
|
604
|
+
- `LS`
|
|
605
|
+
- `Glob`
|
|
606
|
+
- `Grep`
|
|
607
|
+
- `Bash`
|
|
608
|
+
|
|
609
|
+
```ts
|
|
610
|
+
import { createAgent, createAgentWorkspaceTools } from "agent-lattice";
|
|
611
|
+
|
|
612
|
+
const agent = createAgent({
|
|
613
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
614
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
615
|
+
model: "deepseek-v4-flash",
|
|
616
|
+
tools: createAgentWorkspaceTools({
|
|
617
|
+
cwd: process.cwd(),
|
|
618
|
+
allowedDirectories: [process.cwd()],
|
|
619
|
+
}),
|
|
620
|
+
permission: async request => {
|
|
621
|
+
if (request.toolName === "Bash" || request.toolName === "Write" || request.toolName === "Edit") {
|
|
622
|
+
return { behavior: "deny", message: "This host did not approve write or shell access." };
|
|
623
|
+
}
|
|
624
|
+
return { behavior: "allow" };
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
These tools are not enabled by default. `Read`, `LS`, `Glob`, and `Grep` are
|
|
630
|
+
read-only observation tools and are not gated by workspace grants. `Write`,
|
|
631
|
+
`Edit`, and obvious Bash writes are gated to the configured workspace roots and
|
|
632
|
+
task-scoped shared workspace grants, so production hosts should pair write and
|
|
633
|
+
shell access with a permission callback. Shell redirects to `/dev/null` are
|
|
634
|
+
treated as discard targets, not workspace writes.
|
|
635
|
+
|
|
636
|
+
## Multi-turn Session
|
|
637
|
+
|
|
638
|
+
```ts
|
|
639
|
+
const agent = createAgent({
|
|
640
|
+
apiKey: process.env.DEEPSEEK_API_KEY,
|
|
641
|
+
baseURL: "https://api.deepseek.com/anthropic",
|
|
642
|
+
model: "deepseek-v4-flash",
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
await agent.prompt("My name is Ada.");
|
|
646
|
+
const result = await agent.prompt("What is my name?");
|
|
647
|
+
console.log(result.result);
|
|
648
|
+
```
|
|
649
|
+
|
|
650
|
+
The SDK stores conversation state in memory for the lifetime of the `Agent`
|
|
651
|
+
instance. Persistent transcripts and resume support are intentionally out of
|
|
652
|
+
scope for the first release.
|