以下情境为教学假设,人物与数字不代表经核实的真实案例。
交易文件里,竞业条款通常只有一页,但它决定了买方付的钱买到的是一段有保护的收入,还是一段随时可能被创始人带走的关系。Nord 的前 3 家客户占收入 48%,服务收入占 72.7%;如果两位合伙人交割后另起一家同类工作室,买方买到的可能只是几份合同和一个品牌名。竞业、保密、IP 归属和留任奖金,是四种不同的工具,不能用一个「竞业协议」全部代替。
本节只算三件事:竞业限制的三个边界如何影响可执行性,保密与 IP 条款补的是哪个缺口,留任奖金花多少钱、能保护多少收入。它不提供法律意见,也不判断某一条款在某个司法辖区是否有效;所有时长、比例与可执行性都是教学假设。
竞业的三个边界:地域、时长、业务范围
竞业限制的核心不是「不许竞争」,而是「在多大地域、多长时间、多宽的业务范围内不许竞争」。三个边界任意一个拉满,条款看起来更强,实际更可能被认定为不合理而无法执行。买方要的是可执行的保护,不是纸面上最宽的禁止。
地域边界应该跟着客户和收入走,而不是跟着公司注册地走。Nord 在英国注册,但如果客户主要在两个区域,竞业地域写成「全球」并不会增加保护,只会增加被挑战的理由。教学假设是 2 个区域;如果买方实际只在一个区域获客,写成 2 个区域已经足够,写成全球是过度覆盖。
时长边界要区分「卖方竞业」和「员工竞业」。卖方或合伙人的竞业常见 24 个月,因为买方需要时间把客户关系接过去;普通员工的竞业常见 12 个月,因为限制过长会影响其生计,也更难获得对价支持。Nord 的教学假设是合伙人 24 个月、核心外包 12 个月;这不是法律规定,而是交易双方对「关系转移需要多久」的估计。
业务范围边界要用「同类服务、同类客户」定义,而不是「任何软件、任何商业活动」。Nord 的咨询是品牌官网与订阅小工具,竞业范围应落在同一服务线与同一客户类型;如果写成「不得从事任何软件开发」,范围过宽,反而削弱可执行性。教学假设把业务范围量化为 10,000 个基点(全业务)或 5,000 个基点(半业务),只是为了比较宽窄,不是法律分类。
| 边界 | 过窄 | 合理区间(教学假设) | 过宽 |
|---|---|---|---|
| 地域 | 只写注册地 | 客户所在地的 1–2 个区域 | 全球 |
| 时长 | 3 个月,来不及转移关系 | 合伙人 24 个月、员工 12 个月 | 5 年以上 |
| 业务范围 | 只写一个产品名 | 同类服务与同类客户 | 任何软件或任何商业活动 |
把三个边界相乘,可以得到一个「宽度单位」:24 个月 × 2 个区域 × 全业务 = 48 个单位;12 个月 × 2 个区域 × 半业务 = 12 个单位。这个数字不判断合法性,只帮助买卖双方在同一张桌上比较「这一版比上一版宽了多少」。宽度越大,需要支撑它的对价、岗位和客户接触证据就越多。
可执行性:条款写得越宽,越可能一文不值
竞业条款的可执行性取决于三个现实条件:卖方是否真的接触了客户关系,限制是否超出了保护买方合法利益所必需的范围,卖方是否得到了合理对价。三个条件缺一个,条款就可能被全部或部分否定。不同司法辖区的处理方式不同,本节不引用具体法条,只把它当作交易设计变量。
第一个条件是「接触」。如果一位核心外包只写代码、从不接触客户,竞业条款对他的约束力通常弱于对合伙人的约束;买方不能把所有人都当成客户关系的持有者。Nord 的两名合伙人直接持有前 3 家客户关系,三名核心外包负责交付;竞业重点应放在合伙人,外包用保密与不招揽补足。
第二个条件是「范围必需」。买方要保护的是客户关系、定价信息和交付能力,不是阻止卖方从事任何谋生手段。把地域写成全球、业务写成所有软件、时长写成五年,看起来保护更充分,实际更容易被整体挑战。教学假设用 60% 的可执行性来折算保护价值:不是「一定有效」,也不是「一定无效」,而是一个中间值。
第三个条件是「对价」。竞业限制需要有独立对价:留任奖金、离职补偿、交易价款中的一部分,或者明确的签约奖金。对员工只写一句「离职后两年内不得从事同类业务」而不给对价,条款很难站住。对卖方,竞业通常写进交易文件,对价隐含在价格里;但买方仍要能说清哪一部分价格买的是竞业。
| 可执行性条件 | 强 | 弱 | 对交易的影响 |
|---|---|---|---|
| 客户接触 | 合伙人直接持有客户 | 外包只做后台交付 | 决定限制对象 |
| 范围必需 | 同类服务、同类客户 | 任何软件、全球 | 决定条款能否整体执行 |
| 独立对价 | 留任奖金或价格对价 | 只有一句禁止 | 决定条款是否有支撑 |
| 期限 | 24 个月内 | 5 年以上 | 决定是否被认为过度 |
可执行性不是二值。买方在模型里用 60% 折算,卖方则要争取把「不可执行时的后果」写清楚:如果条款被判定过宽,是全部失效,还是缩小到合理范围后继续有效。这个差异会改变买方愿意为竞业付多少钱,也会改变卖方在价格谈判里的让步空间。
保密与 IP 归属:竞业之外的三条底线
竞业限制管的是「离开后做什么」,保密管的是「离开后说什么」,IP 归属管的是「离开后东西归谁」。三件事经常被写进同一份协议,但它们的期限、范围和违约后果不同,不能互相替代。
保密义务通常分两层:商业秘密可以约定在披露后持续有效,普通保密信息则常见 2–3 年的期限(教学假设)。保密条款要写清例外:已经公开的信息、接收方独立开发的信息、法律要求披露的信息。没有例外条款的保密协议,执行时容易被对方以「范围过宽」挑战;例外写得太宽,又会让商业秘密失去保护。
IP 归属要解决「谁创作、谁拥有」。员工在职务范围内创作,经济权利通常归雇主;外包创作,权利通常归外包方,除非合同明确转让。Nord 的三名核心外包如果参与了产品代码或品牌素材,交易前要确认每一份外包合同都有 IP 转让条款,而不是只有一句「成果归甲方使用」。这条底线在卷二已经拆过,见 7.3 IP 归属、转让与员工发明 。
不招揽条款是竞业的轻量替代。它不禁止对方从事同类业务,只禁止在 12–24 个月内挖走客户或员工;对接触客户但不足以支撑完整竞业的人,不招揽通常更容易执行。Nord 的三名核心外包适合「保密 + IP 转让 + 12 个月不招揽」,两名合伙人适合「24 个月竞业 + 不招揽 + 留任」。
| 工具 | 管什么 | 常见期限(教学假设) | 主要缺口 |
|---|---|---|---|
| 竞业 | 离开后不得从事同类业务 | 合伙人 24 个月、员工 12 个月 | 范围过宽可能不可执行 |
| 保密 | 不得披露或使用保密信息 | 商业秘密持续、普通信息 2–3 年 | 例外条款与举证 |
| IP 归属 | 成果的所有权归谁 | 永久 | 外包与创始人历史作品 |
| 不招揽 | 不得挖客户或员工 | 12–24 个月 | 只防挖人,不防直接竞争 |
四条底线里,IP 归属最容易被忽略,因为它不影响交割当天的收入,却影响买方能不能自由使用代码和品牌。保密条款最容易被写得太宽,因为它看起来「多写不亏」;实际上,没有例外的保密协议在争议中更难执行。竞业条款最容易被当成万能工具,但它只覆盖「竞争行为」,覆盖不了「带走客户关系」和「带走代码」。
留任奖金:28,500 美元买 24 个月
留任奖金(retention bonus)的目的不是奖励过去,而是购买未来一段时间的关键人留存。它通常按「月薪 × 留任月数 × 比例」计算,分期支付,提前离职则按比例退回。对买方,它是把「人走了客户就没了」的风险,部分转移成「人留下但先付一笔钱」的成本。
Nord 的核心 5 人按教学假设是 2 名合伙人与 3 名长期外包:合伙人月薪 4,000 美元,外包月薪 500 美元;留任期 12 个月,奖金比例 25%。奖金池是 2 × 4,000 × 12 × 25% + 3 × 500 × 12 × 25% = 24,000 + 4,500 = 28,500 美元,占年收入 154,320 美元的 1,847 个基点。
| 人群 | 人数 | 月薪 | 留任月数 | 比例 | 奖金 |
|---|---|---|---|---|---|
| 合伙人 | 2 | 4,000 | 12 | 25% | 24,000 |
| 核心外包 | 3 | 500 | 12 | 25% | 4,500 |
| 合计 | 5 | 28,500 |
对照组是不留任导致的客户流失。Nord 的前 3 家客户占收入 48%,按「一名合伙人对应前 3 家中的一家」估算,一名合伙人离开带走的收入约为 48% ÷ 3 = 16%,即每年 24,691 美元,两年 49,382 美元。两名合伙人同时离开,收入影响翻倍到每年 49,382 美元、两年 98,765 美元;前 3 家全丢则是每年 74,074 美元、两年 148,147 美元。
| 情景 | 年收入影响 | 两年累计 | 与 28,500 美元奖金比 |
|---|---|---|---|
| 留任 5 人 | 成本 28,500 | 28,500 | 基准 |
| 1 名合伙人离开 | 24,691 | 49,382 | 多 20,882 |
| 2 名合伙人离开 | 49,382 | 98,765 | 多 70,265 |
| 前 3 家客户全丢 | 74,074 | 148,147 | 多 119,647 |
但奖金不等于保护。按 60% 的可执行性折算,两年 49,382 美元的暴露里,受保护部分约 29,629 美元,未被保护部分约 19,753 美元;扣掉 28,500 美元奖金,净收益只有约 1,129 美元。回本点在第 24 个月,几乎等于竞业期的终点。这个结果说明:留任奖金买的是时间,不是保证;如果客户关系高度集中在一个人身上,奖金只能争取交接窗口,不能替代客户迁移。
写成代码:noncompete.ts
下面的 noncompete.ts 把留任成本、收入暴露、可执行性与回本点放在同一个模型里。bonusPoolCents 是确定支出,protectedRevenueCents 是按可执行性折算后的保护价值,netBenefitCents 是两者之差;breakEvenMonths 回答「奖金要多少个月才能被保护收入覆盖」。另一个函数 restrictionBreadth 只量宽窄,不判断合法性。
// file: src/capital/noncompete.ts
// tier: L1
// verify: node --test test/capital/noncompete.test.ts
import { type Cents } from "../core/money.ts";
import { type Bps, divide } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";
export type RetentionPerson = {
role: string;
count: number;
monthlySalaryCents: Cents;
retentionMonths: number;
bonusBps: Bps;
};
export type RetentionInput = {
people: RetentionPerson[];
annualRevenueCents: Cents;
revenueAtRiskBps: Bps;
protectionYears: number;
enforceabilityBps: Bps;
noncompeteMonths: number;
};
export type RetentionOutcome = {
headcount: number;
monthlySalaryCents: Cents;
annualSalaryCents: Cents;
bonusPoolCents: Cents;
bonusPoolBpsOfRevenue: Bps;
annualRevenueAtRiskCents: Cents;
totalRevenueAtRiskCents: Cents;
protectedRevenueCents: Cents;
lostRevenueCents: Cents;
netBenefitCents: Cents;
costPerProtectedMonthCents: Cents;
breakEvenMonths: number;
};
function checkAmount(value: Cents, label: string): string | null {
if (!Number.isInteger(value) || value < 0) {
return `${label} must be a non-negative integer amount in cents`;
}
return null;
}
function checkBps(value: Bps, label: string): string | null {
if (!Number.isInteger(value) || value < 0 || value > 10000) {
return `${label} must be between 0 and 10000 bps`;
}
return null;
}
/**
* A retention bonus is a prepaid bet on a noncompete. The bonus is paid over
* the retention period, while the protection only exists if the restriction is
* enforceable and the customers actually stay. This model separates the three
* numbers: what the bonus costs, how much revenue is exposed, and how much of
* that exposure the restriction plausibly protects. It is a teaching model,
* not legal advice; enforceability is an input, not a conclusion.
*/
export function retentionPlan(
input: RetentionInput,
): Result<RetentionOutcome> {
if (!Array.isArray(input.people) || input.people.length === 0) {
return fail("at least one retention group is required");
}
if (
!Number.isInteger(input.annualRevenueCents) ||
input.annualRevenueCents <= 0
) {
return fail("annual revenue must be a positive integer amount in cents");
}
for (const [value, label] of [
[input.revenueAtRiskBps, "revenue at risk"],
[input.enforceabilityBps, "enforceability"],
] as Array<[Bps, string]>) {
const error = checkBps(value, label);
if (error) {
return fail(error);
}
}
if (
!Number.isInteger(input.protectionYears) ||
input.protectionYears < 1 ||
input.protectionYears > 10
) {
return fail("protection years must be an integer between 1 and 10");
}
if (
!Number.isInteger(input.noncompeteMonths) ||
input.noncompeteMonths < 1 ||
input.noncompeteMonths > 60
) {
return fail("noncompete months must be an integer between 1 and 60");
}
let headcount = 0;
let monthlySalaryCents = 0;
let bonusPoolCents = 0;
for (const person of input.people) {
if (typeof person.role !== "string" || person.role.trim() === "") {
return fail("each retention group needs a role label");
}
if (!Number.isInteger(person.count) || person.count <= 0) {
return fail("retention headcount must be a positive integer");
}
if (
!Number.isInteger(person.monthlySalaryCents) ||
person.monthlySalaryCents <= 0
) {
return fail("monthly salary must be a positive integer amount in cents");
}
if (
!Number.isInteger(person.retentionMonths) ||
person.retentionMonths < 1 ||
person.retentionMonths > 60
) {
return fail("retention months must be an integer between 1 and 60");
}
const bpsError = checkBps(person.bonusBps, "retention bonus");
if (bpsError) {
return fail(bpsError);
}
headcount += person.count;
monthlySalaryCents += person.count * person.monthlySalaryCents;
bonusPoolCents += Math.round(
(person.count *
person.monthlySalaryCents *
person.retentionMonths *
person.bonusBps) /
10000,
);
}
const annualSalaryCents = monthlySalaryCents * 12;
const annualRevenueAtRiskCents = Math.round(
(input.annualRevenueCents * input.revenueAtRiskBps) / 10000,
);
const totalRevenueAtRiskCents =
annualRevenueAtRiskCents * input.protectionYears;
const protectedRevenueCents = Math.round(
(totalRevenueAtRiskCents * input.enforceabilityBps) / 10000,
);
const lostRevenueCents = totalRevenueAtRiskCents - protectedRevenueCents;
const protectedPerMonthCents =
protectedRevenueCents / (input.protectionYears * 12);
const breakEvenMonths =
protectedPerMonthCents > 0
? Math.ceil(bonusPoolCents / protectedPerMonthCents)
: 0;
return ok({
headcount,
monthlySalaryCents,
annualSalaryCents,
bonusPoolCents,
bonusPoolBpsOfRevenue: divide(bonusPoolCents, input.annualRevenueCents),
annualRevenueAtRiskCents,
totalRevenueAtRiskCents,
protectedRevenueCents,
lostRevenueCents,
netBenefitCents: protectedRevenueCents - bonusPoolCents,
costPerProtectedMonthCents: Math.round(
bonusPoolCents / input.noncompeteMonths,
),
breakEvenMonths,
});
}
export type RestrictionInput = {
durationMonths: number;
geographyCount: number;
scopeBps: Bps;
};
export type RestrictionOutcome = {
durationMonths: number;
geographyCount: number;
scopeBps: Bps;
breadthUnits: number;
breadthPerMonth: number;
};
/**
* Quantifies how broad a restriction is without deciding whether it is valid.
* One breadth unit is one month, one geography and the full business scope.
* A 24-month restriction covering two regions at full scope scores 48 units;
* a 12-month restriction covering one region at half scope scores 6. The same
* score can be reasonable in one jurisdiction and unenforceable in another, so
* the number is a comparison aid, not a legal test.
*/
export function restrictionBreadth(
input: RestrictionInput,
): Result<RestrictionOutcome> {
if (
!Number.isInteger(input.durationMonths) ||
input.durationMonths < 1 ||
input.durationMonths > 60
) {
return fail("duration months must be an integer between 1 and 60");
}
if (
!Number.isInteger(input.geographyCount) ||
input.geographyCount < 1 ||
input.geographyCount > 100
) {
return fail("geography count must be an integer between 1 and 100");
}
const bpsError = checkBps(input.scopeBps, "scope");
if (bpsError) {
return fail(bpsError);
}
const breadthUnits = Math.round(
(input.durationMonths * input.geographyCount * input.scopeBps) / 10000,
);
return ok({
durationMonths: input.durationMonths,
geographyCount: input.geographyCount,
scopeBps: input.scopeBps,
breadthUnits,
breadthPerMonth: Math.round(
(input.geographyCount * input.scopeBps) / 10000,
),
});
}
代码里有一个刻意的选择:enforceabilityBps 是输入,不是输出。模型不预测法院怎么判,也不把「条款写了」当成「条款有效」;它只把可执行性当成一个可调假设,让买卖双方看到不同假设下的净收益。restrictionBreadth 同理,只把时长、地域和业务范围乘成一个可比较的宽度单位,不给出「合理」或「不合理」的结论。
把 Nord 代入:5 人、月薪合计 950,000 美分、年工资 11,400,000 美分、奖金池 2,850,000 美分、占收入 1,847 个基点;年收入暴露 2,469,120 美分、两年 4,938,240 美分;按 60% 可执行性,受保护 2,962,944 美分、未保护 1,975,296 美分、净收益 112,944 美分;每月保护成本 118,750 美分,回本 24 个月。竞业宽度按 24 个月 × 2 个区域 × 全业务算是 48 个单位。
小结
- 竞业限制看三个边界:地域跟客户走、时长分合伙人与员工、业务范围限同类服务与同类客户。
- 条款不是越宽越好;过宽的地域、时长和范围会削弱可执行性,宽度单位只用于比较,不是合法性判断。
- 可执行性取决于客户接触、范围必需和独立对价;模型用 60% 折算,是教学假设,不是判决预测。
- 保密管「说什么」、IP 归属管「东西归谁」、不招揽管「挖不挖人」,三者不能用一个竞业条款替代。
- Nord 的核心 5 人留任奖金是 28,500 美元,占年收入 1,847 个基点;一名合伙人离开的两年收入影响是 49,382 美元。
- 按 60% 可执行性,受保护收入约 29,629 美元,扣掉奖金净收益约 1,129 美元,回本点在第 24 个月。
- 留任奖金买的是交接时间,不是客户关系的保证;客户越集中,越要同时做客户迁移,而不是只加竞业条款。
阅读导航:上一节:9.1 雇佣与外包合同的承继 · 下一节:9.3 交接期与知识转移 。
常见错误与边界
- 把竞业写成全球、五年、所有软件。 范围越宽,越可能被整体挑战;地域跟客户走、时长分角色、范围限同类服务,才有讨论基础。
- 只给员工竞业、不给对价。 没有留任奖金、离职补偿或明确对价的竞业,很难站住;对价要在签约时写清,不是离职时补。
- 把竞业当成客户留存的替代。 竞业只能限制对方做什么,不能保证客户不主动走;客户迁移和关系交接要单独做。
- 保密协议没有例外。 已公开信息、独立开发、法律要求披露三类例外不写,条款执行时容易被挑战;例外太宽,商业秘密又失去保护。
- IP 转让只写「使用权」。 外包合同写「成果归甲方使用」不等于所有权转让;买方要的是可出售、可授权、可质押的所有权。
- 忽略创始人的历史作品。 创始人在公司成立前写的代码、画的图,默认归个人;交易前要有一份转让清单,而不是假设「公司一直在用就是公司的」。
- 留任奖金一次性付清。 一次性支付后,人可以在第二天离开;分期支付、提前离职按比例退回,才有留任效果。
- 只算奖金、不算流失。 28,500 美元看起来不少,但一名合伙人离开的两年收入影响是 49,382 美元;奖金要和流失对照,而不是单独看。
- 把 60% 可执行性当事实。 它是教学假设,不是对某个司法辖区的预测;真实交易要由律师按当地规则评估。
- 留任期和竞业期混为一谈。 留任期是「必须继续工作」的期间,竞业期是「离开后不能竞争」的期间;两者可以重叠,也可以不同,付款节点不同。
本节的边界是「关键人可识别、收入可按合伙人拆分、可执行性可假设」的静态模型。它不覆盖股权激励、递延薪酬、税务处理、跨境劳动法和具体竞业条款的效力;这些要把交易文件交给律师与税务顾问审阅。竞业与留任解决「人愿不愿意留」,下一节解决「留的时候怎么把知识交出来」,见 9.3 交接期与知识转移 ;合伙人退出的价格结构见 2.3 合伙人协议与退出机制 。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。