Skip to content

Commit 47ce452

Browse files
committed
perf(agents): slim Skill Workshop prompt section to its routing contract (#100449)
Prompt section drops from 13 lines to 2 (2128 -> 319 bytes); action mechanics move into the skill_workshop tool schema (+25 bytes), for a net -1784 bytes per request on affected sessions. Routing contract and approval flow unchanged.
1 parent 927bbeb commit 47ce452

4 files changed

Lines changed: 31 additions & 42 deletions

File tree

src/agents/skill-workshop-prompt.ts

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,8 @@ export const SKILL_WORKSHOP_TOOL_NAME = "skill_workshop";
88
export function buildSkillWorkshopPromptSection(): string[] {
99
return [
1010
"## Skill Workshop",
11-
"Use `skill_workshop` when the user wants to create, update, revise, list, inspect, apply, reject, or quarantine a reusable skill, Skill Workshop proposal, playbook, workflow, procedure, or durable instruction.",
12-
"Treat a request as durable when it should be saved, repeated, proposed, installed later, shared as a skill, or used as a standing workflow instead of answered once in chat.",
13-
"Do not create or change skill proposal files manually with `write`, `edit`, `exec`, shell commands, or direct filesystem operations. The final proposal artifact must go through `skill_workshop`.",
14-
"Use `action=create` for a new skill, `action=update` for an existing approved/live skill, and `action=revise` for an existing pending proposal; keep `description` under 160 bytes and `proposal_content` within the configured body limit.",
15-
"For `action=update`, pass a concise `description` when the existing live skill description should be shortened in the proposal listing.",
16-
"For `action=revise`, pass `proposal_id` when known. If it is not known, pass the proposal or skill name in `name` so `skill_workshop` can resolve the pending proposal or return candidates.",
17-
"Use `action=list` or `action=inspect` only for pending proposal discovery/inspection. Do not use filesystem search for proposal discovery.",
18-
"If the user names an existing live skill, read or view that skill when needed for context, but create the update proposal through `skill_workshop`.",
19-
"Generated skills are pending proposals by default. Do not apply, install, approve, enable, or write into live skills unless the user explicitly asks for that separate action.",
20-
"Use `action=apply`, `action=reject`, or `action=quarantine` only after the user explicitly asks to approve/use/apply, reject, or quarantine a specific proposal. Pass `proposal_id`; if it is not known, use `action=list` or `action=inspect` first.",
21-
"Do not apply, reject, or quarantine proposals manually with filesystem operations or shell commands. Proposal lifecycle changes must use `skill_workshop`.",
22-
"You may gather context first, but the durable proposal write or lifecycle change must use `skill_workshop`.",
11+
"Route durable skill work — creating, updating, or managing reusable skills, playbooks, or standing workflows — through the `skill_workshop` tool; never write proposal or skill files directly.",
12+
"Generated skills are pending proposals. Apply, reject, or quarantine only when the user explicitly asks.",
2313
"",
2414
];
2515
}

src/agents/system-prompt.test.ts

Lines changed: 12 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
66
import { typedCases } from "../test-utils/typed-cases.js";
77
import { listDeliverableMessageChannels } from "../utils/message-channel.js";
88
import { resolveAgentPromptSurfaceForSessionKey } from "./prompt-surface.js";
9+
import { buildSkillWorkshopPromptSection } from "./skill-workshop-prompt.js";
910
import { buildSubagentSystemPrompt } from "./subagent-system-prompt.js";
1011
import {
1112
buildAgentBootstrapSystemContext,
@@ -733,12 +734,20 @@ describe("buildAgentSystemPrompt", () => {
733734
});
734735

735736
it("instructs models to use skill_workshop only when the tool is available", () => {
737+
const section = buildSkillWorkshopPromptSection();
738+
const sectionText = section.join("\n");
739+
expect(section.length).toBeLessThanOrEqual(4);
740+
expect(sectionText).toContain("Route durable skill work");
741+
expect(sectionText).toContain("through the `skill_workshop` tool");
742+
expect(sectionText).toContain("Generated skills are pending proposals.");
743+
expect(sectionText).toContain("only when the user explicitly asks");
744+
736745
const withoutTool = buildAgentSystemPrompt({
737746
workspaceDir: "/tmp/openclaw",
738747
toolNames: ["read"],
739748
});
740749
expect(withoutTool).not.toContain("## Skill Workshop");
741-
expect(withoutTool).not.toContain("use `skill_workshop`");
750+
expect(withoutTool).not.toContain("Route durable skill work");
742751

743752
const withTool = buildAgentSystemPrompt({
744753
workspaceDir: "/tmp/openclaw",
@@ -748,32 +757,8 @@ describe("buildAgentSystemPrompt", () => {
748757
"- skill_workshop: Create, update, revise, list, inspect, apply, reject, or quarantine Skill Workshop proposals",
749758
);
750759
expect(withTool).toContain("## Skill Workshop");
751-
expect(withTool).toContain(
752-
"Use `skill_workshop` when the user wants to create, update, revise, list, inspect, apply, reject, or quarantine a reusable skill, Skill Workshop proposal, playbook, workflow, procedure, or durable instruction.",
753-
);
754-
expect(withTool).toContain(
755-
"Treat a request as durable when it should be saved, repeated, proposed, installed later, shared as a skill, or used as a standing workflow instead of answered once in chat.",
756-
);
757-
expect(withTool).toContain(
758-
"Do not create or change skill proposal files manually with `write`, `edit`, `exec`, shell commands, or direct filesystem operations.",
759-
);
760-
expect(withTool).toContain("keep `description` under 160 bytes");
761-
expect(withTool).toContain("`proposal_content` within the configured body limit");
762-
expect(withTool).toContain(
763-
"Use `action=list` or `action=inspect` only for pending proposal discovery/inspection. Do not use filesystem search for proposal discovery.",
764-
);
765-
expect(withTool).toContain("`action=revise` for an existing pending proposal");
766-
expect(withTool).toContain("pass the proposal or skill name in `name`");
767-
expect(withTool).toContain(
768-
"Use `action=apply`, `action=reject`, or `action=quarantine` only after the user explicitly asks to approve/use/apply, reject, or quarantine a specific proposal.",
769-
);
770-
expect(withTool).toContain("Generated skills are pending proposals by default.");
771-
expect(withTool).toContain(
772-
"Do not apply, reject, or quarantine proposals manually with filesystem operations or shell commands.",
773-
);
774-
expect(withTool).toContain(
775-
"You may gather context first, but the durable proposal write or lifecycle change must use `skill_workshop`.",
776-
);
760+
expect(withTool).toContain("Route durable skill work");
761+
expect(withTool).toContain("Generated skills are pending proposals.");
777762
});
778763

779764
it("appends available skills when provided", () => {

src/agents/tools/skill-workshop-tool.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,20 @@ afterEach(async () => {
2929
});
3030

3131
describe("skill_workshop tool", () => {
32+
it("describes action selection and pending-proposal discovery in its schema", () => {
33+
const tool = createSkillWorkshopTool({ workspaceDir: "/tmp/openclaw" });
34+
const schema = JSON.stringify(tool.parameters);
35+
36+
expect(schema).toContain("create = new skill");
37+
expect(schema).toContain("update = existing live skill");
38+
expect(schema).toContain("revise = existing pending proposal");
39+
expect(schema).toContain("not filesystem search");
40+
expect(schema).toContain("when proposal_id is unknown");
41+
expect(schema).toContain("returns candidates");
42+
expect(schema).toContain("max 160 bytes");
43+
expect(schema).toContain("shortens the proposal listing entry");
44+
});
45+
3246
it("is exposed in the OpenClaw tool set", async () => {
3347
const workspaceDir = await tempDirs.make("openclaw-skill-workshop-tool-");
3448
const tools = createOpenClawTools({

src/agents/tools/skill-workshop-tool.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ const SkillWorkshopToolSchema = Type.Object(
5555
{
5656
action: stringEnum(SKILL_WORKSHOP_ACTIONS, {
5757
description:
58-
"create for a new skill proposal, update for an existing skill, revise for a pending proposal, list or inspect proposals for proposal discovery, apply/reject/quarantine for explicit proposal lifecycle actions.",
58+
"create = new skill; update = existing live skill; revise = existing pending proposal; list/inspect discover pending proposals (not filesystem search); apply/reject/quarantine are explicit lifecycle actions.",
5959
}),
6060
proposal_id: Type.Optional(
6161
Type.String({
@@ -66,7 +66,7 @@ const SkillWorkshopToolSchema = Type.Object(
6666
name: Type.Optional(
6767
Type.String({
6868
description:
69-
"Skill/proposal name. Required for action=create; optional resolver for action=inspect or action=revise when proposal_id is unknown.",
69+
"Skill/proposal name. Required for create; for inspect/revise when proposal_id is unknown, resolves a pending proposal or returns candidates.",
7070
}),
7171
),
7272
query: Type.Optional(Type.String({ description: "Optional query for action=list." })),
@@ -86,7 +86,7 @@ const SkillWorkshopToolSchema = Type.Object(
8686
Type.String({
8787
maxLength: 160,
8888
description:
89-
"Skill description for action=create, action=update, or action=revise. Keep it concise; max 160 bytes.",
89+
"Skill description for create/update/revise; max 160 bytes. On update, concise text shortens the proposal listing entry.",
9090
}),
9191
),
9292
skill_name: Type.Optional(

0 commit comments

Comments
 (0)