Grow a real OKR company: officers, departments, ranks - and a stable chart

Researched how OKR-run companies cascade (company objective -> C-suite
officers per function -> department objectives -> individual key
results) and encoded that shape into how the CEO builds the org.

- hire_agent gains seniority (director/lead/staff; c_suite stays
  reserved for the CEO's privileges) and a business-card title, so the
  CEO hires a CTO/CFO/CMO as directors instead of a flat pool of ICs
- CEO persona + goal kickoff now direct the OKR cascade: hire 2-4
  officers, hand each a department objective, let them build teams;
  department heads get their own doctrine (hire leads/staff, cascade
  key results as tasks, report up)
- Migration 050 adds Finance and Sales to every org's canonical
  departments (backfilled for existing DBs) so a real C-suite has
  somewhere to hire
- Org chart viewport is a fixed-height scroll region: zooming rescales
  the diagram inside it instead of resizing the card
- Unit test drives a live three-level chain: CEO -> CTO (director) ->
  Engineering Manager (lead) -> staff, with rank/title persisted
This commit is contained in:
ZacharyZhang-NY committed 2026-07-08 21:47:07 -04:00
1 parent c3924af9ac
commit 2bb2acd635
9 files changed
+125 -10

No files matched your search

@@ -0,0 +1,24 @@
export const financeSalesDepartmentsSchema = `
insert into org_departments(id, org_id, name, lead_agent_id, headcount_limit, monthly_budget_limit_cents, created_at, updated_at)
select 'dep_fin_' || o.id, o.id, 'Finance', null, 12, 2500000, datetime('now'), datetime('now')
from organizations o
where not exists (select 1 from org_departments d where d.org_id = o.id and lower(d.name) = 'finance');
insert into org_departments(id, org_id, name, lead_agent_id, headcount_limit, monthly_budget_limit_cents, created_at, updated_at)
select 'dep_sal_' || o.id, o.id, 'Sales', null, 12, 2500000, datetime('now'), datetime('now')
from organizations o
where not exists (select 1 from org_departments d where d.org_id = o.id and lower(d.name) = 'sales');
insert into hiring_policies(id, org_id, department_name, max_headcount, monthly_budget_limit_cents, require_approval_above_cents, created_at, updated_at)
select 'hpl_fin_' || o.id, o.id, 'Finance', 12, 2500000, 500000, datetime('now'), datetime('now')
from organizations o
where not exists (select 1 from hiring_policies hp where hp.org_id = o.id and lower(hp.department_name) = 'finance');
insert into hiring_policies(id, org_id, department_name, max_headcount, monthly_budget_limit_cents, require_approval_above_cents, created_at, updated_at)
select 'hpl_sal_' || o.id, o.id, 'Sales', 12, 2500000, 500000, datetime('now'), datetime('now')
from organizations o
where not exists (select 1 from hiring_policies hp where hp.org_id = o.id and lower(hp.department_name) = 'sales');
insert into schema_migrations(version, name, applied_at)
values (50, 'finance_sales_departments', datetime('now'));
`;
+2
View File
@@ -48,6 +48,7 @@ import { composioKeyPurgeSchema } from "./migrations/046-composio-key-purge";
import { authConfigPurgeSchema } from "./migrations/047-auth-config-purge";
import { uiPreferencesSchema } from "./migrations/048-ui-preferences";
import { avatarsSchema } from "./migrations/049-avatars";
import { financeSalesDepartmentsSchema } from "./migrations/050-finance-sales-departments";
interface Migration {
version: number;
@@ -105,6 +106,7 @@ const MIGRATIONS: Migration[] = [
{ version: 47, name: "auth_config_purge", sql: authConfigPurgeSchema },
{ version: 48, name: "ui_preferences", sql: uiPreferencesSchema },
{ version: 49, name: "avatars", sql: avatarsSchema },
{ version: 50, name: "finance_sales_departments", sql: financeSalesDepartmentsSchema },
];
const SCHEMA_VERSION = MIGRATIONS[MIGRATIONS.length - 1].version;
+14 -4
View File
@@ -40,14 +40,16 @@ export const AGENT_TOOLS: ToolDefinition[] = [
{
name: "hire_agent",
description:
"Add one direct report to your team. During team building, call this 3-5 times to staff the roles you need before delegating work. Staff every hire with the library skills their role needs (check list_skills; several are fine) — those skills become mandatory practice in the hire's runs.",
"Add one direct report to your org. Hires report to YOU, so build a real company: the CEO hires chief officers (seniority \"director\", title like 'CTO' or 'CFO') and delegates department building to them; officers hire their own managers (seniority \"lead\") and staff. Staff every hire with the library skills their role needs (check list_skills; several are fine) — those skills become mandatory practice in the hire's runs.",
parameters: {
type: "object",
additionalProperties: false,
properties: {
name: { type: "string", description: "Full name of the new agent." },
role: { type: "string", description: "Their role/title, e.g. 'Growth Marketer'." },
role: { type: "string", description: "Their role, e.g. 'Growth Marketer'." },
title: { type: "string", description: "Business-card title, e.g. 'CTO', 'Engineering Manager'. Defaults to the role." },
department: { type: "string", enum: [...ORGANIZATION_DEPARTMENTS], description: "The company department the hire belongs to." },
seniority: { type: "string", enum: ["director", "lead", "staff"], description: "Rank in the org: director for chief officers/department heads, lead for managers, staff for individual contributors. Defaults to staff." },
model: { type: "string", description: "Optional model id; defaults to your own model." },
skills: {
type: "array",
@@ -253,23 +255,31 @@ export function dispatchAgentTool(
}
}
const HIRE_SENIORITIES = new Set(["director", "lead", "staff"]);
function hireAgent(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord): string {
if (!ctx.agent.modelProviderId) throw new Error("Acting agent has no model provider to assign hires.");
const name = requireString(args.name, "name");
const role = requireString(args.role, "role");
const department = requireString(args.department, "department");
const title = optionalString(args.title) ?? role;
const seniority = (optionalString(args.seniority) ?? "staff").toLowerCase();
if (!HIRE_SENIORITIES.has(seniority)) {
throw new Error(`"seniority" must be one of director, lead, staff. (c_suite is reserved for the CEO.)`);
}
const model = optionalString(args.model) ?? ctx.agent.modelId;
const requestedSkills = optionalStringArray(args.skills, "skills");
const agentId = deps.organization.createAgent({
orgId: ctx.orgId,
name,
role,
title,
department,
modelProviderId: ctx.agent.modelProviderId,
modelId: model,
budgetCents: HIRE_BUDGET_CENTS,
managerAgentId: ctx.agent.id,
roleLevel: "staff",
roleLevel: seniority,
workspaceId: ctx.workspace?.id ?? null,
heartbeatEnabled: true,
heartbeatIntervalSec: HEARTBEAT_INTERVAL_SEC,
@@ -277,7 +287,7 @@ function hireAgent(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord)
const { granted, unknown } = bindSkillSlugs(deps, ctx.orgId, agentId, requestedSkills);
const skillNote = granted.length > 0 ? ` They carry: ${granted.join(", ")} (plus the automatic humanizer house style).` : "";
const unknownNote = unknown.length > 0 ? ` Unknown skills skipped: ${unknown.join(", ")} — run list_skills and grant_skill to fix.` : "";
return `Hired ${name} as ${role} in ${department} (agent_id=${agentId}).${skillNote}${unknownNote}`;
return `Hired ${name} as ${title} (${seniority}) in ${department}, reporting to you (agent_id=${agentId}).${skillNote}${unknownNote}`;
}
function createTask(deps: AgentToolDeps, ctx: AgentToolContext, args: JsonRecord, options: AgentToolDispatchOptions): string {
+1 -1
View File
@@ -41,7 +41,7 @@ export function goalKickoffDescription(companyName: string, goalTitle: string, g
`Company: ${companyName.trim()}`,
`Goal: ${goalTitle.trim()}`,
goalDescription.trim(),
"Read the bound workspace, create the first operating plan, delegate work, and route decisions through approvals and messages.",
"Treat this goal as the company objective and build the organization that can deliver it: hire chief officers (hire_agent, seniority \"director\", titles like CTO/CFO/CMO/COO) for the functions the goal needs, give each officer their department objective as a task, and let them build and run their own teams. Read the bound workspace, create the first operating plan, and route decisions through approvals and messages.",
].filter((line) => line.length > 0).join("\n\n");
}
+1 -1
View File
@@ -5,7 +5,7 @@ import { createId } from "../../shared/ids";
import { AuditService } from "./audit-service";
import { OperationsService } from "./operations-service";
export const ORGANIZATION_DEPARTMENTS = ["Executive", "Engineering", "Product", "Design", "Marketing", "Operations", "Support"] as const;
export const ORGANIZATION_DEPARTMENTS = ["Executive", "Engineering", "Product", "Design", "Marketing", "Sales", "Finance", "Operations", "Support"] as const;
const RISK_RANK: Record<RiskLevel, number> = { low: 0, medium: 1, high: 2 };
export class OrgGovernanceService {
+5 -2
View File
@@ -136,10 +136,13 @@ function capabilityBriefing(input: AgentInstructionContext): string {
function roleVoice(input: AgentInstructionContext): string {
if (input.isCeo) {
return "You run this company. Think in outcomes, make the call when it is yours to make, and put the right person on each piece instead of doing everything yourself — then tell the team the why, not just the what.";
return [
"You run this company. Think in outcomes, make the call when it is yours to make, and put the right person on each piece instead of doing everything yourself — then tell the team the why, not just the what.",
"Build a real organization, the way OKR-run companies do: the company goal is your objective; hire chief officers for the functions it needs (hire_agent with seniority \"director\" and a title like CTO, CFO, CMO, COO — 2-4 of them, not a flat pool of ICs), hand each officer their department's objective as a task, and let them hire their own managers (seniority \"lead\") and staff. You manage the officers; they manage their departments.",
].join(" ");
}
if (input.hasReports) {
return "You lead a team. Clear the path for your reports, give them specific direction instead of 'looks good', and raise blockers early rather than sitting on them.";
return "You lead a department. Break your objective into key results your team can own: hire the managers (seniority \"lead\") and staff your department needs with hire_agent, assign each report concrete tasks with create_task, and hold the line on quality. Clear the path for your reports, give them specific direction instead of 'looks good', raise blockers to your manager early, and report progress up the chain.";
}
return "You own your craft. Ask the question early instead of guessing, do the work properly, and when you are stuck for more than a few minutes say so in the thread instead of spinning in place.";
}
+1 -1
View File
@@ -585,7 +585,7 @@ a { color: inherit; }
font-variant-numeric: tabular-nums;
}
.org-zoom-label:hover { background: var(--hover); color: var(--text-1); }
.org-scroll { overflow: auto; max-height: 62vh; }
.org-scroll { overflow: auto; height: 56vh; min-height: 320px; }
.org-sizer { position: relative; margin: 20px auto 4px; }
.org-canvas { position: absolute; top: 0; left: 0; }
.org-node-card {
+1 -1
View File
@@ -24,7 +24,7 @@ describe("domain services", () => {
const connection = createConnection();
const rows = connection.db.prepare("select version from schema_migrations order by version asc").all() as Array<{ version: number }>;
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49]);
expect(rows.map((row) => row.version)).toEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]);
connection.close();
});
+76
View File
@@ -0,0 +1,76 @@
import { randomUUID } from "node:crypto";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, expect, test } from "vitest";
import { DatabaseConnection } from "../../src/main/database/connection";
import { OrganizationService } from "../../src/main/services/organization-service";
import { ChatService } from "../../src/main/services/chat-service";
import { dispatchAgentTool, type AgentToolContext } from "../../src/main/services/agent-tools";
function call(name: string, args: Record<string, unknown>) {
return { id: `call_${randomUUID()}`, name, arguments: args };
}
describe("org structure", () => {
test("the CEO hires titled officers who build their own departments - a three-level chain", () => {
const connection = new DatabaseConnection(join(tmpdir(), `ac-org-${randomUUID()}.db`));
const organization = new OrganizationService(connection.db);
let orgId = "";
let ceoId = "";
let providerId = "";
connection.transaction(() => {
orgId = organization.createOrganization({ organizationName: "OrbitOps", employeeName: "Yang", mission: "m", goal: "g" });
providerId = (connection.db.prepare("select id from providers where org_id = ? and provider_key = 'openai'").get(orgId) as { id: string }).id;
ceoId = organization.createAgent({
orgId, name: "CEO", role: "CEO", department: "Executive",
modelProviderId: providerId, modelId: "gpt-5.4", budgetCents: 25000, managerAgentId: null, roleLevel: "c_suite",
});
});
const deps = { db: connection.db, organization, chat: new ChatService(connection.db) };
const ctx = (agentId: string): AgentToolContext => ({
orgId,
agent: { id: agentId, modelProviderId: providerId, modelId: "gpt-5.4" },
taskProjectId: null,
taskGoalId: null,
workspace: null,
});
// Level 2: the CEO hires a chief officer with rank and title.
const hiredCto = connection.transaction(() =>
dispatchAgentTool(deps, ctx(ceoId), call("hire_agent", {
name: "Ava Sun", role: "Head of Engineering", title: "CTO", department: "Engineering", seniority: "director",
})));
expect(hiredCto).toContain("Hired Ava Sun as CTO (director) in Engineering");
const cto = connection.db.prepare("select id, title, role_level, manager_agent_id from agents where org_id = ? and name = 'Ava Sun'")
.get(orgId) as { id: string; title: string; role_level: string; manager_agent_id: string };
expect(cto).toMatchObject({ title: "CTO", role_level: "director", manager_agent_id: ceoId });
// Level 3: the officer hires into their own department.
const hiredManager = connection.transaction(() =>
dispatchAgentTool(deps, ctx(cto.id), call("hire_agent", {
name: "Ken Ito", role: "Backend Engineer", department: "Engineering", seniority: "lead", title: "Engineering Manager",
})));
expect(hiredManager).toContain("(lead)");
const manager = connection.db.prepare("select role_level, manager_agent_id, title from agents where org_id = ? and name = 'Ken Ito'")
.get(orgId) as { role_level: string; manager_agent_id: string; title: string };
expect(manager).toMatchObject({ role_level: "lead", manager_agent_id: cto.id, title: "Engineering Manager" });
// Defaults: seniority omitted means staff; c_suite is not hireable.
connection.transaction(() =>
dispatchAgentTool(deps, ctx(cto.id), call("hire_agent", { name: "Ren Park", role: "Engineer", department: "Engineering" })));
const staff = connection.db.prepare("select role_level from agents where org_id = ? and name = 'Ren Park'").get(orgId) as { role_level: string };
expect(staff.role_level).toBe("staff");
const rejected = dispatchAgentTool(deps, ctx(ceoId), call("hire_agent", {
name: "Imposter", role: "CEO 2", department: "Executive", seniority: "c_suite",
}));
expect(rejected).toContain("ERROR");
expect(rejected).toContain("reserved for the CEO");
// The org now has the real-company departments to grow into.
const departments = (connection.db.prepare("select department_name from hiring_policies where org_id = ? order by department_name").all(orgId) as Array<{ department_name: string }>)
.map((row) => row.department_name);
expect(departments).toContain("Finance");
expect(departments).toContain("Sales");
connection.close();
});
});