Assign default skills when a hire is approved
The CEO's operating prompt instructed the model to inspect each direct report every wake, compare assigned skills to role, and POST missing ones — business logic routed through the LLM at O(reports) API calls per heartbeat. The guarantee was unreliable and expensive. Move the default-kit decision to hire approval, the one deterministic event that knows both the agent's department and the company's seeded skill catalogue. A static department → built-in-category mapping selects the right bundle; the CEO remains free to override per agent later. Remove the stale Priority 2 block from the CEO prompt and collapse the renumbered priorities.
This commit is contained in:
1 parent
5dd74a477d
commit
a3006f0b38
3 files changed
+119
-17
No files matched your search
@@ -3,6 +3,7 @@ import type { AppDatabase } from "../database";
|
||||
import type { NotificationOptions } from "../notification-service";
|
||||
import { extractApprovalDocumentIds } from "@shared/approval-documents";
|
||||
import { notifyHireApproved } from "../hire-hook";
|
||||
import { autoAssignSkillsForHire } from "../skills/auto-assign-on-hire";
|
||||
import type { AutomationEventContext } from "./types";
|
||||
import { normalizeWorkflowSteps } from "./automation-workflow";
|
||||
|
||||
@@ -97,6 +98,29 @@ export function handleApprovalResolved(
|
||||
const agent = db.getAgent(approval.relatedAgentId);
|
||||
|
||||
if (agent.status === "idle") {
|
||||
// Deterministic default skill kit. The CEO's operating prompt
|
||||
// used to carry this responsibility ("ensure every direct report
|
||||
// has skills"), turning O(reports) API calls into a per-wake
|
||||
// tax. At hire approval time we know the agent's department and
|
||||
// the company's seeded catalogue — assign the default bundle so
|
||||
// the new hire's first run already has the right SKILL.md
|
||||
// manifest.
|
||||
const { assignedSkillIds } = autoAssignSkillsForHire(db, {
|
||||
agentId: agent.id,
|
||||
companyId,
|
||||
department: agent.department,
|
||||
});
|
||||
if (assignedSkillIds.length > 0) {
|
||||
db.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
action: "agent.skills_defaulted",
|
||||
entityType: "agent",
|
||||
entityId: agent.id,
|
||||
detail: `${assignedSkillIds.length} default skill(s) assigned on hire.`,
|
||||
});
|
||||
}
|
||||
|
||||
db.addActivity({
|
||||
companyId,
|
||||
actor: "system",
|
||||
|
||||
@@ -20,16 +20,7 @@ If there are NO active goals, this is your top priority — create strategic goa
|
||||
-d '{"title":"Goal title","description":"Measurable success criteria","ownerAgentId":"{{agentId}}","status":"active"}'
|
||||
Then decompose each goal into projects, and each project into tasks assigned to department leads.
|
||||
|
||||
## Priority 2: Ensure All Agents Have Skills (SOP-002b)
|
||||
Every agent needs domain skills to produce quality work. Check and fix immediately:
|
||||
1. For EACH of your direct reports, check their skills: GET /api/agents/{agentId}/skills
|
||||
2. If an agent has ZERO skills assigned, this is urgent — assign skills NOW:
|
||||
a. GET /api/companies/{{companyId}}/skills to list available skills
|
||||
b. For each skill matching the agent's role: POST /api/agents/{agentId}/skills with {"skillId": "<id>"}
|
||||
c. Match skills to roles — CTO gets engineering skills, CMO gets marketing skills, etc.
|
||||
3. This MUST happen before delegating any work — agents without skills produce poor output
|
||||
|
||||
## Priority 3: Review Completed Work (SOP-004)
|
||||
## Priority 2: Review Completed Work (SOP-004)
|
||||
When tasks from your reports have status "in_review":
|
||||
- Read ALL comments on the task to understand what was done
|
||||
- Review any documents the agent created: GET /api/companies/{{companyId}}/documents
|
||||
@@ -42,7 +33,7 @@ When tasks from your reports have status "in_review":
|
||||
- This is YOUR HIGHEST PRIORITY after standup — reviews unblock your entire team
|
||||
- NEVER let review requests sit — they cascade-block everyone downstream
|
||||
|
||||
## Priority 4: Goal Decomposition & Delegation (SOP-003)
|
||||
## Priority 3: Goal Decomposition & Delegation (SOP-003)
|
||||
When you have active goals without projects or tasks:
|
||||
1. Create a project for each major workstream:
|
||||
POST /api/companies/{{companyId}}/projects with goalId, leadAgentId, description
|
||||
@@ -56,21 +47,19 @@ When you have active goals without projects or tasks:
|
||||
- COO/Operations → process, coordination, company cadence
|
||||
- CFO/Finance → budget, spend tracking, financial health
|
||||
- Other roles → match by capabilities description
|
||||
- Before delegating, check agent skills: GET /api/agents/{agentId}/skills
|
||||
- If the agent lacks a needed skill, assign it first: POST /api/agents/{agentId}/skills with {"skillId": "<id>"}
|
||||
- Skills give agents domain expertise (coding standards, frameworks, processes) — always ensure the right skills are assigned before sending work
|
||||
- Default domain skills are assigned automatically when a hire is approved; only call GET /api/agents/{agentId}/skills or POST /api/agents/{agentId}/skills when you have a reason to override that default for a specific agent
|
||||
5. Leave a delegation summary comment on each parent task
|
||||
6. Wake the assigned agents so they start immediately:
|
||||
POST /api/agents/{agentId}/wake with reason explaining the new assignment
|
||||
|
||||
## Priority 5: Unblock & Coordinate
|
||||
## Priority 4: Unblock & Coordinate
|
||||
- Review blocked tasks: read the blocker comments and take action
|
||||
- For credential needs: create secret_access approvals via POST /api/companies/{{companyId}}/approvals
|
||||
- For cross-department dependencies: send direct messages to the relevant department lead
|
||||
- For Board-level decisions: create an approval with clear context and options
|
||||
- Review failed runs from your reports: check what went wrong and decide whether to retry or reassign
|
||||
|
||||
## Priority 6: Grow the Team
|
||||
## Priority 5: Grow the Team
|
||||
When workload exceeds capacity or new capabilities are needed:
|
||||
- Assess: count open tasks vs active agents per department
|
||||
- If a department is overloaded: propose hiring via POST /api/companies/{{companyId}}/agents
|
||||
@@ -78,7 +67,7 @@ When workload exceeds capacity or new capabilities are needed:
|
||||
- Each hire needs: a distinct agent name, clear role/title, specific capabilities, budget justification, department assignment, and the correct manager (reportsTo)
|
||||
- After hire approval, create onboarding tasks for the new agent
|
||||
|
||||
## Priority 7: Strategic Communication & Reporting
|
||||
## Priority 6: Strategic Communication & Reporting
|
||||
- Schedule all-hands meetings when major milestones are reached:
|
||||
POST /api/companies/{{companyId}}/meetings with type "all_hands"
|
||||
- Create status_report documents summarizing company progress:
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
import type { AppDatabase } from "../database";
|
||||
import type { DepartmentId } from "@shared/types";
|
||||
|
||||
/**
|
||||
* Server-side default skill assignment at hire approval time.
|
||||
*
|
||||
* Before this module existed, the CEO's operating prompt was explicitly
|
||||
* told to inspect every direct report every wake, fetch the skill
|
||||
* catalogue, and POST /agents/{id}/skills for any unassigned role-
|
||||
* relevant entry. That is business logic routed through an LLM — wasted
|
||||
* tokens and an unreliable guarantee. The hire approval is the
|
||||
* deterministic moment to give a fresh agent a sensible default kit;
|
||||
* the CEO is still free to add or remove skills later.
|
||||
*
|
||||
* The mapping is department → built-in skill categories. Each seeded
|
||||
* company skill carries its category in `metadata.builtinCategory`
|
||||
* (see `skills/runtime.ts#seedBuiltinSkills`); we match against that
|
||||
* tag so a company that imported extra skills under the same category
|
||||
* inherits them too, and deletes remove the assignment pool cleanly.
|
||||
*/
|
||||
|
||||
const CATEGORIES_BY_DEPARTMENT: Record<DepartmentId, readonly string[]> = {
|
||||
executive: ["basic-for-all", "document-process"],
|
||||
engineering: ["software-engineer", "basic-for-all"],
|
||||
product: ["frontend-design", "basic-for-all", "document-process"],
|
||||
design: ["frontend-design", "basic-for-all"],
|
||||
marketing: ["marketing", "basic-for-all", "document-process"],
|
||||
sales: ["marketing", "basic-for-all", "document-process"],
|
||||
hr: ["basic-for-all", "document-process"],
|
||||
finance: ["basic-for-all", "document-process"],
|
||||
legal: ["basic-for-all", "document-process"],
|
||||
operations: ["software-engineer", "basic-for-all", "document-process"],
|
||||
customer_support: ["basic-for-all", "document-process"],
|
||||
research: ["basic-for-all", "document-process"],
|
||||
};
|
||||
|
||||
function readBuiltinCategory(metadata: unknown): string | null {
|
||||
if (!metadata) return null;
|
||||
if (typeof metadata === "string") {
|
||||
try {
|
||||
const parsed = JSON.parse(metadata) as Record<string, unknown>;
|
||||
const value = parsed?.builtinCategory;
|
||||
return typeof value === "string" ? value : null;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
if (typeof metadata === "object") {
|
||||
const value = (metadata as Record<string, unknown>).builtinCategory;
|
||||
return typeof value === "string" ? value : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export interface AutoAssignSkillsInput {
|
||||
agentId: string;
|
||||
companyId: string;
|
||||
department: DepartmentId | string | null | undefined;
|
||||
}
|
||||
|
||||
export interface AutoAssignSkillsResult {
|
||||
assignedSkillIds: string[];
|
||||
}
|
||||
|
||||
export function autoAssignSkillsForHire(
|
||||
db: AppDatabase,
|
||||
input: AutoAssignSkillsInput,
|
||||
): AutoAssignSkillsResult {
|
||||
const department = (input.department ?? "") as DepartmentId;
|
||||
const categories = CATEGORIES_BY_DEPARTMENT[department];
|
||||
if (!categories || categories.length === 0) {
|
||||
return { assignedSkillIds: [] };
|
||||
}
|
||||
|
||||
const alreadyAssigned = new Set(db.listAgentSkillAssignments(input.agentId));
|
||||
const wanted = new Set(categories);
|
||||
const catalogue = db.listCompanySkills(input.companyId);
|
||||
|
||||
const assigned: string[] = [];
|
||||
for (const skill of catalogue) {
|
||||
if (alreadyAssigned.has(skill.id)) continue;
|
||||
const category = readBuiltinCategory(skill.metadata);
|
||||
if (!category || !wanted.has(category)) continue;
|
||||
db.assignSkillToAgent(input.agentId, skill.id);
|
||||
assigned.push(skill.id);
|
||||
}
|
||||
|
||||
return { assignedSkillIds: assigned };
|
||||
}
|
||||
Reference in new issue
Block a user