deepagents 1.4.2 → 1.5.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/dist/index.cjs CHANGED
@@ -31,8 +31,10 @@ let zod_v4 = require("zod/v4");
31
31
  let micromatch = require("micromatch");
32
32
  micromatch = __toESM(micromatch);
33
33
  let path = require("path");
34
- let zod_v3 = require("zod/v3");
35
34
  let _langchain_core_messages = require("@langchain/core/messages");
35
+ let zod = require("zod");
36
+ let yaml = require("yaml");
37
+ yaml = __toESM(yaml);
36
38
  let node_fs_promises = require("node:fs/promises");
37
39
  node_fs_promises = __toESM(node_fs_promises);
38
40
  let node_fs = require("node:fs");
@@ -44,9 +46,6 @@ let fast_glob = require("fast-glob");
44
46
  fast_glob = __toESM(fast_glob);
45
47
  let node_os = require("node:os");
46
48
  node_os = __toESM(node_os);
47
- let zod = require("zod");
48
- let yaml = require("yaml");
49
- yaml = __toESM(yaml);
50
49
 
51
50
  //#region src/backends/protocol.ts
52
51
  /**
@@ -362,7 +361,7 @@ var StateBackend = class {
362
361
  * @param limit - Maximum number of lines to read
363
362
  * @returns Formatted file content with line numbers, or error message
364
363
  */
365
- read(filePath, offset = 0, limit = 2e3) {
364
+ read(filePath, offset = 0, limit = 500) {
366
365
  const fileData = this.getFiles()[filePath];
367
366
  if (!fileData) return `Error: File '${filePath}' not found`;
368
367
  return formatReadResponse(fileData, offset, limit);
@@ -619,7 +618,7 @@ function createReadFileTool(backend, options) {
619
618
  state: (0, _langchain_langgraph.getCurrentTaskInput)(config),
620
619
  store: config.store
621
620
  });
622
- const { file_path, offset = 0, limit = 2e3 } = input;
621
+ const { file_path, offset = 0, limit = 500 } = input;
623
622
  return await resolvedBackend.read(file_path, offset, limit);
624
623
  }, {
625
624
  name: "read_file",
@@ -627,7 +626,7 @@ function createReadFileTool(backend, options) {
627
626
  schema: zod_v4.z.object({
628
627
  file_path: zod_v4.z.string().describe("Absolute path to the file to read"),
629
628
  offset: zod_v4.z.coerce.number().optional().default(0).describe("Line offset to start reading from (0-indexed)"),
630
- limit: zod_v4.z.coerce.number().optional().default(2e3).describe("Maximum number of lines to read")
629
+ limit: zod_v4.z.coerce.number().optional().default(500).describe("Maximum number of lines to read")
631
630
  })
632
631
  });
633
632
  }
@@ -883,7 +882,7 @@ const DEFAULT_SUBAGENT_PROMPT = "In order to complete the objective that the use
883
882
  const EXCLUDED_STATE_KEYS = [
884
883
  "messages",
885
884
  "todos",
886
- "jumpTo",
885
+ "structuredResponse",
887
886
  "files"
888
887
  ];
889
888
  const DEFAULT_GENERAL_PURPOSE_DESCRIPTION = "General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.";
@@ -1118,9 +1117,9 @@ function createTaskTool(options) {
1118
1117
  }, {
1119
1118
  name: "task",
1120
1119
  description: taskDescription ? taskDescription : getTaskToolDescription(subagentDescriptions),
1121
- schema: zod_v3.z.object({
1122
- description: zod_v3.z.string().describe("The task to execute with the selected agent"),
1123
- subagent_type: zod_v3.z.string().describe(`Name of the agent to use. Available: ${Object.keys(subagentGraphs).join(", ")}`)
1120
+ schema: zod_v4.z.object({
1121
+ description: zod_v4.z.string().describe("The task to execute with the selected agent"),
1122
+ subagent_type: zod_v4.z.string().describe(`Name of the agent to use. Available: ${Object.keys(subagentGraphs).join(", ")}`)
1124
1123
  })
1125
1124
  });
1126
1125
  }
@@ -1198,6 +1197,514 @@ function createPatchToolCallsMiddleware() {
1198
1197
  });
1199
1198
  }
1200
1199
 
