Skip to content

Extracting Non-Compete Terms From an Employment Agreement

9 min read · updated August 11, 2026

The hard part of a non-compete is not finding it. It is that the section labelled “Restrictive Covenants” usually contains three or four separate promises with different durations, and a schema with one non_compete_duration column silently throws most of them away.

One paragraph, four covenants

A representative section, compressed: “For twelve (12) months following the Termination Date, Employee shall not, directly or indirectly, engage in any Competing Business within the Restricted Territory; for twenty-four (24) months following the Termination Date, Employee shall not solicit any Customer with whom Employee had material contact during the final twelve (12) months of employment; and for twenty-four (24) months, Employee shall not solicit or hire any person employed by the Company.”

That is one paragraph and three obligations with two durations, plus a fourth date range — the twelve-month lookback defining which customers count — that is not a duration of any obligation at all. An extractor that returns{"non_compete": "24 months"} has picked the largest number in the paragraph and attached it to the wrong covenant.

The shape that works is a list, one record per covenant — the general pattern in schema design for a document holding several entities — each with a type from a closed vocabulary you define: non_compete, customer_non_solicit, employee_non_solicit, no_hire, non_disparagement, non_dealing. Closed because the distinctions are real: a no-hire covenant bars hiring somebody who applies unprompted and a non-solicit does not, and the two are drafted in adjacent clauses that look nearly identical. Using an enum with strict validation — see testing enum constraint compliance — keeps a model from inventing a fifth category on a document that is merely worded oddly.

The duration has no date in it

Every lease clause in this cluster anchors on a date printed in the document. A post-employment covenant does not. “Twelve months following the Termination Date” anchors on an event that has usually not happened when the document is signed, and may never happen.

So the extracted record is a length and an anchor event, and the end date is null with a stated reason — a null that means “not derivable here” rather than “not found”, which is a distinction handling a missing required field turns on:

{
  "covenant_type": "non_compete",
  "duration_months": 12,
  "anchor_event": "termination_of_employment",
  "anchor_qualifier": "for any reason, with or without cause",
  "end_date": null,
  "end_date_basis": "requires termination date from HR record"
}

The anchor_qualifier earns its place. Some covenants run only from termination by the employee or termination for cause, which means the covenant may not bind at all depending on how employment ended. A record that flattens that to “12 months from termination” is not a compression of the clause, it is a different clause. Resolution happens where the contract meets an employment record; the extraction’s job is to make that join possible, which is the same discipline as extracting first and reasoning second.

Geography is often a formula

Half of restricted-territory definitions are literal — a list of states, a radius from a stated address, a country. The other half are computed: “any county or metropolitan statistical area in which Employee performed services or solicited business on behalf of the Company during the final twenty-four (24) months of employment”.

There is no territory in the document. There is a rule for producing one from records the document does not contain. Store the two cases as different shapes: a territory_type of enumerated with a list, or of derived with the rule text, the lookback period and the unit of geography it names. A radius clause is a third shape and needs the number, the unit and the centre — and the centre is frequently “any office at which Employee worked”, which is derived again.

Miles and kilometres both appear, and so does the trap of a radius stated around “any Company location” where the company has forty locations. The extracted record should not attempt to resolve that into a shape on a map. It should carry enough structure that a system with a locations table can.

Splitting restricted activity

The activity half of a non-compete is where careless extraction loses the most meaning, because the sentence is built from stacked qualifiers. “Engage in, own, manage, operate, control, be employed by, or provide services to any Competing Business” is a list of prohibited relationships. “Competing Business” is a defined term elsewhere, usually narrowed to specific products or market segments. And there is frequently a passive-investment carve-out: ownership of less than some percentage of a public company’s stock is permitted.

  • The defined term must be resolved. A record whose scope field reads “any Competing Business” contains no information. Follow the capitalised term to its definition and store the definition text, along with the section number it came from.
  • Carve-outs are not obligations. The passive-holding exception belongs in an exceptions array, not appended to the scope prose, for the same reason NDA carve-outs need their own field: concatenating a prohibition with its exception produces a string whose meaning depends on a reader noticing the word “except”.
  • “Directly or indirectly” is a flag, not prose. It appears in most covenants and its absence is the notable case, so store it as a boolean and let the notable case be queryable.

Tolling and severability break the end date

Two clauses elsewhere in the agreement change what the duration means, and both are usually missed because they are not in the covenant paragraph.

A tolling clause reads: “The Restricted Period shall be extended by any period during which Employee is in violation of this Section.” The duration is now conditional on facts that arise after the contract, so the computed end date is a floor rather than an answer. Store tolling_on_breach as a boolean, and where it is true, present the derived end date as an earliest date.

A reformation or blue-pencil clause reads: “If any restriction is held unenforceable, it shall be reduced to the maximum enforceable scope.” The stated scope is then not necessarily the operative scope. The extraction should record what the document says and set a flag that a savings clause exists; it should not attempt to guess what a narrowed version would be. That is a legal question and it is not one an extraction pipeline should answer.

Whether a covenant is enforceable at all depends on jurisdiction, on the role, and in some places on statute, and it changes. This page is about getting the clause into fields; it is not about enforceability, and an extraction record should never carry an enforceable column filled in by a model.

Fields that decide who reviews it

Because enforceability varies, the fields that matter most downstream are often not in the covenant at all. Extract them anyway, on the same record, so the row can be routed without reopening the document:

  • Governing law and forum, from the clause that states them — covered in extracting governing law.
  • Employee work location as stated in the agreement, which frequently differs from the governing law and from the company’s headquarters.
  • Consideration recited for the covenant — continued employment, a signing payment, equity — because its presence or absence is one of the first things a reviewer looks for.
  • Assignment and successor language, which decides whether the covenant follows the business through an acquisition.

A section that produces four covenant rows, each with a duration, an anchor event, a resolved scope definition and a jurisdiction, is queryable: it can answer “which of our covenants run more than eighteen months and are governed by the law of a state where the employee never worked”. A single free-text column cannot answer anything, and that is the difference the schema buys.