Skip to content

Commit 4ea1858

Browse files
antonnakAnton Nakaliuzhnyiclaude
authored
fix(skills): use systemMessage.concat() instead of systemPrompt string (#208)
* fix(skills): use systemMessage.concat() instead of systemPrompt string * style: fix Prettier formatting in skills.test.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix(skills): update integration tests to use systemMessage API The integration tests were using the old systemPrompt string API but wrapModelCall now uses request.systemMessage.concat(). Updated tests to pass SystemMessage objects and read from systemMessage.text. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Anton Nakaliuzhnyi <anakaliuzhnyi@gipartners.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 7f91c53 commit 4ea1858

File tree

4 files changed

+53
-40
lines changed

4 files changed

+53
-40
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
"deepagents": patch
3+
---
4+
5+
fix(skills): use systemMessage.concat() instead of systemPrompt string in SkillsMiddleware
6+
7+
Aligns SkillsMiddleware.wrapModelCall with FilesystemMiddleware and SubAgentMiddleware
8+
by using request.systemMessage.concat() instead of request.systemPrompt string concatenation.
9+
This preserves SystemMessage content blocks including cache_control annotations for
10+
Anthropic prompt caching.

libs/deepagents/src/middleware/skills.test.ts

Lines changed: 30 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -737,7 +737,7 @@ description: [invalid yaml syntax: unclosed bracket
737737

738738
const mockHandler = vi.fn().mockReturnValue({ response: "ok" });
739739
const request: any = {
740-
systemPrompt: "Base prompt",
740+
systemMessage: new SystemMessage("Base prompt"),
741741
state: {
742742
skillsMetadata: [
743743
{
@@ -753,10 +753,10 @@ description: [invalid yaml syntax: unclosed bracket
753753

754754
expect(mockHandler).toHaveBeenCalled();
755755
const modifiedRequest = mockHandler.mock.calls[0][0];
756-
expect(modifiedRequest.systemPrompt).toContain("Skills System");
757-
expect(modifiedRequest.systemPrompt).toContain("web-research");
758-
expect(modifiedRequest.systemPrompt).toContain("Research the web");
759-
expect(modifiedRequest.systemPrompt).toContain(
756+
expect(modifiedRequest.systemMessage.text).toContain("Skills System");
757+
expect(modifiedRequest.systemMessage.text).toContain("web-research");
758+
expect(modifiedRequest.systemMessage.text).toContain("Research the web");
759+
expect(modifiedRequest.systemMessage.text).toContain(
760760
"/skills/user/web-research/SKILL.md",
761761
);
762762
});
@@ -769,14 +769,16 @@ description: [invalid yaml syntax: unclosed bracket
769769

770770
const mockHandler = vi.fn().mockReturnValue({ response: "ok" });
771771
const request: any = {
772-
systemPrompt: "Base prompt",
772+
systemMessage: new SystemMessage("Base prompt"),
773773
state: { skillsMetadata: [] },
774774
};
775775

776776
middleware.wrapModelCall!(request, mockHandler);
777777

778778
const modifiedRequest = mockHandler.mock.calls[0][0];
779-
expect(modifiedRequest.systemPrompt).toContain("No skills available yet");
779+
expect(modifiedRequest.systemMessage.text).toContain(
780+
"No skills available yet",
781+
);
780782
});
781783

782784
it("should show priority indicator for last source", () => {
@@ -787,18 +789,18 @@ description: [invalid yaml syntax: unclosed bracket
787789

788790
const mockHandler = vi.fn().mockReturnValue({ response: "ok" });
789791
const request: any = {
790-
systemPrompt: "Base prompt",
792+
systemMessage: new SystemMessage("Base prompt"),
791793
state: { skillsMetadata: [] },
792794
};
793795

794796
middleware.wrapModelCall!(request, mockHandler);
795797

796798
const modifiedRequest = mockHandler.mock.calls[0][0];
797799
// Last source should have "higher priority" indicator
798-
expect(modifiedRequest.systemPrompt).toContain("(higher priority)");
800+
expect(modifiedRequest.systemMessage.text).toContain("(higher priority)");
799801
// Should show project source with priority
800-
expect(modifiedRequest.systemPrompt).toContain("Project Skills");
801-
expect(modifiedRequest.systemPrompt).toContain("/skills/project/");
802+
expect(modifiedRequest.systemMessage.text).toContain("Project Skills");
803+
expect(modifiedRequest.systemMessage.text).toContain("/skills/project/");
802804
});
803805

804806
it("should show allowed tools for skills that have them", () => {
@@ -809,7 +811,7 @@ description: [invalid yaml syntax: unclosed bracket
809811

810812
const mockHandler = vi.fn().mockReturnValue({ response: "ok" });
811813
const request: any = {
812-
systemPrompt: "Base prompt",
814+
systemMessage: new SystemMessage("Base prompt"),
813815
state: {
814816
skillsMetadata: [
815817
{
@@ -825,9 +827,9 @@ description: [invalid yaml syntax: unclosed bracket
825827
middleware.wrapModelCall!(request, mockHandler);
826828

827829
const modifiedRequest = mockHandler.mock.calls[0][0];
828-
expect(modifiedRequest.systemPrompt).toContain("Allowed tools:");
829-
expect(modifiedRequest.systemPrompt).toContain("search_web");
830-
expect(modifiedRequest.systemPrompt).toContain("fetch_url");
830+
expect(modifiedRequest.systemMessage.text).toContain("Allowed tools:");
831+
expect(modifiedRequest.systemMessage.text).toContain("search_web");
832+
expect(modifiedRequest.systemMessage.text).toContain("fetch_url");
831833
});
832834

833835
it("should not show allowed tools line if skill has no allowed tools", () => {
@@ -838,7 +840,7 @@ description: [invalid yaml syntax: unclosed bracket
838840

839841
const mockHandler = vi.fn().mockReturnValue({ response: "ok" });
840842
const request: any = {
841-
systemPrompt: "Base prompt",
843+
systemMessage: new SystemMessage("Base prompt"),
842844
state: {
843845
skillsMetadata: [
844846
{
@@ -856,7 +858,7 @@ description: [invalid yaml syntax: unclosed bracket
856858
const modifiedRequest = mockHandler.mock.calls[0][0];
857859
// Should not have "Allowed tools:" line for skills without allowed tools
858860
const allowedToolsCount = (
859-
modifiedRequest.systemPrompt.match(/Allowed tools:/g) || []
861+
modifiedRequest.systemMessage.text.match(/Allowed tools:/g) || []
860862
).length;
861863
expect(allowedToolsCount).toBe(0);
862864
});
@@ -869,18 +871,19 @@ description: [invalid yaml syntax: unclosed bracket
869871

870872
const mockHandler = vi.fn().mockReturnValue({ response: "ok" });
871873
const request: any = {
872-
systemPrompt: "Original system prompt content",
874+
systemMessage: new SystemMessage("Original system prompt content"),
873875
state: { skillsMetadata: [] },
874876
};
875877

876878
middleware.wrapModelCall!(request, mockHandler);
877879

878880
const modifiedRequest = mockHandler.mock.calls[0][0];
879881
// Original prompt should come before skills section
880-
const originalIndex = modifiedRequest.systemPrompt.indexOf(
882+
const originalIndex = modifiedRequest.systemMessage.text.indexOf(
881883
"Original system prompt content",
882884
);
883-
const skillsIndex = modifiedRequest.systemPrompt.indexOf("Skills System");
885+
const skillsIndex =
886+
modifiedRequest.systemMessage.text.indexOf("Skills System");
884887
expect(originalIndex).toBeLessThan(skillsIndex);
885888
});
886889
});
@@ -911,16 +914,16 @@ description: [invalid yaml syntax: unclosed bracket
911914
// Step 2: Inject skills into prompt
912915
const mockHandler = vi.fn().mockReturnValue({ response: "ok" });
913916
const request: any = {
914-
systemPrompt: "You are a helpful assistant.",
917+
systemMessage: new SystemMessage("You are a helpful assistant."),
915918
state: stateUpdate,
916919
};
917920

918921
middleware.wrapModelCall!(request, mockHandler);
919922

920923
const modifiedRequest = mockHandler.mock.calls[0][0];
921-
expect(modifiedRequest.systemPrompt).toContain("web-research");
922-
expect(modifiedRequest.systemPrompt).toContain("code-review");
923-
expect(modifiedRequest.systemPrompt).toContain(
924+
expect(modifiedRequest.systemMessage.text).toContain("web-research");
925+
expect(modifiedRequest.systemMessage.text).toContain("code-review");
926+
expect(modifiedRequest.systemMessage.text).toContain(
924927
"You are a helpful assistant",
925928
);
926929
});
@@ -955,15 +958,15 @@ description: [invalid yaml syntax: unclosed bracket
955958
// Step 2: wrapModelCall should use the restored skills from state
956959
const mockHandler = vi.fn().mockReturnValue({ response: "ok" });
957960
const request: any = {
958-
systemPrompt: "Base prompt",
961+
systemMessage: new SystemMessage("Base prompt"),
959962
state: checkpointState,
960963
};
961964

962965
middleware.wrapModelCall!(request, mockHandler);
963966

964967
const modifiedRequest = mockHandler.mock.calls[0][0];
965-
expect(modifiedRequest.systemPrompt).toContain("restored-skill");
966-
expect(modifiedRequest.systemPrompt).toContain(
968+
expect(modifiedRequest.systemMessage.text).toContain("restored-skill");
969+
expect(modifiedRequest.systemMessage.text).toContain(
967970
"Restored from checkpoint",
968971
);
969972
});

libs/deepagents/src/middleware/skills.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -574,13 +574,10 @@ export function createSkillsMiddleware(options: SkillsMiddlewareOptions) {
574574
skillsLocations,
575575
).replace("{skills_list}", skillsList);
576576

577-
// Append to existing system prompt
578-
const currentSystemPrompt = request.systemPrompt || "";
579-
const newSystemPrompt = currentSystemPrompt
580-
? `${currentSystemPrompt}\n\n${skillsSection}`
581-
: skillsSection;
577+
// Combine with existing system message
578+
const newSystemMessage = request.systemMessage.concat(skillsSection);
582579

583-
return handler({ ...request, systemPrompt: newSystemPrompt });
580+
return handler({ ...request, systemMessage: newSystemMessage });
584581
},
585582
});
586583
}

libs/deepagents/src/skills/index.int.test.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { describe, it, expect, beforeEach, afterEach } from "vitest";
22
import fs from "node:fs";
33
import path from "node:path";
44
import os from "node:os";
5+
import { SystemMessage } from "@langchain/core/messages";
56
import { createSettings } from "../config.js";
67
import { listSkills } from "./loader.js";
78
import { createSkillsMiddleware } from "../middleware/skills.js";
@@ -72,19 +73,20 @@ Use this skill when the user asks about integration testing.
7273
expect(stateUpdate!.skillsMetadata[0].name).toBe("my-skill");
7374

7475
// Step 5: Verify skills are injected into system prompt
75-
let capturedPrompt = "";
76+
let capturedMessage: SystemMessage | undefined;
7677
const mockHandler = (req: any) => {
77-
capturedPrompt = req.systemPrompt;
78+
capturedMessage = req.systemMessage;
7879
return { response: "ok" };
7980
};
8081
middleware.wrapModelCall!(
8182
{
82-
systemPrompt: "Base prompt",
83+
systemMessage: new SystemMessage("Base prompt"),
8384
state: stateUpdate,
8485
} as any,
8586
mockHandler as any,
8687
);
8788

89+
const capturedPrompt = capturedMessage!.text;
8890
expect(capturedPrompt).toContain("my-skill");
8991
expect(capturedPrompt).toContain("A test skill for integration testing");
9092
// Check for the new format with priority indicator
@@ -241,21 +243,22 @@ description: Test skill
241243
// Run wrapModelCall for both in sequence
242244
let finalPrompt = "";
243245

244-
// First, memory middleware
246+
// First, memory middleware (uses systemPrompt string API)
245247
memoryMiddleware.wrapModelCall!(
246248
{
247249
systemPrompt: "Base prompt",
248250
state: combinedState,
249251
} as any,
250252
((req: any) => {
251-
// Then, skills middleware
253+
// Then, skills middleware (uses systemMessage API)
254+
// Bridge: wrap memory middleware's string output as SystemMessage
252255
skillsMiddleware.wrapModelCall!(
253256
{
254-
systemPrompt: req.systemPrompt,
257+
systemMessage: new SystemMessage(req.systemPrompt),
255258
state: combinedState,
256259
} as any,
257260
((innerReq: any) => {
258-
finalPrompt = innerReq.systemPrompt;
261+
finalPrompt = innerReq.systemMessage.text;
259262
return { response: "ok" };
260263
}) as any,
261264
);

0 commit comments

Comments
 (0)