The CRM context layer rule schema
A canonical schema for CRM AI context entries: the governance envelope, field entries, rule entries, the thirteen rule categories, and a JSON Schema file you can validate against.
This is the canonical schema for a CRM AI context layer entry. The main guide develops it through examples; this page is the specification, so supporting pages and downstream tooling have one shape to agree on.
Two files, both stable URLs you can fetch or hand to a coding agent:
context-layer-entry.schema.json— JSON Schema, draft 2020-12minimum-viable-context-layer.yaml— a seven-entry starter library that validates against it
An entry is either a field entry, which explains what a CRM field means and how far to trust it, or a rule entry, which carries an instruction the agent has to apply. Both share the same governance envelope, because both need to be reviewed, versioned, retired, and traced back to whatever went wrong to cause them.
The governance envelope
Every entry carries these regardless of kind. Five are required.
| Field | Required | What it does |
|---|---|---|
id | yes | Stable handle. Never reused, never renumbered. Referenced by dependsOn, run logs, and golden questions |
kind | yes | field or rule |
title | yes | The retrieval key. See below — this one does more work than it looks like |
appliesTo | yes | Objects governed, as an array of API names. ["*"] for universal entries |
status | yes | draft · approved · active · deprecated. Only active reaches a production agent |
category | no | One of thirteen, listed further down |
dependsOn | no | Entry ids to load alongside this one |
alwaysLoad | no | Ship with every question regardless of scope |
version | no | Increment on any change to meaning. Makes a regression attributable to a revision |
priority | no | Conflict resolution. Higher wins |
effectiveFrom / expiresOn | no | For entries only correct inside a window |
reviewAfter | no | Freshness SLA for the entry itself, not the field it describes |
replacedBy | no | Successor entry id, set alongside status: deprecated |
owner | no | Team or person accountable |
evidence | no | Why this is true: a Finance definition, an automation, a policy doc |
originFailure | no | The logged wrong answer that caused the entry to exist |
testIds | no | Golden questions that fail without it and pass with it |
lastReviewed / updated | no | Dates. Quote them in YAML so parsers return strings |
Write the title as a trigger
The schema puts a 12-character minimum on title, which is a crude proxy for a
real rule: state the condition under which the entry matters, not the category it
belongs to.
In a phased retrieval design the agent decides whether to fetch a body from the title alone. That makes the title the entire basis of a retrieval decision, and category labels lose that decision every time.
Opportunity exclusionsis a folder name. It gets skipped and the rule never applies.Exclude system-generated and test opportunities from all pipeline and win-rate reportinggets fetched.
If a title could sit on a folder, rewrite it.
Three fields most people skip
dependsOn is what stops cross-object rules from getting stranded. A definition
that assumes an exclusion has already been applied should declare it, rather than
hoping both happen to be retrieved.
alwaysLoad encodes an asymmetry that matters more than it looks like.
Interpretation rules are safe to fetch on demand; the worst case is the agent reads
a value naively and says so. Exclusions are not, because an agent that doesn't
know an exclusion exists cannot decide to fetch it. It runs the query, gets a
number full of test records, and reports it confidently. Set alwaysLoad: true on
every exclusion, anti_pattern, and security entry.
originFailure is the one that makes the library reviewable a year later. Most
entries past the first handful exist because something got answered wrong once, and
recording which failure turns the library from a pile of assertions into a record of
what you learned.
Field entries
Additional properties when kind: field. property and meaning are required.
| Field | What it does |
|---|---|
property | Field or property API name |
object | The object it lives on |
label | Human-facing label, when it differs from the API name |
meaning | What the field means in plain language, including who or what writes it |
interpretation | How to read the values: thresholds, bands, what each option signifies |
valueMap | Stored value to meaning, for dropdowns where the API value differs from the UI label |
source | How the value gets there |
authoritative | Whether to trust it: true, false, or human_owned |
writePolicy | What happens if an agent writes to it |
freshness | How current, and how stale is too stale for a decision |
primarySource | false for rollups and summaries |
scopeCaveats | Where the meaning doesn't hold: record types, regions, currencies, periods |
source is the axis a single trust flag collapses:
| Value | Why it's distinct |
|---|---|
human_entered | Decays through neglect. Someone stops maintaining it and nothing announces that |
system_derived | Reliable while the job runs. Fails silently when it stops |
integration_written | Carries the integration's timestamp and user, which destroys "recently modified" as a freshness signal |
formula | Can be perfectly authoritative and still impossible to group or filter on |
ai_generated | Fresh, fluent, confident, and asserting things. Should almost always pair with authoritative: false |
authoritative: human_owned is the third state binary trust misses. A rep's
qualification score or a CSM's health assessment should be read, compared against,
and reported as agreement or disagreement with reasoning. Never overwritten. Skip
this state and the AI's opinion quietly replaces the practitioner's, trust
collapses, and the field gets gamed or abandoned inside a quarter.
writePolicy: force_set_by_automation is the dangerous value. A field a
workflow force-sets on every save will accept an agent's update, return 200, and
revert. The recommendation looks applied and isn't, which is worse than a refusal
because everyone downstream believes it worked.
id: F-014
kind: field
title: >
Read renewal_risk_score as the authoritative renewal signal where 75 and above
is high risk
appliesTo: [Account]
category: interpretation
status: active
version: 2
property: renewal_risk_score
object: Account
meaning: >
0-100 estimate of renewal risk, recalculated nightly by the scoring job.
Higher means more risk. Reps do not enter this manually.
interpretation:
- "0-39: low risk"
- "40-74: watch"
- "75-100: high risk"
source: system_derived
authoritative: true
writePolicy: read_only
freshness: "Recalculated nightly. Treat as stale after 48 hours."
primarySource: true
owner: customer-success-ops
originFailure: "Agent read account_health = Green and called a 82-risk account healthy"
testIds: [GQ-12]
updated: "2026-08-20"Rule entries
Additional properties when kind: rule. ruleText is required.
| Field | What it does |
|---|---|
ruleText | The instruction the agent receives |
filterFragment | The literal query predicate, where the rule has one |
fieldAllowlist | For allowed-object entries: the fields an agent may read |
outputShape | For output_shape entries: the numbered structure a recurring answer must follow |
Two things about ruleText change compliance more than anything else in this
schema.
Phrase exclusions as pre-flight checks. "Test records are excluded from reporting" is a description, and descriptions get read and ignored. "Before any query on this object, add these filters: …" is an instruction to perform. Same information, materially different behavior.
Ship the predicate. A prose definition of a stage threshold gets re-derived on
every run and mis-derived on some of them. filterFragment is deterministic, and
it also gives you something to diff when the definition changes.
The thirteen categories
Naming the categories makes the library sortable, reviewable, and easy to reference from other pages.
| Category | What it governs | Always load? |
|---|---|---|
definition | What a business term means: qualified pipeline, New ARR | no |
interpretation | How to read a value: thresholds, bands, stored options | no |
authority | Which field wins, which is dead, which is human-owned | no |
freshness | How current a value is and when it stops being usable | no |
exclusion | Records that must be filtered out before counting | yes |
attribution | Who or what gets credit, and from which field | no |
temporal | Fiscal calendars, period boundaries, date-range handling | yes |
currency | Multi-currency handling and conversion | yes |
write_policy | What an agent may write, and what silently reverts | no |
output_shape | The structure a recurring answer must take | no |
anti_pattern | Operations that succeed and return something wrong | yes |
cross_object_dependency | Rules that only hold when a join is done a specific way | no |
security | Object and field allowlists, disclosure requirements | yes |
The five marked always-load share a property: the agent cannot detect that they're missing. A skipped interpretation rule produces a hedge. A skipped exclusion produces a confident wrong number.
anti_pattern is the category most taxonomies leave out and the one worth adding
first. The expensive failures are the ones that return a result. Grouping on a
multi-select, aggregating a formula field, a rollup the platform reshapes rather
than rejecting. An error gets fixed in five minutes. A plausible number gets
presented to a VP.
Resolution states
Separate from the schema, an assembled payload should be able to say what it doesn't know. Four states beyond the happy path, and each has a different correct behavior:
| State | Meaning | What the agent should do |
|---|---|---|
known | Approved guidance exists and loaded | Answer, naming the rules applied |
known_stale | Guidance exists but is past reviewAfter | Answer, and flag that the rule is overdue for review |
unknown | No approved guidance for a field the question needs | Refuse or route to review. Don't infer meaning from the field name |
conflicting | Two active entries contradict and priority doesn't separate them | Stop. Don't let the model arbitrate silently |
permission_limited | The query ran against a partial view of the org | Answer, and state whose permissions it ran under |
conflicting is worth building a validator for. Two rules that disagree will
produce an answer either way, and which one wins becomes a function of retrieval
order rather than a decision anyone made.
Validating
The schema is strict on purpose: unknown properties are rejected, appliesTo must
be an array, and a field entry can't carry rule-only properties.
pip install jsonschema pyyaml
python - <<'EOF'
import json, yaml
from jsonschema import Draft202012Validator
schema = json.load(open("context-layer-entry.schema.json"))
entries = yaml.safe_load(open("rules.yaml"))
v = Draft202012Validator(schema)
for entry in entries:
for err in v.iter_errors(entry):
path = "/".join(map(str, err.path)) or "(root)"
print(f"{entry.get('id', '?')} {path}: {err.message}")
EOFWorth wiring into CI next to the golden-question run. A malformed rule and a wrong rule fail the same way in production: quietly.
Where to start
Don't build to this schema on day one. Start with the minimum viable context layer, which is seven entries using about eight of these fields, and add the rest as you hit the problem each one solves.
- How to build an AI context layer for your CRM — the concept and the full build
- MCP vs. CRM context layer — where the protocol ends and this begins
- Salesforce implementation — the same schema as a custom object
- HubSpot implementation — as custom properties or an Enterprise custom object
FAQ
- Do I need every field in this schema?
- No. Five fields are required: id, kind, title, appliesTo, and status. Everything else earns its place when you hit the problem it solves. A first library of seven rules will use maybe eight fields total; version, priority, and expiresOn start mattering once rules begin contradicting each other.
- Why does the title have a minimum length?
- Because in a phased retrieval design the agent decides whether to fetch a rule's body from its title alone, and short titles are almost always category labels. 'Opportunity exclusions' is a folder name and gets skipped. The schema enforces 12 characters as a crude proxy for 'this states a condition, not a category.'
- What is the difference between source and evidence?
- `source` is on field entries and says how the value gets into the field: human-entered, system-derived, integration-written, formula, or AI-generated. `evidence` is on any entry and says why the entry itself is true — a Finance definition, an automation that force-sets the field, a policy document. One is about the data, the other is about the rule.
- Can an entry apply to more than one object?
- Yes, and appliesTo is always an array for exactly that reason. A single scope field forces you to either duplicate the rule per object or bury the cross-object part in prose, and prose is invisible to the retrieval layer. Use ["*"] for entries that apply to every question.
Get the next guide
New guides and the occasional note on GTM tooling. Don't worry, I won't drop you into a three-month nurture.