From b522672423fe45d5c9a28a2cc377e537f9559ffa Mon Sep 17 00:00:00 2001 From: Dotta Date: Sat, 25 Apr 2026 10:35:58 -0500 Subject: [PATCH] Default new hires to approved Co-Authored-By: Paperclip --- docs/guides/board-operator/delegation.md | 2 +- docs/start/core-concepts.md | 4 +-- .../0071_default_hire_approval_off.sql | 1 + packages/db/src/migrations/meta/_journal.json | 7 ++++ packages/db/src/schema/companies.ts | 2 +- .../src/__tests__/company-portability.test.ts | 32 +++++++++++++++++-- server/src/services/company-portability.ts | 8 ++--- 7 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 packages/db/src/migrations/0071_default_hire_approval_off.sql diff --git a/docs/guides/board-operator/delegation.md b/docs/guides/board-operator/delegation.md index 7096632a8a7..9f888615535 100644 --- a/docs/guides/board-operator/delegation.md +++ b/docs/guides/board-operator/delegation.md @@ -47,7 +47,7 @@ You do **not** need to tell the CEO to engage specific agents. After you approve - **Breaks goals into concrete tasks** with clear descriptions, priorities, and acceptance criteria - **Assigns tasks to the right agent** based on role and capabilities (e.g., engineering tasks go to the CTO or engineers, marketing tasks go to the CMO) - **Creates subtasks** when work needs to be decomposed further -- **Hires new agents** when the team lacks capacity for a goal (subject to your approval) +- **Hires new agents** when the team lacks capacity for a goal, with hire approvals available when enabled in company settings - **Monitors progress** on each heartbeat, checking task status and unblocking reports - **Escalates to you** when it encounters something it can't resolve — budget issues, blocked approvals, or strategic ambiguity diff --git a/docs/start/core-concepts.md b/docs/start/core-concepts.md index 33de5806a8e..6709e8a8c2f 100644 --- a/docs/start/core-concepts.md +++ b/docs/start/core-concepts.md @@ -57,9 +57,9 @@ The CEO is the primary delegator. When you set company goals, the CEO: 1. Creates a strategy and submits it for your approval 2. Breaks approved goals into tasks 3. Assigns tasks to agents based on their role and capabilities -4. Hires new agents when needed (subject to your approval) +4. Hires new agents when needed, with hire approvals available when you enable them -You don't need to manually assign every task — set the goals and let the CEO organize the work. You approve key decisions (strategy, hiring) and monitor progress. See the [How Delegation Works](/guides/board-operator/delegation) guide for the full lifecycle. +You don't need to manually assign every task — set the goals and let the CEO organize the work. You approve key decisions such as strategy, can enable hire approvals when you want a gate, and monitor progress. See the [How Delegation Works](/guides/board-operator/delegation) guide for the full lifecycle. ## Heartbeats diff --git a/packages/db/src/migrations/0071_default_hire_approval_off.sql b/packages/db/src/migrations/0071_default_hire_approval_off.sql new file mode 100644 index 00000000000..384197002ac --- /dev/null +++ b/packages/db/src/migrations/0071_default_hire_approval_off.sql @@ -0,0 +1 @@ +ALTER TABLE "companies" ALTER COLUMN "require_board_approval_for_new_agents" SET DEFAULT false; diff --git a/packages/db/src/migrations/meta/_journal.json b/packages/db/src/migrations/meta/_journal.json index 765875c4463..9af1d824b48 100644 --- a/packages/db/src/migrations/meta/_journal.json +++ b/packages/db/src/migrations/meta/_journal.json @@ -498,6 +498,13 @@ "when": 1776780004000, "tag": "0070_active_run_output_watchdog", "breakpoints": true + }, + { + "idx": 71, + "version": "7", + "when": 1777131234000, + "tag": "0071_default_hire_approval_off", + "breakpoints": true } ] } diff --git a/packages/db/src/schema/companies.ts b/packages/db/src/schema/companies.ts index f8566d27bb5..15f5fe6283c 100644 --- a/packages/db/src/schema/companies.ts +++ b/packages/db/src/schema/companies.ts @@ -15,7 +15,7 @@ export const companies = pgTable( spentMonthlyCents: integer("spent_monthly_cents").notNull().default(0), requireBoardApprovalForNewAgents: boolean("require_board_approval_for_new_agents") .notNull() - .default(true), + .default(false), feedbackDataSharingEnabled: boolean("feedback_data_sharing_enabled") .notNull() .default(false), diff --git a/server/src/__tests__/company-portability.test.ts b/server/src/__tests__/company-portability.test.ts index f5858a3e32d..017943850e0 100644 --- a/server/src/__tests__/company-portability.test.ts +++ b/server/src/__tests__/company-portability.test.ts @@ -139,12 +139,12 @@ describe("company portability", () => { brandColor: "#5c5fff", logoAssetId: null, logoUrl: null, - requireBoardApprovalForNewAgents: true, + requireBoardApprovalForNewAgents: false, }); companySvc.create.mockResolvedValue({ id: "company-imported", name: "Imported Paperclip", - requireBoardApprovalForNewAgents: true, + requireBoardApprovalForNewAgents: false, }); agentSvc.list.mockResolvedValue([ { @@ -461,6 +461,32 @@ describe("company portability", () => { expect(exported.warnings).toContain("Agent claudecoder PATH override was omitted from export because it is system-dependent."); }); + it("exports hire approval policy only when approval is required", async () => { + const portability = companyPortabilityService({} as any); + + companySvc.getById.mockResolvedValueOnce({ + id: "company-1", + name: "Paperclip", + description: null, + issuePrefix: "PAP", + brandColor: "#5c5fff", + logoAssetId: null, + logoUrl: null, + requireBoardApprovalForNewAgents: true, + }); + + const exported = await portability.exportBundle("company-1", { + include: { + company: true, + agents: false, + projects: false, + issues: false, + }, + }); + + expect(asTextFile(exported.files[".paperclip.yaml"])).toContain("requireBoardApprovalForNewAgents: true"); + }); + it("exports default sidebar order into the Paperclip extension and manifest", async () => { const portability = companyPortabilityService({} as any); @@ -2554,7 +2580,7 @@ describe("company portability", () => { status: "idle", })); expect(companySvc.create).toHaveBeenCalledWith(expect.objectContaining({ - requireBoardApprovalForNewAgents: true, + requireBoardApprovalForNewAgents: false, })); }); diff --git a/server/src/services/company-portability.ts b/server/src/services/company-portability.ts index bbc7d4b691b..27477c41c25 100644 --- a/server/src/services/company-portability.ts +++ b/server/src/services/company-portability.ts @@ -2264,7 +2264,7 @@ function buildEnvInputMap(inputs: CompanyPortabilityEnvInput[]) { } function readCompanyApprovalDefault(_frontmatter: Record) { - return true; + return false; } function readIncludeEntries(frontmatter: Record): CompanyPackageIncludeEntry[] { @@ -3465,7 +3465,7 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { company: stripEmptyValues({ brandColor: company.brandColor ?? null, logoPath: companyLogoPath, - requireBoardApprovalForNewAgents: company.requireBoardApprovalForNewAgents ? undefined : false, + requireBoardApprovalForNewAgents: company.requireBoardApprovalForNewAgents ? true : undefined, feedbackDataSharingEnabled: company.feedbackDataSharingEnabled ? true : undefined, feedbackDataSharingConsentAt: company.feedbackDataSharingConsentAt?.toISOString() ?? null, feedbackDataSharingConsentByUserId: company.feedbackDataSharingConsentByUserId ?? null, @@ -3986,8 +3986,8 @@ export function companyPortabilityService(db: Db, storage?: StorageService) { description: include.company ? (sourceManifest.company?.description ?? null) : null, brandColor: include.company ? (sourceManifest.company?.brandColor ?? null) : null, requireBoardApprovalForNewAgents: include.company - ? (sourceManifest.company?.requireBoardApprovalForNewAgents ?? true) - : true, + ? (sourceManifest.company?.requireBoardApprovalForNewAgents ?? false) + : false, feedbackDataSharingEnabled: include.company ? (sourceManifest.company?.feedbackDataSharingEnabled ?? false) : false,