1200
+ //#endregion
1201
+ //#region src/middleware/memory.ts
1202
+ /**
1203
+ * Middleware for loading agent memory/context from AGENTS.md files.
1204
+ *
1205
+ * This module implements support for the AGENTS.md specification (https://agents.md/),
1206
+ * loading memory/context from configurable sources and injecting into the system prompt.
1207
+ *
1208
+ * ## Overview
1209
+ *
1210
+ * AGENTS.md files provide project-specific context and instructions to help AI agents
1211
+ * work effectively. Unlike skills (which are on-demand workflows), memory is always
1212
+ * loaded and provides persistent context.
1213
+ *
1214
+ * ## Usage
1215
+ *
1216
+ * ```typescript
1217
+ * import { createMemoryMiddleware } from "@anthropic/deepagents";
1218
+ * import { FilesystemBackend } from "@anthropic/deepagents";
1219
+ *
1220
+ * // Security: FilesystemBackend allows reading/writing from the entire filesystem.
1221
+ * // Either ensure the agent is running within a sandbox OR add human-in-the-loop (HIL)
1222
+ * // approval to file operations.
1223
+ * const backend = new FilesystemBackend({ rootDir: "/" });
1224
+ *
1225
+ * const middleware = createMemoryMiddleware({
1226
+ * backend,
1227
+ * sources: [
1228
+ * "~/.deepagents/AGENTS.md",
1229
+ * "./.deepagents/AGENTS.md",
1230
+ * ],
1231
+ * });
1232
+ *
1233
+ * const agent = createDeepAgent({ middleware: [middleware] });
1234
+ * ```
1235
+ *
1236
+ * ## Memory Sources
1237
+ *
1238
+ * Sources are simply paths to AGENTS.md files that are loaded in order and combined.
1239
+ * Multiple sources are concatenated in order, with all content included.
1240
+ * Later sources appear after earlier ones in the combined prompt.
1241
+ *
1242
+ * ## File Format
1243
+ *
1244
+ * AGENTS.md files are standard Markdown with no required structure.
1245
+ * Common sections include:
1246
+ * - Project overview
1247
+ * - Build/test commands
1248
+ * - Code style guidelines
1249
+ * - Architecture notes
1250
+ */
1251
+ /**
1252
+ * State schema for memory middleware.
1253
+ */
1254
+ const MemoryStateSchema = zod.z.object({ memoryContents: zod.z.record(zod.z.string(), zod.z.string()).optional() });
1255
+ /**
1256
+ * Default system prompt template for memory.
1257
+ * Ported from Python's comprehensive memory guidelines.
1258
+ */
1259
+ const MEMORY_SYSTEM_PROMPT = `<agent_memory>
1260
+ {memory_contents}
1261
+ </agent_memory>
1262
+
1263
+ <memory_guidelines>
1264
+ The above <agent_memory> was loaded in from files in your filesystem. As you learn from your interactions with the user, you can save new knowledge by calling the \`edit_file\` tool.
1265
+
1266
+ **Learning from feedback:**
1267
+ - One of your MAIN PRIORITIES is to learn from your interactions with the user. These learnings can be implicit or explicit. This means that in the future, you will remember this important information.
1268
+ - When you need to remember something, updating memory must be your FIRST, IMMEDIATE action - before responding to the user, before calling other tools, before doing anything else. Just update memory immediately.
1269
+ - When user says something is better/worse, capture WHY and encode it as a pattern.
1270
+ - Each correction is a chance to improve permanently - don't just fix the immediate issue, update your instructions.
1271
+ - A great opportunity to update your memories is when the user interrupts a tool call and provides feedback. You should update your memories immediately before revising the tool call.
1272
+ - Look for the underlying principle behind corrections, not just the specific mistake.
1273
+ - The user might not explicitly ask you to remember something, but if they provide information that is useful for future use, you should update your memories immediately.
1274
+
1275
+ **Asking for information:**
1276
+ - If you lack context to perform an action (e.g. send a Slack DM, requires a user ID/email) you should explicitly ask the user for this information.
1277
+ - It is preferred for you to ask for information, don't assume anything that you do not know!
1278
+ - When the user provides information that is useful for future use, you should update your memories immediately.
1279
+
1280
+ **When to update memories:**
1281
+ - When the user explicitly asks you to remember something (e.g., "remember my email", "save this preference")
1282
+ - When the user describes your role or how you should behave (e.g., "you are a web researcher", "always do X")
1283
+ - When the user gives feedback on your work - capture what was wrong and how to improve
1284
+ - When the user provides information required for tool use (e.g., slack channel ID, email addresses)
1285
+ - When the user provides context useful for future tasks, such as how to use tools, or which actions to take in a particular situation
1286
+ - When you discover new patterns or preferences (coding styles, conventions, workflows)
1287
+
1288
+ **When to NOT update memories:**
1289
+ - When the information is temporary or transient (e.g., "I'm running late", "I'm on my phone right now")
1290
+ - When the information is a one-time task request (e.g., "Find me a recipe", "What's 25 * 4?")
1291
+ - When the information is a simple question that doesn't reveal lasting preferences (e.g., "What day is it?", "Can you explain X?")
1292
+ - When the information is an acknowledgment or small talk (e.g., "Sounds good!", "Hello", "Thanks for that")
1293
+ - When the information is stale or irrelevant in future conversations
1294
+ - Never store API keys, access tokens, passwords, or any other credentials in any file, memory, or system prompt.
1295
+ - If the user asks where to put API keys or provides an API key, do NOT echo or save it.
1296
+
1297
+ **Examples:**
1298
+ Example 1 (remembering user information):
1299
+ User: Can you connect to my google account?
1300
+ Agent: Sure, I'll connect to your google account, what's your google account email?
1301
+ User: john@example.com
1302
+ Agent: Let me save this to my memory.
1303
+ Tool Call: edit_file(...) -> remembers that the user's google account email is john@example.com
1304
+
1305
+ Example 2 (remembering implicit user preferences):
1306
+ User: Can you write me an example for creating a deep agent in LangChain?
1307
+ Agent: Sure, I'll write you an example for creating a deep agent in LangChain <example code in Python>
1308
+ User: Can you do this in JavaScript
1309
+ Agent: Let me save this to my memory.
1310
+ Tool Call: edit_file(...) -> remembers that the user prefers to get LangChain code examples in JavaScript
1311
+ Agent: Sure, here is the JavaScript example<example code in JavaScript>
1312
+
1313
+ Example 3 (do not remember transient information):
1314
+ User: I'm going to play basketball tonight so I will be offline for a few hours.
1315
+ Agent: Okay I'll add a block to your calendar.
1316
+ Tool Call: create_calendar_event(...) -> just calls a tool, does not commit anything to memory, as it is transient information
1317
+ </memory_guidelines>`;
1318
+ /**
1319
+ * Format loaded memory contents for injection into prompt.
1320
+ * Pairs memory locations with their contents for clarity.
1321
+ */
1322
+ function formatMemoryContents(contents, sources) {
1323
+ if (Object.keys(contents).length === 0) return "(No memory loaded)";
1324
+ const sections = [];
1325
+ for (const path$4 of sources) if (contents[path$4]) sections.push(`${path$4}\n${contents[path$4]}`);
1326
+ if (sections.length === 0) return "(No memory loaded)";
1327
+ return sections.join("\n\n");
1328
+ }
1329
+ /**
1330
+ * Load memory content from a backend path.
1331
+ *
1332
+ * @param backend - Backend to load from.
1333
+ * @param path - Path to the AGENTS.md file.
1334
+ * @returns File content if found, null otherwise.
1335
+ */
1336
+ async function loadMemoryFromBackend(backend, path$4) {
1337
+ if (!backend.downloadFiles) {
1338
+ const content = await backend.read(path$4);
1339
+ if (content.startsWith("Error:")) return null;
1340
+ return content;
1341
+ }
1342
+ const results = await backend.downloadFiles([path$4]);
1343
+ if (results.length !== 1) throw new Error(`Expected 1 response for path ${path$4}, got ${results.length}`);
1344
+ const response = results[0];
1345
+ if (response.error != null) {
1346
+ if (response.error === "file_not_found") return null;
1347
+ throw new Error(`Failed to download ${path$4}: ${response.error}`);
1348
+ }
1349
+ if (response.content != null) return new TextDecoder().decode(response.content);
1350
+ return null;
1351
+ }
1352
+ /**
1353
+ * Create middleware for loading agent memory from AGENTS.md files.
1354
+ *
1355
+ * Loads memory content from configured sources and injects into the system prompt.
1356
+ * Supports multiple sources that are combined together.
1357
+ *
1358
+ * @param options - Configuration options
1359
+ * @returns AgentMiddleware for memory loading and injection
1360
+ *
1361
+ * @example
1362
+ * ```typescript
1363
+ * const middleware = createMemoryMiddleware({
1364
+ * backend: new FilesystemBackend({ rootDir: "/" }),
1365
+ * sources: [
1366
+ * "~/.deepagents/AGENTS.md",
1367
+ * "./.deepagents/AGENTS.md",
1368
+ * ],
1369
+ * });
1370
+ * ```
1371
+ */
1372
+ function createMemoryMiddleware(options) {
1373
+ const { backend, sources } = options;
1374
+ /**
1375
+ * Resolve backend from instance or factory.
1376
+ */
1377
+ function getBackend$1(state) {
1378
+ if (typeof backend === "function") return backend({ state });
1379
+ return backend;
1380
+ }
1381
+ return (0, langchain.createMiddleware)({
1382
+ name: "MemoryMiddleware",
1383
+ stateSchema: MemoryStateSchema,
1384
+ async beforeAgent(state) {
1385
+ if ("memoryContents" in state && state.memoryContents != null) return;
1386
+ const resolvedBackend = getBackend$1(state);
1387
+ const contents = {};
1388
+ for (const path$4 of sources) try {
1389
+ const content = await loadMemoryFromBackend(resolvedBackend, path$4);
1390
+ if (content) contents[path$4] = content;
1391
+ } catch (error) {
1392
+ console.debug(`Failed to load memory from ${path$4}:`, error);
1393
+ }
1394
+ return { memoryContents: contents };
1395
+ },
1396
+ wrapModelCall(request, handler) {
1397
+ const formattedContents = formatMemoryContents(request.state?.memoryContents || {}, sources);
1398
+ const memorySection = MEMORY_SYSTEM_PROMPT.replace("{memory_contents}", formattedContents);
1399
+ const currentSystemPrompt = request.systemPrompt || "";
1400
+ const newSystemPrompt = currentSystemPrompt ? `${memorySection}\n\n${currentSystemPrompt}` : memorySection;
1401
+ return handler({
1402
+ ...request,
1403
+ systemPrompt: newSystemPrompt
1404
+ });
1405
+ }
1406
+ });
1407
+ }
1408
+
1409
+ //#endregion
1410
+ //#region src/middleware/skills.ts
1411
+ /**
1412
+ * Backend-agnostic skills middleware for loading agent skills from any backend.
1413
+ *
1414
+ * This middleware implements Anthropic's agent skills pattern with progressive disclosure,
1415
+ * loading skills from backend storage via configurable sources.
1416
+ *
1417
+ * ## Architecture
1418
+ *
1419
+ * Skills are loaded from one or more **sources** - paths in a backend where skills are
1420
+ * organized. Sources are loaded in order, with later sources overriding earlier ones
1421
+ * when skills have the same name (last one wins). This enables layering: base -> user
1422
+ * -> project -> team skills.
1423
+ *
1424
+ * The middleware uses backend APIs exclusively (no direct filesystem access), making it
1425
+ * portable across different storage backends (filesystem, state, remote storage, etc.).
1426
+ *
1427
+ * ## Usage
1428
+ *
1429
+ * ```typescript
1430
+ * import { createSkillsMiddleware, FilesystemBackend } from "@anthropic/deepagents";
1431
+ *
1432
+ * const middleware = createSkillsMiddleware({
1433
+ * backend: new FilesystemBackend({ rootDir: "/" }),
1434
+ * sources: [
1435
+ * "/skills/user/",
1436
+ * "/skills/project/",
1437
+ * ],
1438
+ * });
1439
+ *
1440
+ * const agent = createDeepAgent({ middleware: [middleware] });
1441
+ * ```
1442
+ *
1443
+ * Or use the `skills` parameter on createDeepAgent:
1444
+ *
1445
+ * ```typescript
1446
+ * const agent = createDeepAgent({
1447
+ * skills: ["/skills/user/", "/skills/project/"],
1448
+ * });
1449
+ * ```
1450
+ */
1451
+ const MAX_SKILL_FILE_SIZE = 10 * 1024 * 1024;
1452
+ const MAX_SKILL_NAME_LENGTH = 64;
1453
+ const MAX_SKILL_DESCRIPTION_LENGTH = 1024;
1454
+ /**
1455
+ * State schema for skills middleware.
1456
+ */
1457
+ const SkillsStateSchema = zod.z.object({ skillsMetadata: zod.z.array(zod.z.object({
1458
+ name: zod.z.string(),
1459
+ description: zod.z.string(),
1460
+ path: zod.z.string(),
1461
+ license: zod.z.string().nullable().optional(),
1462
+ compatibility: zod.z.string().nullable().optional(),
1463
+ metadata: zod.z.record(zod.z.string(), zod.z.string()).optional(),
1464
+ allowedTools: zod.z.array(zod.z.string()).optional()
1465
+ })).optional() });
1466
+ /**
1467
+ * Skills System Documentation prompt template.
1468
+ */
1469
+ const SKILLS_SYSTEM_PROMPT = `
1470
+ ## Skills System
1471
+
1472
+ You have access to a skills library that provides specialized capabilities and domain knowledge.
1473
+
1474
+ {skills_locations}
1475
+
1476
+ **Available Skills:**
1477
+
1478
+ {skills_list}
1479
+
1480
+ **How to Use Skills (Progressive Disclosure):**
1481
+
1482
+ Skills follow a **progressive disclosure** pattern - you know they exist (name + description above), but you only read the full instructions when needed:
1483
+
1484
+ 1. **Recognize when a skill applies**: Check if the user's task matches any skill's description
1485
+ 2. **Read the skill's full instructions**: The skill list above shows the exact path to use with read_file
1486
+ 3. **Follow the skill's instructions**: SKILL.md contains step-by-step workflows, best practices, and examples
1487
+ 4. **Access supporting files**: Skills may include Python scripts, configs, or reference docs - use absolute paths
1488
+
1489
+ **When to Use Skills:**
1490
+ - When the user's request matches a skill's domain (e.g., "research X" → web-research skill)
1491
+ - When you need specialized knowledge or structured workflows
1492
+ - When a skill provides proven patterns for complex tasks
1493
+
1494
+ **Skills are Self-Documenting:**
1495
+ - Each SKILL.md tells you exactly what the skill does and how to use it
1496
+ - The skill list above shows the full path for each skill's SKILL.md file
1497
+
1498
+ **Executing Skill Scripts:**
1499
+ Skills may contain Python scripts or other executable files. Always use absolute paths from the skill list.
1500
+
1501
+ **Example Workflow:**
1502
+
1503
+ User: "Can you research the latest developments in quantum computing?"
1504
+
1505
+ 1. Check available skills above → See "web-research" skill with its full path
1506
+ 2. Read the skill using the path shown in the list
1507
+ 3. Follow the skill's research workflow (search → organize → synthesize)
1508
+ 4. Use any helper scripts with absolute paths
1509
+
1510
+ Remember: Skills are tools to make you more capable and consistent. When in doubt, check if a skill exists for the task!
1511
+ `;
1512
+ /**
1513
+ * Validate skill name per Agent Skills specification.
1514
+ */
1515
+ function validateSkillName$1(name, directoryName) {
1516
+ if (!name) return {
1517
+ valid: false,
1518
+ error: "name is required"
1519
+ };
1520
+ if (name.length > MAX_SKILL_NAME_LENGTH) return {
1521
+ valid: false,
1522
+ error: "name exceeds 64 characters"
1523
+ };
1524
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) return {
1525
+ valid: false,
1526
+ error: "name must be lowercase alphanumeric with single hyphens only"
1527
+ };
1528
+ if (name !== directoryName) return {
1529
+ valid: false,
1530
+ error: `name '${name}' must match directory name '${directoryName}'`
1531
+ };
1532
+ return {
1533
+ valid: true,
1534
+ error: ""
1535
+ };
1536
+ }
1537
+ /**
1538
+ * Parse YAML frontmatter from SKILL.md content.
1539
+ */
1540
+ function parseSkillMetadataFromContent(content, skillPath, directoryName) {
1541
+ if (content.length > MAX_SKILL_FILE_SIZE) {
1542
+ console.warn(`Skipping ${skillPath}: content too large (${content.length} bytes)`);
1543
+ return null;
1544
+ }
1545
+ const match = content.match(/^---\s*\n([\s\S]*?)\n---\s*\n/);
1546
+ if (!match) {
1547
+ console.warn(`Skipping ${skillPath}: no valid YAML frontmatter found`);
1548
+ return null;
1549
+ }
1550
+ const frontmatterStr = match[1];
1551
+ let frontmatterData;
1552
+ try {
1553
+ frontmatterData = yaml.default.parse(frontmatterStr);
1554
+ } catch (e) {
1555
+ console.warn(`Invalid YAML in ${skillPath}:`, e);
1556
+ return null;
1557
+ }
1558
+ if (!frontmatterData || typeof frontmatterData !== "object") {
1559
+ console.warn(`Skipping ${skillPath}: frontmatter is not a mapping`);
1560
+ return null;
1561
+ }
1562
+ const name = frontmatterData.name;
1563
+ const description = frontmatterData.description;
1564
+ if (!name || !description) {
1565
+ console.warn(`Skipping ${skillPath}: missing required 'name' or 'description'`);
1566
+ return null;
1567
+ }
1568
+ const validation = validateSkillName$1(String(name), directoryName);
1569
+ if (!validation.valid) console.warn(`Skill '${name}' in ${skillPath} does not follow Agent Skills specification: ${validation.error}. Consider renaming for spec compliance.`);
1570
+ let descriptionStr = String(description).trim();
1571
+ if (descriptionStr.length > MAX_SKILL_DESCRIPTION_LENGTH) {
1572
+ console.warn(`Description exceeds ${MAX_SKILL_DESCRIPTION_LENGTH} characters in ${skillPath}, truncating`);
1573
+ descriptionStr = descriptionStr.slice(0, MAX_SKILL_DESCRIPTION_LENGTH);
1574
+ }
1575
+ const allowedToolsStr = frontmatterData["allowed-tools"];
1576
+ const allowedTools = allowedToolsStr ? allowedToolsStr.split(" ") : [];
1577
+ return {
1578
+ name: String(name),
1579
+ description: descriptionStr,
1580
+ path: skillPath,
1581
+ metadata: frontmatterData.metadata || {},
1582
+ license: typeof frontmatterData.license === "string" ? frontmatterData.license.trim() || null : null,
1583
+ compatibility: typeof frontmatterData.compatibility === "string" ? frontmatterData.compatibility.trim() || null : null,
1584
+ allowedTools
1585
+ };
1586
+ }
1587
+ /**
1588
+ * List all skills from a backend source.
1589
+ */
1590
+ async function listSkillsFromBackend(backend, sourcePath) {
1591
+ const skills = [];
1592
+ const normalizedPath = sourcePath.endsWith("/") ? sourcePath : `${sourcePath}/`;
1593
+ let fileInfos;
1594
+ try {
1595
+ fileInfos = await backend.lsInfo(normalizedPath);
1596
+ } catch {
1597
+ return [];
1598
+ }
1599
+ const entries = fileInfos.map((info) => ({
1600
+ name: info.path.replace(/\/$/, "").split("/").pop() || "",
1601
+ type: info.is_dir ? "directory" : "file"
1602
+ }));
1603
+ for (const entry of entries) {
1604
+ if (entry.type !== "directory") continue;
1605
+ const skillMdPath = `${normalizedPath}${entry.name}/SKILL.md`;
1606
+ let content;
1607
+ if (backend.downloadFiles) {
1608
+ const results = await backend.downloadFiles([skillMdPath]);
1609
+ if (results.length !== 1) continue;
1610
+ const response = results[0];
1611
+ if (response.error != null || response.content == null) continue;
1612
+ content = new TextDecoder().decode(response.content);
1613
+ } else {
1614
+ const readResult = await backend.read(skillMdPath);
1615
+ if (readResult.startsWith("Error:")) continue;
1616
+ content = readResult;
1617
+ }
1618
+ const metadata = parseSkillMetadataFromContent(content, skillMdPath, entry.name);
1619
+ if (metadata) skills.push(metadata);
1620
+ }
1621
+ return skills;
1622
+ }
1623
+ /**
1624
+ * Format skills locations for display in system prompt.
1625
+ */
1626
+ function formatSkillsLocations(sources) {
1627
+ if (sources.length === 0) return "**Skills Sources:** None configured";
1628
+ const lines = ["**Skills Sources:**"];
1629
+ for (const source of sources) lines.push(`- \`${source}\``);
1630
+ return lines.join("\n");
1631
+ }
1632
+ /**
1633
+ * Format skills metadata for display in system prompt.
1634
+ */
1635
+ function formatSkillsList(skills, sources) {
1636
+ if (skills.length === 0) return `(No skills available yet. Skills can be created in ${sources.join(" or ")})`;
1637
+ const lines = [];
1638
+ for (const skill of skills) {
1639
+ lines.push(`- **${skill.name}**: ${skill.description}`);
1640
+ lines.push(` → Read \`${skill.path}\` for full instructions`);
1641
+ }
1642
+ return lines.join("\n");
1643
+ }
1644
+ /**
1645
+ * Create backend-agnostic middleware for loading and exposing agent skills.
1646
+ *
1647
+ * This middleware loads skills from configurable backend sources and injects
1648
+ * skill metadata into the system prompt. It implements the progressive disclosure
1649
+ * pattern: skill names and descriptions are shown in the prompt, but the agent
1650
+ * reads full SKILL.md content only when needed.
1651
+ *
1652
+ * @param options - Configuration options
1653
+ * @returns AgentMiddleware for skills loading and injection
1654
+ *
1655
+ * @example
1656
+ * ```typescript
1657
+ * const middleware = createSkillsMiddleware({
1658
+ * backend: new FilesystemBackend({ rootDir: "/" }),
1659
+ * sources: ["/skills/user/", "/skills/project/"],
1660
+ * });
1661
+ * ```
1662
+ */
1663
+ function createSkillsMiddleware(options) {
1664
+ const { backend, sources } = options;
1665
+ let loadedSkills = [];
1666
+ /**
1667
+ * Resolve backend from instance or factory.
1668
+ */
1669
+ function getBackend$1(state) {
1670
+ if (typeof backend === "function") return backend({ state });
1671
+ return backend;
1672
+ }
1673
+ return (0, langchain.createMiddleware)({
1674
+ name: "SkillsMiddleware",
1675
+ stateSchema: SkillsStateSchema,
1676
+ async beforeAgent(state) {
1677
+ if (loadedSkills.length > 0) return;
1678
+ if ("skillsMetadata" in state && state.skillsMetadata != null) {
1679
+ loadedSkills = state.skillsMetadata;
1680
+ return;
1681
+ }
1682
+ const resolvedBackend = getBackend$1(state);
1683
+ const allSkills = /* @__PURE__ */ new Map();
1684
+ for (const sourcePath of sources) try {
1685
+ const skills = await listSkillsFromBackend(resolvedBackend, sourcePath);
1686
+ for (const skill of skills) allSkills.set(skill.name, skill);
1687
+ } catch (error) {
1688
+ console.debug(`[BackendSkillsMiddleware] Failed to load skills from ${sourcePath}:`, error);
1689
+ }
1690
+ loadedSkills = Array.from(allSkills.values());
1691
+ return { skillsMetadata: loadedSkills };
1692
+ },
1693
+ wrapModelCall(request, handler) {
1694
+ const skillsMetadata = loadedSkills.length > 0 ? loadedSkills : request.state?.skillsMetadata || [];
1695
+ const skillsLocations = formatSkillsLocations(sources);
1696
+ const skillsList = formatSkillsList(skillsMetadata, sources);
1697
+ const skillsSection = SKILLS_SYSTEM_PROMPT.replace("{skills_locations}", skillsLocations).replace("{skills_list}", skillsList);
1698
+ const currentSystemPrompt = request.systemPrompt || "";
1699
+ const newSystemPrompt = currentSystemPrompt ? `${currentSystemPrompt}\n\n${skillsSection}` : skillsSection;
1700
+ return handler({
1701
+ ...request,
1702
+ systemPrompt: newSystemPrompt
1703
+ });
1704
+ }
1705
+ });
1706
+ }
1707
+
1201
1708
  //#endregion
