diff --git a/src/main/prompt-builder/api-section.ts b/src/main/prompt-builder/api-section.ts index a73936a5..baedb628 100644 --- a/src/main/prompt-builder/api-section.ts +++ b/src/main/prompt-builder/api-section.ts @@ -13,10 +13,52 @@ import meetingsEn from "./api-docs/meetings.en.md?raw"; import agentsEn from "./api-docs/agents.en.md?raw"; import sprintsEn from "./api-docs/sprints.en.md?raw"; -const API_DOC_SECTIONS: string[] = [ - headerEn, agentControlEn, tasksEn, goalsProjectsEn, approvalsEn, - messagesEn, filesEn, knowledgeEn, meetingsEn, agentsEn, sprintsEn, -]; +/** + * Role-scoped API documentation bundles. + * + * Every agent used to receive the full endpoint catalogue. Individual + * contributors paid the token cost for hiring / budget / org-design + * endpoints they had no authority to call; cache churn followed any + * api-docs edit. Scope each bundle to the operations the role can + * actually perform under its decision authority. + */ +export type ApiAudience = "executive" | "manager" | "individual_contributor"; + +const SECTIONS_BY_ROLE: Record = { + executive: [ + headerEn, + agentControlEn, + tasksEn, + goalsProjectsEn, + approvalsEn, + messagesEn, + filesEn, + knowledgeEn, + meetingsEn, + agentsEn, + sprintsEn, + ], + manager: [ + headerEn, + agentControlEn, + tasksEn, + goalsProjectsEn, + approvalsEn, + messagesEn, + filesEn, + knowledgeEn, + meetingsEn, + sprintsEn, + ], + individual_contributor: [ + headerEn, + tasksEn, + messagesEn, + filesEn, + knowledgeEn, + meetingsEn, + ], +}; function render(template: string, ctx: PromptContext): string { const connectorIds = ctx.allConnectors && ctx.allConnectors.length > 0 @@ -30,7 +72,9 @@ function render(template: string, ctx: PromptContext): string { .replace(/\{\{connectorIds\}\}/g, connectorIds); } -export function buildAPISection(ctx: PromptContext): string { - const body = API_DOC_SECTIONS.map((s) => render(s, ctx).trimEnd()).join("\n\n"); +export function buildAPISection(ctx: PromptContext, audience: ApiAudience): string { + const body = SECTIONS_BY_ROLE[audience] + .map((s) => render(s, ctx).trimEnd()) + .join("\n\n"); return `${body}\n${buildSocialSection(ctx)}`; } diff --git a/src/main/prompt-builder/sop-section.ts b/src/main/prompt-builder/sop-section.ts index cd241a9e..542fec14 100644 --- a/src/main/prompt-builder/sop-section.ts +++ b/src/main/prompt-builder/sop-section.ts @@ -14,15 +14,67 @@ import sop013En from "./sops/sop-013-incident.en.md?raw"; import sop014En from "./sops/sop-014-hiring-pipeline.en.md?raw"; import sop015En from "./sops/sop-015-knowledge.en.md?raw"; -const SOPS_IN_ORDER: string[] = [ - sop001En, sop002En, sop003En, sop004En, sop005En, - sop006En, sop007En, sop008En, sop009En, sop010En, - sop011En, sop012En, sop013En, sop014En, sop015En, -]; +/** + * Role-scoped SOP bundles. + * + * The system prompt previously concatenated all 15 SOPs for every agent + * regardless of role. Individual contributors were receiving the hiring + * pipeline, cadence, and budget SOPs they had no authority to act on; + * every wake paid the token bill. The prompt-cache prefix also grew + * with each SOP added over the life of the product. + * + * Each role receives the procedures it can execute under its decision + * authority (see `operating-en.ts#authorityRules`). SOPs that every + * agent must know regardless of seat — standup, communication, + * incident response, knowledge contribution — appear in every bundle. + */ + +const SOP_BY_ROLE = { + executive: [ + sop001En, // standup + sop003En, // delegation + sop004En, // code-review (CEO still reviews top-level deliverables) + sop005En, // hiring + sop006En, // escalation + sop007En, // communication + sop008En, // cross-department + sop009En, // documents + sop010En, // budget + sop012En, // cadence + sop013En, // incident + sop014En, // hiring-pipeline + sop015En, // knowledge + ], + manager: [ + sop001En, // standup + sop002En, // task-lifecycle + sop003En, // delegation + sop004En, // code-review + sop006En, // escalation + sop007En, // communication + sop008En, // cross-department + sop009En, // documents + sop011En, // quality + sop013En, // incident + sop015En, // knowledge + ], + individual_contributor: [ + sop001En, // standup + sop002En, // task-lifecycle + sop004En, // code-review + sop006En, // escalation + sop007En, // communication + sop009En, // documents + sop011En, // quality + sop015En, // knowledge + ], +} as const; + +export type SopAudience = keyof typeof SOP_BY_ROLE; const SOP_HEADING = "# Standard Operating Procedures"; -export function buildSOPSection(): string { - const body = SOPS_IN_ORDER.map((text) => text.trimEnd()).join("\n\n"); +export function buildSOPSection(audience: SopAudience): string { + const body = SOP_BY_ROLE[audience].map((text) => text.trimEnd()).join("\n\n"); return `${SOP_HEADING}\n\n${body}\n`; } diff --git a/src/main/prompt-builder/system-prompt.ts b/src/main/prompt-builder/system-prompt.ts index b680d34a..382cab94 100644 --- a/src/main/prompt-builder/system-prompt.ts +++ b/src/main/prompt-builder/system-prompt.ts @@ -198,8 +198,9 @@ export function buildSystemPrompt(ctx: PromptContext): string { const approvalsSection = buildPendingApprovalsSection(ctx.pendingApprovals); if (approvalsSection) sections.push(approvalsSection); - sections.push(buildSOPSection()); - sections.push(buildAPISection(ctx)); + const audience = isCEO ? "executive" : isManager ? "manager" : "individual_contributor"; + sections.push(buildSOPSection(audience)); + sections.push(buildAPISection(ctx, audience)); if (ctx.recentComments.length > 0) { const lines = ctx.recentComments.slice(0, 10)