diff --git a/src/main/services/agent-tools.ts b/src/main/services/agent-tools.ts index 9835f0e7..d58e8bc1 100644 --- a/src/main/services/agent-tools.ts +++ b/src/main/services/agent-tools.ts @@ -200,12 +200,12 @@ export const AGENT_TOOLS: ToolDefinition[] = [ }, { name: "list_skills", - description: "See the company's skill library and who holds each skill. You carry every skill automatically as CEO; teammates only run with skills you grant them.", + description: "See the company's skill library and who holds each skill. A skill joins an agent's runs only once granted, the CEO included; house-style skills are carried by everyone.", parameters: { type: "object", additionalProperties: false, properties: {}, required: [] }, }, { name: "grant_skill", - description: "CEO only: grant a teammate one of the company's skills so its instructions join their next run. Match the skill to the job.", + description: "CEO only: grant a teammate, or yourself, one of the company's skills so its instructions join their next run. Match the skill to the job.", parameters: { type: "object", additionalProperties: false, diff --git a/src/main/services/task-prompts.ts b/src/main/services/task-prompts.ts index 3f35f241..a793ba7b 100644 --- a/src/main/services/task-prompts.ts +++ b/src/main/services/task-prompts.ts @@ -132,7 +132,7 @@ function capabilityBriefing(input: AgentInstructionContext): string { ); if (input.isCeo) { lines.push( - "You are the CEO: you automatically carry every connected service, and access for everyone else is yours to manage — when you delegate work that needs an external service, grant it first with grant_connector and take it back with revoke_connector when done. The company also ships a built-in skill library: staff every hire with the skills their role needs by passing skills to hire_agent (several per person is normal), and adjust later with grant_skill / revoke_skill. A teammate without the grant cannot use the tool or skill, so grant before you assign. The humanizer skill is company house style — every agent, you included, carries it automatically and must write like a person, not like an AI.", + "You are the CEO: you automatically carry every connected service, and access for everyone else is yours to manage — when you delegate work that needs an external service, grant it first with grant_connector and take it back with revoke_connector when done. The company also ships a built-in skill library: staff every hire with the skills their role needs by passing skills to hire_agent (several per person is normal), and adjust later with grant_skill / revoke_skill. You run only with skills granted to you too: grant yourself a playbook before you rely on it. A teammate without the grant cannot use the tool or skill, so grant before you assign. The humanizer skill is company house style — every agent, you included, carries it automatically and must write like a person, not like an AI.", ); } else if (input.connectorNames.length === 0) { lines.push( diff --git a/tests/unit/skill-grants.test.ts b/tests/unit/skill-grants.test.ts index a9435e8c..695286a8 100644 --- a/tests/unit/skill-grants.test.ts +++ b/tests/unit/skill-grants.test.ts @@ -6,7 +6,7 @@ import { DatabaseConnection } from "../../src/main/database/connection"; import { OrganizationService } from "../../src/main/services/organization-service"; import { ChatService } from "../../src/main/services/chat-service"; import { SkillService } from "../../src/main/services/skill-service"; -import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools"; +import { AGENT_TOOLS, dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools"; function call(name: string, args: Record) { return { id: `call_${randomUUID()}`, name, arguments: args }; @@ -75,6 +75,20 @@ function importSkill(h: Harness, name: string, slug: string): void { } describe("skill grants", () => { + test("the CEO runs only with granted skills, as the tool text says, and can grant itself", () => { + const h = harness(); + importSkill(h, "Release Gate", "release-gate"); + const listSkills = AGENT_TOOLS.find((tool) => tool.name === "list_skills"); + expect(listSkills?.description).not.toContain("every skill automatically"); + expect(listSkills?.description).toContain("the CEO included"); + const skills = new SkillService(h.connection.db, ""); + expect(skills.hasRuntimeSkills(h.orgId, h.ceoId)).toBe(false); + expect(dispatchAgentTool(h.deps, h.ceoCtx, call("grant_skill", { agent_name: "CEO", skill: "release-gate" }))) + .toBe("CEO now has the Release Gate skill; it joins their next run."); + expect(skills.hasRuntimeSkills(h.orgId, h.ceoId)).toBe(true); + h.connection.close(); + }); + test("the CEO grants and revokes skills for teammates", () => { const h = harness(); importSkill(h, "Release Gate", "release-gate");