1202
1709
  //#region src/backends/store.ts
1203
1710
  /**
@@ -1345,7 +1852,7 @@ var StoreBackend = class {
1345
1852
  * @param limit - Maximum number of lines to read
1346
1853
  * @returns Formatted file content with line numbers, or error message
1347
1854
  */
1348
- async read(filePath, offset = 0, limit = 2e3) {
1855
+ async read(filePath, offset = 0, limit = 500) {
1349
1856
  try {
1350
1857
  return formatReadResponse(await this.readRaw(filePath), offset, limit);
1351
1858
  } catch (e) {
@@ -1639,7 +2146,7 @@ var FilesystemBackend = class {
1639
2146
  * @param limit - Maximum number of lines to read
1640
2147
  * @returns Formatted file content with line numbers, or error message
1641
2148
  */
1642
- async read(filePath, offset = 0, limit = 2e3) {
2149
+ async read(filePath, offset = 0, limit = 500) {
1643
2150
  try {
1644
2151
  const resolvedPath = this.resolvePath(filePath);
1645
2152
  let content;
@@ -2102,7 +2609,7 @@ var CompositeBackend = class {
2102
2609
  * @param limit - Maximum number of lines to read
2103
2610
  * @returns Formatted file content with line numbers, or error message
2104
2611
  */
2105
- async read(filePath, offset = 0, limit = 2e3) {
2612
+ async read(filePath, offset = 0, limit = 500) {
2106
2613
  const [backend, strippedKey] = this.getBackendAndKey(filePath);
2107
2614
  return await backend.read(strippedKey, offset, limit);
2108
2615
  }
@@ -2223,6 +2730,7 @@ var CompositeBackend = class {
2223
2730
  });
2224
2731
  }
2225
2732
  for (const [backend, batch] of batchesByBackend) {
2733
+ if (!backend.uploadFiles) throw new Error("Backend does not support uploadFiles");
2226
2734
  const batchFiles = batch.map((b) => [b.path, b.content]);
2227
2735
  const batchResponses = await backend.uploadFiles(batchFiles);
2228
2736
  for (let i = 0; i < batch.length; i++) {
@@ -2254,6 +2762,7 @@ var CompositeBackend = class {
2254
2762
  });
2255
2763
  }
2256
2764
  for (const [backend, batch] of batchesByBackend) {
2765
+ if (!backend.downloadFiles) throw new Error("Backend does not support downloadFiles");
2257
2766
  const batchPaths = batch.map((b) => b.path);
2258
2767
  const batchResponses = await backend.downloadFiles(batchPaths);
2259
2768
  for (let i = 0; i < batch.length; i++) {
@@ -2553,7 +3062,7 @@ var BaseSandbox = class {
2553
3062
  * @param limit - Maximum number of lines to read
2554
3063
  * @returns Formatted file content with line numbers, or error message
2555
3064
  */
2556
- async read(filePath, offset = 0, limit = 2e3) {
3065
+ async read(filePath, offset = 0, limit = 500) {
2557
3066
  const command = buildReadCommand(filePath, offset, limit);
2558
3067
  const result = await this.execute(command);
2559
3068
  if (result.exitCode !== 0) return `Error: File '${filePath}' not found`;
@@ -2687,7 +3196,7 @@ const BASE_PROMPT = `In order to complete the objective that the user asks of yo
2687
3196
  * ```
2688
3197
  */
2689
3198
  function createDeepAgent(params = {}) {
2690
- const { model = "claude-sonnet-4-5-20250929", tools = [], systemPrompt, middleware: customMiddleware = [], subagents = [], responseFormat, contextSchema, checkpointer, store, backend, interruptOn, name } = params;
3199
+ const { model = "claude-sonnet-4-5-20250929", tools = [], systemPrompt, middleware: customMiddleware = [], subagents = [], responseFormat, contextSchema, checkpointer, store, backend, interruptOn, name, memory, skills } = params;
2691
3200
  const finalSystemPrompt = systemPrompt ? typeof systemPrompt === "string" ? `${systemPrompt}\n\n${BASE_PROMPT}` : new langchain.SystemMessage({ content: [{
2692
3201
  type: "text",
2693
3202
  text: BASE_PROMPT
@@ -2696,14 +3205,20 @@ function createDeepAgent(params = {}) {
2696
3205
  text: systemPrompt.content
2697
3206
  }] : systemPrompt.content] }) : BASE_PROMPT;
2698
3207
  const filesystemBackend = backend ? backend : (config) => new StateBackend(config);
3208
+ const skillsMiddleware = skills != null && skills.length > 0 ? [createSkillsMiddleware({
3209
+ backend: filesystemBackend,
3210
+ sources: skills
3211
+ })] : [];
2699
3212
  const builtInMiddleware = [
2700
3213
  (0, langchain.todoListMiddleware)(),
3214
+ ...skillsMiddleware,
2701
3215
  createFilesystemMiddleware({ backend: filesystemBackend }),
2702
3216
  createSubAgentMiddleware({
2703
3217
  defaultModel: model,
2704
3218
  defaultTools: tools,
2705
3219
  defaultMiddleware: [
2706
3220
  (0, langchain.todoListMiddleware)(),
3221
+ ...skillsMiddleware,
2707
3222
  createFilesystemMiddleware({ backend: filesystemBackend }),
2708
3223
  (0, langchain.summarizationMiddleware)({
2709
3224
  model,
@@ -2723,7 +3238,11 @@ function createDeepAgent(params = {}) {
2723
3238
  keep: { messages: 6 }
2724
3239
  }),
2725
3240
  (0, langchain.anthropicPromptCachingMiddleware)({ unsupportedModelBehavior: "ignore" }),
2726
- createPatchToolCallsMiddleware()
3241
+ createPatchToolCallsMiddleware(),
3242
+ ...memory != null && memory.length > 0 ? [createMemoryMiddleware({
3243
+ backend: filesystemBackend,
3244
+ sources: memory
3245
+ })] : []
2727
3246
  ];
2728
3247
  if (interruptOn) builtInMiddleware.push((0, langchain.humanInTheLoopMiddleware)({ interruptOn }));
2729
3248
  return (0, langchain.createAgent)({
@@ -2834,406 +3353,14 @@ function createSettings(options = {}) {
2834
3353
  }
2835
3354
 
2836
3355
  //#endregion
2837
- //#region src/skills/loader.ts
3356
+ //#region src/middleware/agent-memory.ts
2838
3357
  /**
2839
- * Skill loader for parsing and loading agent skills from SKILL.md files.
2840
- *
2841
- * This module implements Anthropic's agent skills pattern with YAML frontmatter parsing.
2842
- * Each skill is a directory containing a SKILL.md file with:
2843
- * - YAML frontmatter (name, description required)
2844
- * - Markdown instructions for the agent
2845
- * - Optional supporting files (scripts, configs, etc.)
2846
- *
2847
- * @example
2848
- * ```markdown
2849
- * ---
2850
- * name: web-research
2851
- * description: Structured approach to conducting thorough web research
2852
- * ---
2853
- *
2854
- * # Web Research Skill
2855
- *
2856
- * ## When to Use
2857
- * - User asks you to research a topic
2858
- * ...
2859
- * ```
3358
+ * Middleware for loading agent-specific long-term memory into the system prompt.
2860
3359
  *
2861
- * @see https://agentskills.io/specification
2862
- */
2863
- /** Maximum size for SKILL.md files (10MB) */
2864
- const MAX_SKILL_FILE_SIZE = 10 * 1024 * 1024;
2865
- /** Agent Skills spec constraints */
2866
- const MAX_SKILL_NAME_LENGTH = 64;
2867
- const MAX_SKILL_DESCRIPTION_LENGTH = 1024;
2868
- /** Pattern for validating skill names per Agent Skills spec */
2869
- const SKILL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
2870
- /** Pattern for extracting YAML frontmatter */
2871
- const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---\s*\n/;
2872
- /**
2873
- * Check if a path is safely contained within base_dir.
2874
- *
2875
- * This prevents directory traversal attacks via symlinks or path manipulation.
2876
- * The function resolves both paths to their canonical form (following symlinks)
2877
- * and verifies that the target path is within the base directory.
2878
- *
2879
- * @param targetPath - The path to validate
2880
- * @param baseDir - The base directory that should contain the path
2881
- * @returns True if the path is safely within baseDir, false otherwise
2882
- */
2883
- function isSafePath(targetPath, baseDir) {
2884
- try {
2885
- const resolvedPath = node_fs.default.realpathSync(targetPath);
2886
- const resolvedBase = node_fs.default.realpathSync(baseDir);
2887
- return resolvedPath.startsWith(resolvedBase + node_path.default.sep) || resolvedPath === resolvedBase;
2888
- } catch {
2889
- return false;
2890
- }
2891
- }
2892
- /**
2893
- * Validate skill name per Agent Skills spec.
2894
- *
2895
- * Requirements:
2896
- * - Max 64 characters
2897
- * - Lowercase alphanumeric and hyphens only (a-z, 0-9, -)
2898
- * - Cannot start or end with hyphen
2899
- * - No consecutive hyphens
2900
- * - Must match parent directory name
2901
- *
2902
- * @param name - The skill name from YAML frontmatter
2903
- * @param directoryName - The parent directory name
2904
- * @returns Validation result with error message if invalid
2905
- */
2906
- function validateSkillName(name, directoryName) {
2907
- if (!name) return {
2908
- valid: false,
2909
- error: "name is required"
2910
- };
2911
- if (name.length > MAX_SKILL_NAME_LENGTH) return {
2912
- valid: false,
2913
- error: "name exceeds 64 characters"
2914
- };
2915
- if (!SKILL_NAME_PATTERN.test(name)) return {
2916
- valid: false,
2917
- error: "name must be lowercase alphanumeric with single hyphens only"
2918
- };
2919
- if (name !== directoryName) return {
2920
- valid: false,
2921
- error: `name '${name}' must match directory name '${directoryName}'`
2922
- };
2923
- return { valid: true };
2924
- }
2925
- /**
2926
- * Parse YAML frontmatter from content.
2927
- *
2928
- * @param content - The file content
2929
- * @returns Parsed frontmatter object, or null if parsing fails
2930
- */
2931
- function parseFrontmatter(content) {
2932
- const match = content.match(FRONTMATTER_PATTERN);
2933
- if (!match) return null;
2934
- try {
2935
- const parsed = yaml.default.parse(match[1]);
2936
- return typeof parsed === "object" && parsed !== null ? parsed : null;
2937
- } catch {
2938
- return null;
2939
- }
2940
- }
2941
- /**
2942
- * Parse YAML frontmatter from a SKILL.md file per Agent Skills spec.
2943
- *
2944
- * @param skillMdPath - Path to the SKILL.md file
2945
- * @param source - Source of the skill ('user' or 'project')
2946
- * @returns SkillMetadata with all fields, or null if parsing fails
2947
- */
2948
- function parseSkillMetadata(skillMdPath, source) {
2949
- try {
2950
- const stats = node_fs.default.statSync(skillMdPath);
2951
- if (stats.size > MAX_SKILL_FILE_SIZE) {
2952
- console.warn(`Skipping ${skillMdPath}: file too large (${stats.size} bytes)`);
2953
- return null;
2954
- }
2955
- const frontmatter = parseFrontmatter(node_fs.default.readFileSync(skillMdPath, "utf-8"));
2956
- if (!frontmatter) {
2957
- console.warn(`Skipping ${skillMdPath}: no valid YAML frontmatter found`);
2958
- return null;
2959
- }
2960
- const name = frontmatter.name;
2961
- const description = frontmatter.description;
2962
- if (!name || !description) {
2963
- console.warn(`Skipping ${skillMdPath}: missing required 'name' or 'description'`);
2964
- return null;
2965
- }
2966
- const directoryName = node_path.default.basename(node_path.default.dirname(skillMdPath));
2967
- const validation = validateSkillName(String(name), directoryName);
2968
- if (!validation.valid) console.warn(`Skill '${name}' in ${skillMdPath} does not follow Agent Skills spec: ${validation.error}. Consider renaming to be spec-compliant.`);
2969
- let descriptionStr = String(description);
2970
- if (descriptionStr.length > MAX_SKILL_DESCRIPTION_LENGTH) {
2971
- console.warn(`Description exceeds ${MAX_SKILL_DESCRIPTION_LENGTH} chars in ${skillMdPath}, truncating`);
2972
- descriptionStr = descriptionStr.slice(0, MAX_SKILL_DESCRIPTION_LENGTH);
2973
- }
2974
- return {
2975
- name: String(name),
2976
- description: descriptionStr,
2977
- path: skillMdPath,
2978
- source,
2979
- license: frontmatter.license ? String(frontmatter.license) : void 0,
2980
- compatibility: frontmatter.compatibility ? String(frontmatter.compatibility) : void 0,
2981
- metadata: frontmatter.metadata && typeof frontmatter.metadata === "object" ? frontmatter.metadata : void 0,
2982
- allowedTools: frontmatter["allowed-tools"] ? String(frontmatter["allowed-tools"]) : void 0
2983
- };
2984
- } catch (error) {
2985
- console.warn(`Error reading ${skillMdPath}: ${error}`);
2986
- return null;
2987
- }
2988
- }
2989
- /**
2990
- * List all skills from a single skills directory (internal helper).
2991
- *
2992
- * Scans the skills directory for subdirectories containing SKILL.md files,
2993
- * parses YAML frontmatter, and returns skill metadata.
2994
- *
2995
- * Skills are organized as:
2996
- * ```
2997
- * skills/
2998
- * ├── skill-name/
2999
- * │ ├── SKILL.md # Required: instructions with YAML frontmatter
3000
- * │ ├── script.py # Optional: supporting files
3001
- * │ └── config.json # Optional: supporting files
3002
- * ```
3003
- *
3004
- * @param skillsDir - Path to the skills directory
3005
- * @param source - Source of the skills ('user' or 'project')
3006
- * @returns List of skill metadata
3007
- */
3008
- function listSkillsFromDir(skillsDir, source) {
3009
- const expandedDir = skillsDir.startsWith("~") ? node_path.default.join(process.env.HOME || process.env.USERPROFILE || "", skillsDir.slice(1)) : skillsDir;
3010
- if (!node_fs.default.existsSync(expandedDir)) return [];
3011
- let resolvedBase;
3012
- try {
3013
- resolvedBase = node_fs.default.realpathSync(expandedDir);
3014
- } catch {
3015
- return [];
3016
- }
3017
- const skills = [];
3018
- let entries;
3019
- try {
3020
- entries = node_fs.default.readdirSync(resolvedBase, { withFileTypes: true });
3021
- } catch {
3022
- return [];
3023
- }
3024
- for (const entry of entries) {
3025
- const skillDir = node_path.default.join(resolvedBase, entry.name);
3026
- if (!isSafePath(skillDir, resolvedBase)) continue;
3027
- if (!entry.isDirectory()) continue;
3028
- const skillMdPath = node_path.default.join(skillDir, "SKILL.md");
3029
- if (!node_fs.default.existsSync(skillMdPath)) continue;
3030
- if (!isSafePath(skillMdPath, resolvedBase)) continue;
3031
- const metadata = parseSkillMetadata(skillMdPath, source);
3032
- if (metadata) skills.push(metadata);
3033
- }
3034
- return skills;
3035
- }
3036
- /**
3037
- * List skills from user and/or project directories.
3038
- *
3039
- * When both directories are provided, project skills with the same name as
3040
- * user skills will override them.
3041
- *
3042
- * @param options - Options specifying which directories to search
3043
- * @returns Merged list of skill metadata from both sources, with project skills
3044
- * taking precedence over user skills when names conflict
3045
- */
3046
- function listSkills(options) {
3047
- const allSkills = /* @__PURE__ */ new Map();
3048
- if (options.userSkillsDir) {
3049
- const userSkills = listSkillsFromDir(options.userSkillsDir, "user");
3050
- for (const skill of userSkills) allSkills.set(skill.name, skill);
3051
- }
3052
- if (options.projectSkillsDir) {
3053
- const projectSkills = listSkillsFromDir(options.projectSkillsDir, "project");
3054
- for (const skill of projectSkills) allSkills.set(skill.name, skill);
3055
- }
3056
- return Array.from(allSkills.values());
3057
- }
3058
-
3059
- //#endregion
3060
- //#region src/middleware/skills.ts
3061
- /**
3062
- * Middleware for loading and exposing agent skills to the system prompt.
3063
- *
3064
- * This middleware implements Anthropic's "Agent Skills" pattern with progressive disclosure:
3065
- * 1. Parse YAML frontmatter from SKILL.md files at session start
3066
- * 2. Inject skills metadata (name + description) into system prompt
3067
- * 3. Agent reads full SKILL.md content when relevant to a task
3068
- *
3069
- * Skills directory structure (per-agent + project):
3070
- * User-level: ~/.deepagents/{AGENT_NAME}/skills/
3071
- * Project-level: {PROJECT_ROOT}/.deepagents/skills/
3072
- *
3073
- * @example
3074
- * ```
3075
- * ~/.deepagents/{AGENT_NAME}/skills/
3076
- * ├── web-research/
3077
- * │ ├── SKILL.md # Required: YAML frontmatter + instructions
3078
- * │ └── helper.py # Optional: supporting files
3079
- * ├── code-review/
3080
- * │ ├── SKILL.md
3081
- * │ └── checklist.md
3082
- *
3083
- * .deepagents/skills/
3084
- * ├── project-specific/
3085
- * │ └── SKILL.md # Project-specific skills
3086
- * ```
3087
- */
3088
- /**
3089
- * State schema for skills middleware.
3090
- */
3091
- const SkillsStateSchema = zod.z.object({ skillsMetadata: zod.z.array(zod.z.object({
3092
- name: zod.z.string(),
3093
- description: zod.z.string(),
3094
- path: zod.z.string(),
3095
- source: zod.z.enum(["user", "project"]),
3096
- license: zod.z.string().optional(),
3097
- compatibility: zod.z.string().optional(),
3098
- metadata: zod.z.record(zod.z.string(), zod.z.string()).optional(),
3099
- allowedTools: zod.z.string().optional()
3100
- })).optional() });
3101
- /**
3102
- * Skills System Documentation prompt template.
3103
- */
3104
- const SKILLS_SYSTEM_PROMPT = `
3105
-
3106
- ## Skills System
3107
-
3108
- You have access to a skills library that provides specialized capabilities and domain knowledge.
3109
-
3110
- {skills_locations}
3111
-
3112
- **Available Skills:**
3113
-
3114
- {skills_list}
3115
-
3116
- **How to Use Skills (Progressive Disclosure):**
3117
-
3118
- Skills follow a **progressive disclosure** pattern - you know they exist (name + description above), but you only read the full instructions when needed:
3119
-
3120
- 1. **Recognize when a skill applies**: Check if the user's task matches any skill's description
3121
- 2. **Read the skill's full instructions**: The skill list above shows the exact path to use with read_file
3122
- 3. **Follow the skill's instructions**: SKILL.md contains step-by-step workflows, best practices, and examples
3123
- 4. **Access supporting files**: Skills may include Python scripts, configs, or reference docs - use absolute paths
3124
-
3125
- **When to Use Skills:**
3126
- - When the user's request matches a skill's domain (e.g., "research X" → web-research skill)
3127
- - When you need specialized knowledge or structured workflows
3128
- - When a skill provides proven patterns for complex tasks
3129
-
3130
- **Skills are Self-Documenting:**
3131
- - Each SKILL.md tells you exactly what the skill does and how to use it
3132
- - The skill list above shows the full path for each skill's SKILL.md file
3133
-
3134
- **Executing Skill Scripts:**
3135
- Skills may contain Python scripts or other executable files. Always use absolute paths from the skill list.
3136
-
3137
- **Example Workflow:**
3138
-
3139
- User: "Can you research the latest developments in quantum computing?"
3140
-
3141
- 1. Check available skills above → See "web-research" skill with its full path
3142
- 2. Read the skill using the path shown in the list
3143
- 3. Follow the skill's research workflow (search → organize → synthesize)
3144
- 4. Use any helper scripts with absolute paths
3145
-
3146
- Remember: Skills are tools to make you more capable and consistent. When in doubt, check if a skill exists for the task!
3147
- `;
3148
- /**
3149
- * Format skills locations for display in system prompt.
3150
- */
3151
- function formatSkillsLocations(userSkillsDisplay, projectSkillsDir) {
3152
- const locations = [`**User Skills**: \`${userSkillsDisplay}\``];
3153
- if (projectSkillsDir) locations.push(`**Project Skills**: \`${projectSkillsDir}\` (overrides user skills)`);
3154
- return locations.join("\n");
3155
- }
3156
- /**
3157
- * Format skills metadata for display in system prompt.
3158
- */
3159
- function formatSkillsList(skills, userSkillsDisplay, projectSkillsDir) {
3160
- if (skills.length === 0) {
3161
- const locations = [userSkillsDisplay];
3162
- if (projectSkillsDir) locations.push(projectSkillsDir);
3163
- return `(No skills available yet. You can create skills in ${locations.join(" or ")})`;
3164
- }
3165
- const userSkills = skills.filter((s) => s.source === "user");
3166
- const projectSkills = skills.filter((s) => s.source === "project");
3167
- const lines = [];
3168
- if (userSkills.length > 0) {
3169
- lines.push("**User Skills:**");
3170
- for (const skill of userSkills) {
3171
- lines.push(`- **${skill.name}**: ${skill.description}`);
3172
- lines.push(` → Read \`${skill.path}\` for full instructions`);
3173
- }
3174
- lines.push("");
3175
- }
3176
- if (projectSkills.length > 0) {
3177
- lines.push("**Project Skills:**");
3178
- for (const skill of projectSkills) {
3179
- lines.push(`- **${skill.name}**: ${skill.description}`);
3180
- lines.push(` → Read \`${skill.path}\` for full instructions`);
3181
- }
3182
- }
3183
- return lines.join("\n");
3184
- }
3185
- /**
3186
- * Create middleware for loading and exposing agent skills.
3187
- *
3188
- * This middleware implements Anthropic's agent skills pattern:
3189
- * - Loads skills metadata (name, description) from YAML frontmatter at session start
3190
- * - Injects skills list into system prompt for discoverability
3191
- * - Agent reads full SKILL.md content when a skill is relevant (progressive disclosure)
3192
- *
3193
- * Supports both user-level and project-level skills:
3194
- * - User skills: ~/.deepagents/{AGENT_NAME}/skills/
3195
- * - Project skills: {PROJECT_ROOT}/.deepagents/skills/
3196
- * - Project skills override user skills with the same name
3197
- *
3198
- * @param options - Configuration options
3199
- * @returns AgentMiddleware for skills loading and injection
3200
- */
3201
- function createSkillsMiddleware(options) {
3202
- const { skillsDir, assistantId, projectSkillsDir } = options;
3203
- const userSkillsDisplay = `~/.deepagents/${assistantId}/skills`;
3204
- return (0, langchain.createMiddleware)({
3205
- name: "SkillsMiddleware",
3206
- stateSchema: SkillsStateSchema,
3207
- beforeAgent() {
3208
- return { skillsMetadata: listSkills({
3209
- userSkillsDir: skillsDir,
3210
- projectSkillsDir
3211
- }) };
3212
- },
3213
- wrapModelCall(request, handler) {
3214
- const skillsMetadata = request.state?.skillsMetadata || [];
3215
- const skillsLocations = formatSkillsLocations(userSkillsDisplay, projectSkillsDir);
3216
- const skillsList = formatSkillsList(skillsMetadata, userSkillsDisplay, projectSkillsDir);
3217
- const skillsSection = SKILLS_SYSTEM_PROMPT.replace("{skills_locations}", skillsLocations).replace("{skills_list}", skillsList);
3218
- const currentSystemPrompt = request.systemPrompt || "";
3219
- const newSystemPrompt = currentSystemPrompt ? `${currentSystemPrompt}\n\n${skillsSection}` : skillsSection;
3220
- return handler({
3221
- ...request,
3222
- systemPrompt: newSystemPrompt
3223
- });
3224
- }
3225
- });
3226
- }
3227
-
3228
- //#endregion
3229
- //#region src/middleware/agent-memory.ts
3230
- /**
3231
- * Middleware for loading agent-specific long-term memory into the system prompt.
3232
- *
3233
- * This middleware loads the agent's long-term memory from agent.md files
3234
- * and injects it into the system prompt. Memory is loaded from:
3235
- * - User memory: ~/.deepagents/{agent_name}/agent.md
3236
- * - Project memory: {project_root}/.deepagents/agent.md
3360
+ * This middleware loads the agent's long-term memory from agent.md files
3361
+ * and injects it into the system prompt. Memory is loaded from:
3362
+ * - User memory: ~/.deepagents/{agent_name}/agent.md
3363
+ * - Project memory: {project_root}/.deepagents/agent.md
3237
3364
  */
3238
3365
  /**
3239
3366
  * State schema for agent memory middleware.
@@ -3425,6 +3552,229 @@ function createAgentMemoryMiddleware(options) {
3425
3552
  });
3426
3553
  }
3427
3554
 
3555
+ //#endregion
3556
+ //#region src/skills/loader.ts
3557
+ /**
3558
+ * Skill loader for parsing and loading agent skills from SKILL.md files.
3559
+ *
3560
+ * This module implements Anthropic's agent skills pattern with YAML frontmatter parsing.
3561
+ * Each skill is a directory containing a SKILL.md file with:
3562
+ * - YAML frontmatter (name, description required)
3563
+ * - Markdown instructions for the agent
3564
+ * - Optional supporting files (scripts, configs, etc.)
3565
+ *
3566
+ * @example
3567
+ * ```markdown
3568
+ * ---
3569
+ * name: web-research
3570
+ * description: Structured approach to conducting thorough web research
3571
+ * ---
3572
+ *
3573
+ * # Web Research Skill
3574
+ *
3575
+ * ## When to Use
3576
+ * - User asks you to research a topic
3577
+ * ...
3578
+ * ```
3579
+ *
3580
+ * @see https://agentskills.io/specification
3581
+ */
3582
+ /** Maximum size for SKILL.md files (10MB) */
3583
+ const MAX_SKILL_FILE_SIZE$1 = 10 * 1024 * 1024;
3584
+ /** Agent Skills spec constraints */
3585
+ const MAX_SKILL_NAME_LENGTH$1 = 64;
3586
+ const MAX_SKILL_DESCRIPTION_LENGTH$1 = 1024;
3587
+ /** Pattern for validating skill names per Agent Skills spec */
3588
+ const SKILL_NAME_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
3589
+ /** Pattern for extracting YAML frontmatter */
3590
+ const FRONTMATTER_PATTERN = /^---\s*\n([\s\S]*?)\n---\s*\n/;
3591
+ /**
3592
+ * Check if a path is safely contained within base_dir.
3593
+ *
3594
+ * This prevents directory traversal attacks via symlinks or path manipulation.
3595
+ * The function resolves both paths to their canonical form (following symlinks)
3596
+ * and verifies that the target path is within the base directory.
3597
+ *
3598
+ * @param targetPath - The path to validate
3599
+ * @param baseDir - The base directory that should contain the path
3600
+ * @returns True if the path is safely within baseDir, false otherwise
3601
+ */
3602
+ function isSafePath(targetPath, baseDir) {
3603
+ try {
3604
+ const resolvedPath = node_fs.default.realpathSync(targetPath);
3605
+ const resolvedBase = node_fs.default.realpathSync(baseDir);
3606
+ return resolvedPath.startsWith(resolvedBase + node_path.default.sep) || resolvedPath === resolvedBase;
3607
+ } catch {
3608
+ return false;
3609
+ }
3610
+ }
3611
+ /**
3612
+ * Validate skill name per Agent Skills spec.
3613
+ *
3614
+ * Requirements:
3615
+ * - Max 64 characters
3616
+ * - Lowercase alphanumeric and hyphens only (a-z, 0-9, -)
3617
+ * - Cannot start or end with hyphen
3618
+ * - No consecutive hyphens
3619
+ * - Must match parent directory name
3620
+ *
3621
+ * @param name - The skill name from YAML frontmatter
3622
+ * @param directoryName - The parent directory name
3623
+ * @returns Validation result with error message if invalid
3624
+ */
3625
+ function validateSkillName(name, directoryName) {
3626
+ if (!name) return {
3627
+ valid: false,
3628
+ error: "name is required"
3629
+ };
3630
+ if (name.length > MAX_SKILL_NAME_LENGTH$1) return {
3631
+ valid: false,
3632
+ error: "name exceeds 64 characters"
3633
+ };
3634
+ if (!SKILL_NAME_PATTERN.test(name)) return {
3635
+ valid: false,
3636
+ error: "name must be lowercase alphanumeric with single hyphens only"
3637
+ };
3638
+ if (name !== directoryName) return {
3639
+ valid: false,
3640
+ error: `name '${name}' must match directory name '${directoryName}'`
3641
+ };
3642
+ return { valid: true };
3643
+ }
3644
+ /**
3645
+ * Parse YAML frontmatter from content.
3646
+ *
3647
+ * @param content - The file content
3648
+ * @returns Parsed frontmatter object, or null if parsing fails
3649
+ */
3650
+ function parseFrontmatter(content) {
3651
+ const match = content.match(FRONTMATTER_PATTERN);
3652
+ if (!match) return null;
3653
+ try {
3654
+ const parsed = yaml.default.parse(match[1]);
3655
+ return typeof parsed === "object" && parsed !== null ? parsed : null;
3656
+ } catch {
3657
+ return null;
3658
+ }
3659
+ }
3660
+ /**
3661
+ * Parse YAML frontmatter from a SKILL.md file per Agent Skills spec.
3662
+ *
3663
+ * @param skillMdPath - Path to the SKILL.md file
3664
+ * @param source - Source of the skill ('user' or 'project')
3665
+ * @returns SkillMetadata with all fields, or null if parsing fails
3666
+ */
3667
+ function parseSkillMetadata(skillMdPath, source) {
3668
+ try {
3669
+ const stats = node_fs.default.statSync(skillMdPath);
3670
+ if (stats.size > MAX_SKILL_FILE_SIZE$1) {
3671
+ console.warn(`Skipping ${skillMdPath}: file too large (${stats.size} bytes)`);
3672
+ return null;
3673
+ }
3674
+ const frontmatter = parseFrontmatter(node_fs.default.readFileSync(skillMdPath, "utf-8"));
3675
+ if (!frontmatter) {
3676
+ console.warn(`Skipping ${skillMdPath}: no valid YAML frontmatter found`);
3677
+ return null;
3678
+ }
3679
+ const name = frontmatter.name;
3680
+ const description = frontmatter.description;
3681
+ if (!name || !description) {
3682
+ console.warn(`Skipping ${skillMdPath}: missing required 'name' or 'description'`);
3683
+ return null;
3684
+ }
3685
+ const directoryName = node_path.default.basename(node_path.default.dirname(skillMdPath));
3686
+ const validation = validateSkillName(String(name), directoryName);
3687
+ if (!validation.valid) console.warn(`Skill '${name}' in ${skillMdPath} does not follow Agent Skills spec: ${validation.error}. Consider renaming to be spec-compliant.`);
3688
+ let descriptionStr = String(description);
3689
+ if (descriptionStr.length > MAX_SKILL_DESCRIPTION_LENGTH$1) {
3690
+ console.warn(`Description exceeds ${MAX_SKILL_DESCRIPTION_LENGTH$1} chars in ${skillMdPath}, truncating`);
3691
+ descriptionStr = descriptionStr.slice(0, MAX_SKILL_DESCRIPTION_LENGTH$1);
3692
+ }
3693
+ return {
3694
+ name: String(name),
3695
+ description: descriptionStr,
3696
+ path: skillMdPath,
3697
+ source,
3698
+ license: frontmatter.license ? String(frontmatter.license) : void 0,
3699
+ compatibility: frontmatter.compatibility ? String(frontmatter.compatibility) : void 0,
3700
+ metadata: frontmatter.metadata && typeof frontmatter.metadata === "object" ? frontmatter.metadata : void 0,
3701
+ allowedTools: frontmatter["allowed-tools"] ? String(frontmatter["allowed-tools"]) : void 0
3702
+ };
3703
+ } catch (error) {
3704
+ console.warn(`Error reading ${skillMdPath}: ${error}`);
3705
+ return null;
3706
+ }
3707
+ }
3708
+ /**
3709
+ * List all skills from a single skills directory (internal helper).
3710
+ *
3711
+ * Scans the skills directory for subdirectories containing SKILL.md files,
3712
+ * parses YAML frontmatter, and returns skill metadata.
3713
+ *
3714
+ * Skills are organized as:
3715
+ * ```
3716
+ * skills/
3717
+ * ├── skill-name/
3718
+ * │ ├── SKILL.md # Required: instructions with YAML frontmatter
3719
+ * │ ├── script.py # Optional: supporting files
3720
+ * │ └── config.json # Optional: supporting files
3721
+ * ```
3722
+ *
3723
+ * @param skillsDir - Path to the skills directory
3724
+ * @param source - Source of the skills ('user' or 'project')
3725
+ * @returns List of skill metadata
3726
+ */
3727
+ function listSkillsFromDir(skillsDir, source) {
3728
+ const expandedDir = skillsDir.startsWith("~") ? node_path.default.join(process.env.HOME || process.env.USERPROFILE || "", skillsDir.slice(1)) : skillsDir;
3729
+ if (!node_fs.default.existsSync(expandedDir)) return [];
3730
+ let resolvedBase;
3731
+ try {
3732
+ resolvedBase = node_fs.default.realpathSync(expandedDir);
3733
+ } catch {
3734
+ return [];
3735
+ }
3736
+ const skills = [];
3737
+ let entries;
3738
+ try {
3739
+ entries = node_fs.default.readdirSync(resolvedBase, { withFileTypes: true });
3740
+ } catch {
3741
+ return [];
3742
+ }
3743
+ for (const entry of entries) {
3744
+ const skillDir = node_path.default.join(resolvedBase, entry.name);
3745
+ if (!isSafePath(skillDir, resolvedBase)) continue;
3746
+ if (!entry.isDirectory()) continue;
3747
+ const skillMdPath = node_path.default.join(skillDir, "SKILL.md");
3748
+ if (!node_fs.default.existsSync(skillMdPath)) continue;
3749
+ if (!isSafePath(skillMdPath, resolvedBase)) continue;
3750
+ const metadata = parseSkillMetadata(skillMdPath, source);
3751
+ if (metadata) skills.push(metadata);
3752
+ }
3753
+ return skills;
3754
+ }
3755
+ /**
3756
+ * List skills from user and/or project directories.
3757
+ *
3758
+ * When both directories are provided, project skills with the same name as
3759
+ * user skills will override them.
3760
+ *
3761
+ * @param options - Options specifying which directories to search
3762
+ * @returns Merged list of skill metadata from both sources, with project skills
3763
+ * taking precedence over user skills when names conflict
3764
+ */
3765
+ function listSkills(options) {
3766
+ const allSkills = /* @__PURE__ */ new Map();
3767
+ if (options.userSkillsDir) {
3768
+ const userSkills = listSkillsFromDir(options.userSkillsDir, "user");
3769
+ for (const skill of userSkills) allSkills.set(skill.name, skill);
3770
+ }
3771
+ if (options.projectSkillsDir) {
3772
+ const projectSkills = listSkillsFromDir(options.projectSkillsDir, "project");
3773
+ for (const skill of projectSkills) allSkills.set(skill.name, skill);
3774
+ }
3775
+ return Array.from(allSkills.values());
3776
+ }
3777
+
3428
3778
  //#endregion
3429
3779
  exports.BaseSandbox = BaseSandbox;
3430
3780
  exports.CompositeBackend = CompositeBackend;
@@ -3437,6 +3787,7 @@ exports.StoreBackend = StoreBackend;
3437
3787
  exports.createAgentMemoryMiddleware = createAgentMemoryMiddleware;
3438
3788
  exports.createDeepAgent = createDeepAgent;
3439
3789
  exports.createFilesystemMiddleware = createFilesystemMiddleware;
3790
+ exports.createMemoryMiddleware = createMemoryMiddleware;
3440
3791
  exports.createPatchToolCallsMiddleware = createPatchToolCallsMiddleware;
3441
3792
  exports.createSettings = createSettings;
3442
3793
  exports.createSkillsMiddleware = createSkillsMiddleware;