# nanti.ai documentation
> The complete product documentation from https://nanti.ai/docs, in Markdown. Generated at build time from the same source the pages render from.
## Nanti Sales Coach
### Getting started · https://nanti.ai/docs/sales-coach/getting-started
Install Nanti Sales Coach in your Odoo 19, connect it to the coaching service, and get the first coaching within minutes, on the sales history you already have.
Applies to: Odoo 19 · Community and Enterprise
## What it is
Nanti Sales Coach is an AI sales coach that runs inside the Odoo quotation screen. The moment a
salesperson picks a customer, a coach panel briefs them on the account and suggests concrete,
one-click plays: what this customer should reorder, where they are trading down, and the exact
words to say. If you have not seen it work yet, the
[product page](/sales-coach) has a live preview.
## Requirements
- **Odoo 19.0**, Community or Enterprise, on-premise or hosted. Earlier Odoo versions are not
supported.
- The **Sales** (`sale_management`) and **Discuss** (`mail`) apps installed. These are the only
hard dependencies.
- Outbound HTTPS from your Odoo server to the coaching service, plus a tenant **API key** for AI
coaching. Without a key the module still works in deterministic mode (see the note below).
One optional module adds extra behavior:
| Optional app | What it adds |
| --- | --- |
| Inventory, with expiry dates on lots | The expiry push play (suggesting expiring stock to category buyers). It ships **off by default** and is enabled in Settings. |
## Install
1. Install the `nanti_sales_coach` module in your Odoo 19.
2. On install, the module creates one settings record per company automatically. Companies you
create later get one too.
3. Grant your people the coach security groups (below).
4. Open **Sales → Sales Coach → Settings** to connect it.
## Connect
Settings live under **Sales → Sales Coach → Settings**, one record per company.
- **Backend URL**: where your Odoo server sends coaching requests. The default is the hosted
service, `https://coach.nanti.ai`.
- **API key**: your private tenant key. Only Odoo Administrators can see or edit it, and it is
never sent to the browser.
- Click **Test connection**. On success you will see: "Connected to your tenant. Plan and usage
are shown in the same message." If it fails, the message tells you exactly why; each one is
covered in [Troubleshooting](/docs/sales-coach/troubleshooting).
## First coaching
There is no import or sync step. The coach reads your **existing confirmed sales history**
directly inside your Odoo database the moment a customer is selected, looking back 365 days by
default.
- **Coaching cards appear as soon as a customer is picked.** The first cards are computed locally
and render immediately.
- **AI wording arrives seconds later** and replaces the cards in place, typically within about
ten seconds. Nothing blocks the salesperson while it happens.
- **New customers** with fewer than three confirmed orders get company-wide coaching only (the
weekly directive and strategic products). Per-customer patterns need at least three orders of
history, so the coach does not guess.
With no API key, AI disabled, or the service unreachable, the panel serves locally computed
cards with built-in wording and shows a quiet "offline coaching" badge. Your Odoo is never
blocked by the coach.
## Who can do what
Access is controlled by two security groups in the "Sales Coach" privilege category:
| Group | What it allows |
| --- | --- |
| **User** | See the coach panel and act on cards on sales orders. |
| **Manager** | Everything a User can, plus Settings, directives, product flags, logs, and the Performance dashboard. |
Odoo's own **Sales Administrator** role automatically implies the coach Manager group.
## Next
Read [Concepts](/docs/sales-coach/concepts) to understand what the panel is showing, or jump to
[Daily use](/docs/sales-coach/daily-use) for the salesperson's flow.
### Concepts · https://nanti.ai/docs/sales-coach/concepts
The coach panel, the customer snapshot, the Coach Score, every coaching play, Coach Chat, and how the coach learns from dismissals. Defined precisely, as the product actually behaves.
Applies to: Odoo 19 · Community and Enterprise
## The coach panel
The panel docks on the right of the quotation form, sharing space with the chatter through a
segmented toggle: **Messages** and **Coach** (the Coach tab carries a live dot when there are
cards), plus an **Ask** tab when Coach Chat is enabled. On small screens, a floating
**Open Sales Coach** button opens it instead.
Top to bottom, the panel shows:
1. **Header**: the customer's name, a one-line summary (risk word, cadence, last order, for
example "Watch · every 9 days · last order 14d ago"), the **Coach Score** ring, a free
cache-refresh button (↻), and the **Fresh look (1)** button that forces a new AI pass for
1 enrichment.
2. **The customer snapshot**: the KPI strip.
3. **The order-vs-usual bar**.
4. **Focus chips** that filter the card stack.
5. **The card stack**, with a "Show N more" control past the visible limit (7 cards by default).
The panel is honest about its own state. With no customer picked it says "Pick a customer and
I'll brief you." A brand-new customer gets "New customer - no history yet." with company-wide
coaching only. When there is history but nothing worth saying: "No coaching plays right now. I'll
stay quiet until there is a useful play." On confirmed orders the panel becomes a read-only recap
with actions locked.
The footer chip carries provenance: "Coached by Nanti AI · just now" normally, "coaching…" while
AI wording is being generated, and "offline coaching" when running in deterministic mode.
## The customer snapshot
Six KPIs, all computed locally from your own confirmed sales history, each of them toggleable in
Settings:
| KPI | What it means |
| --- | --- |
| Last order | Days since the last confirmed order. Turns amber when it passes 1.5 times the customer's cadence, red past 2.5 times. |
| Order cadence | The median number of days between this customer's orders. |
| Avg order value | The median order total in the lookback window, with a live delta against the current order. |
| Revenue trend | Last 90 days of revenue against the 90 days before. Flagged when it drops 20 percent or more. |
| Range coverage | How many of your active product categories this customer buys from. |
| Risk | A churn classification: Healthy, Watch, or At risk. |
Four more KPIs are available and ship **off by default**, each behind its own toggle: **overdue
balance**, **margin profile**, **year-to-date revenue**, and **premium share**. Overdue balance
and margin appear only when the underlying data exists (Accounting installed, costs set).
## The Coach Score
A live **0 to 100** ring in the panel header. It measures how close the **current quote** is to
this customer's **own normal shape**, not to an average of your customer base. It only appears
once the customer has enough history for patterns (three confirmed orders by default).
Three weighted components make up the score:
| Component | Weight | What it compares |
| --- | --- | --- |
| Basket | 40 | The order total against the customer's median order value. |
| Mix | 25 | The premium share of this quote against their historical premium share. |
| Growth | 15 | Whether a growth item is on the quote when a growth play is available. |
A quote that matches the customer's norms scores near 100. Clicking the ring opens **"What would
raise it"**: up to three concrete raisers, each with a **+delta** and an instruction taken from a
real card (for example, "+16 Add 12 kg — Jamón Cocido"). If nothing applies, it says "This quote
is close to their normal shape."
The score recomputes on every line edit. Accepting a play that fills the weakest component moves
the score up by roughly that component's remaining gap, which is exactly the +delta the raiser
showed. This is why per-accept gains taper as the basket fills.
## Coaching plays
One card is one detected signal. Every card carries a badge, a headline, up to two lines of
reasoning, a quoted **talk track** the salesperson can say out loud, and one or two actions. The
cards render instantly with built-in wording; the AI then re-words them in place. The AI may
rephrase, but it never invents figures: every number on a card traces back to your data.
The plays, and what accepting each one does to the quotation:
| Play | When it appears | Accepting it |
| --- | --- | --- |
| Regular buy | A product this customer buys on a rhythm is due and missing from the order | Adds the line at their usual quantity |
| Quantity drop | A line's quantity is well below this customer's typical amount | Sets the quantity back to typical |
| Mix defense | The customer switched from a premium product to its cheaper alternative | Switches the line back, or adds the premium alongside; may carry an authorized discount |
| Premium upsell | A regular buyer of a standard product has never tried its premium version | Adds a small trial quantity alongside |
| Cross-sell | Customers who buy what this one buys also buy a companion product this one does not | Adds the companion line |
| New line | A strategic product this customer has never bought | Adds a starter line |
| This week | The owner's running directive matches this customer | Adds the priority product, with the authorized discount when the directive is an offer |
| Expiry push | Stock is expiring soon and this customer buys the category (ships off by default) | Adds the line, with a discount when authorized |
| Payment nudge | The account carries an overdue balance (needs Accounting installed; salespeople see it only when "Show credit to salespeople" is on, managers always do) | Logs a payment follow-up activity noted "Follow up on open balance"; never changes the quote |
| Margin guard | A line's discount sits above your warning floor (it never fires inside an offer's authorized discount) | Sets the line's discount back down to the floor |
| Volume nudge | The order sits just short of a pricelist break or the free-delivery threshold | Raises the line quantity to the break, or adds the customer's usual filler product to clear free delivery; sometimes talk-only |
| Account health | The account has drifted into Watch or At risk | Logs a follow-up activity on the account; a conversation play, not a product play |
| Low basket | The order total is far below this customer's usual and the gap is unexplained | Points back at the missing regulars; no direct line change |
At most one card appears per play-and-product combination per order, and the panel shows up to
seven cards before "Show N more".
Discounts are guarded server side. An accept can never apply a percentage above what the
directive or offer authorizes, no matter what the card shows.
## Focus filters
Single-select chips above the cards, each with a live count. Chips with nothing to show stay
hidden.
- **All**: every card.
- **This week**: the owner's directive.
- **Protect**: mix defense, quantity drops, account health, low basket, payment nudges, margin guard.
- **Grow**: new lines, premium upsells, cross-sells, volume nudges, expiry pushes.
- **Regulars**: due regular buys.
## The order-vs-usual bar
A horizontal bar under the KPI strip labelled "This order". With lines on the quote it reads
`{ratio}% of their usual` (the order total divided by their typical order value). With an empty
basket it reads `empty basket — their usual is {amount}` (both verbatim UI strings).
## Ask the coach (Coach Chat)
With Coach Chat enabled, the side panel gains a third tab, **Ask** ("Ask the coach", subtitle
"Customer-only answers from the live coach context."). Type a free-text question about the
customer on the current quotation and the answer is grounded only in that customer's live coach
context: the same compact aggregates a coaching card uses, one customer at a time. With no
customer picked it says "Pick a customer first." and "Coach Chat stays scoped to one customer at
a time."; switching customers clears the thread ("New customer - chat cleared.").
When the answer spots an opportunity, the chat can offer the same one-click accept actions as the
cards; accepting from chat writes to the recommendation log like a card accept (source
"Coach chat"), so the Performance dashboard counts it. Each turn sends the last 20 messages of
the conversation; no chat transcript is stored on the coaching service. A chat question counts
as 2 enrichments, from the same pool as card wording.
Coach Chat is off by default (`enable_chat` in Settings) and is a premium AI feature: if
your subscription does not include it, the Ask tab shows a locked preview and an upgrade path
instead of failing. If the connection drops mid-chat, the tab says "Chat needs a connection.
Cards still work.", and deterministic coaching continues.
## The weekly directive
The owner's or manager's instruction, written once, that becomes coaching on every matching
quotation. A directive has a type (**push product**, **offer**, or **message**), the products or
categories it covers, a date range (one week by default), a priority weight, and a free-text
**"Why / how to argue it"** note that the AI weaves into the talk track. Offers carry a
**discount authority** percentage, which is the hard ceiling for any discount the card can apply.
Directives can target everyone, a customer segment (partner tags), buyers of a category, or a
hand-picked customer list. They appear to salespeople as the **This week** card and in the My
Coach brief, and a **"Duplicate next week"** action rolls one forward seven days. See
[For managers and owners](/docs/sales-coach/managers) for the full flow.
## The My Coach daily brief
Under **Sales → Sales Coach → My Coach**, each salesperson gets a personal brief:
- **Due to order**: customers whose own rhythm says an order is due, each with a one-click
quotation button.
- **Worth a call**: accounts drifting into Watch or At risk.
- **This week**: the running directives.
- A personal wins figure: accepted plays this month, with their estimated and actual value.
"My customers" means the partners assigned to the salesperson plus the customers of their recent
confirmed orders.
## How dismissals teach it
Dismissing a card offers three reasons, and each one does something different:
| Reason | Effect |
| --- | --- |
| Declined | The play goes quiet for 90 days for this customer. |
| Wrong data | The card is flagged for manager review; it shows up on the Performance dashboard. |
| Not relevant | Recorded as the reason, for tuning. |
A plain snooze without a reason quiets the card for 14 days. Suppressed plays stay quiet until
their window passes, so the panel does not nag.
### Daily use · https://nanti.ai/docs/sales-coach/daily-use
The salesperson's flow with the coach, start to finish. Read the brief, say the line, accept with one click, ask the coach about the customer, undo when needed, and log the follow-up.
Applies to: Odoo 19 · Community and Enterprise
## The flow
1. Open a quotation and pick a customer. The **Coach** tab lights up, and the snapshot plus the
first cards render immediately.
2. Within about ten seconds the cards upgrade to AI wording in place. The footer shows
"coaching…" and then "Coached by Nanti AI · just now". Nothing blocks you while it happens.
3. Read the card: two lines of reasoning, then the quoted **talk track**. Tap the talk track to
copy it, so you can paste it into a chat or read it on a call.
4. Narrow the stack with the focus chips (This week, Protect, Grow, Regulars) and act on the
cards that fit the conversation.
## Accepting a play
Click the card's primary action, for example **"+ Add 12 kg"**, **"Set qty to 8"**, or **"Switch
line"**. The change lands on the current, unsaved order: a line is added, a quantity is fixed, a
product is switched or added alongside, or an authorized discount is applied. The card collapses
to a one-line confirmation, "Added 12 kg Jamón Cocido", with an **undo** link.
**Undo** reverts the line change and brings the card back. Dismissals get their own undo too.
You do not need to save the order for the coach to react. It reads the form as you type and
recomputes shortly after you stop editing.
## Logging a follow-up
The **Account health** card is a conversation play, not a product play. Its action, **"Log
follow-up"**, creates a scheduled activity on the account ("Follow up on account health") and
never touches the order. The **Payment nudge** card works the same way: **"Log payment
follow-up"** creates an activity noted "Follow up on open balance" and never changes the quote.
## Asking the coach
With Coach Chat enabled, the **Ask** tab sits next to Messages and Coach. Ask a free-text
question about the customer on the quotation ("Ask about the current customer...") and the
answer comes only from that customer's real numbers, one customer at a time. When the answer
carries an action, accepting it works exactly like accepting a card and counts on the
Performance dashboard. Switching customers clears the thread. The full behavior, including what
a chat turn sends and meters, is in [Concepts](/docs/sales-coach/concepts#ask-the-coach-coach-chat)
and [Security and data](/docs/sales-coach/security-and-data).
## What updates live
- **Changing the customer** recomputes everything immediately.
- **Editing lines** (product, quantity, unit, price, discount) recomputes after a short pause
(1.2 seconds by default), so the panel does not flicker while you type.
- The snapshot deltas, the order-vs-usual bar, the Coach Score, and the card stack all react
together.
- The **Fresh look (1)** button forces a brand-new AI pass whenever you want one; it spends
1 enrichment. The ↻ refresh button beside it re-reads from cache and is free.
## When the order is confirmed
On confirmation, the panel becomes a read-only recap of what the coach suggested and what was
taken. Accepted plays tied to a product get their actual value recorded from the confirmed order
lines, which is what the manager's [Performance dashboard](/docs/sales-coach/managers) reports.
With the [Coach on confirmed orders](/docs/sales-coach/configuration) setting on (off by
default), confirmed sale orders that are still editable keep live coaching and actions; locked
or done orders keep the recap.
### For managers and owners · https://nanti.ai/docs/sales-coach/managers
The Performance dashboard and what each number means, the weekly owner digest email, and how to write a weekly directive that reaches every relevant quotation with your reasoning attached.
Applies to: Odoo 19 · Community and Enterprise
## The Performance dashboard
**Sales → Sales Coach → Performance** (coach Managers only). The subtitle says what it is:
"What the coach found, what the team took, and what it invoiced." A period switcher defaults to
the last 30 days.
The numbers, precisely:
| Metric | Definition |
| --- | --- |
| Coach-attributed revenue | The value of accepted plays on **confirmed orders**, recorded from the confirmed order lines. Real money from real orders, not estimates. |
| Accepted value | The value estimated at the moment each play was accepted. |
| Acceptance rate | Accepted cards divided by shown cards, as a percentage. |
| Where the value comes from | Accepted plays grouped by play type, with count, estimated value, and actual value. |
| Team adoption | Per salesperson: cards shown, accepted, estimated value, actual value. |
| Worth a look | Cards the team flagged as "wrong data" (each needs a manager's review) and the breakdown of dismissal reasons. |
| Running directives | How many directives are currently live. |
Before the team accepts anything, the dashboard says so plainly: "No accepted plays in this
period yet. The numbers start the moment the team takes a card."
Attribution is recorded when the order is confirmed, from the confirmed order
lines. If your process cancels or revises confirmed orders often, read the number with that in
mind.
## The weekly owner digest
An automatic Monday-morning email that tells managers what the coach did last week, without
opening a dashboard. It sends once per week when the company-local time reaches Monday 07:00
(timezone resolved from the company calendar, then the company address, then the user).
The email, top to bottom: the title **"Weekly owner digest"**, a hero tile labelled
**"The number"** (coach-attributed revenue from confirmed orders in the last 7 days, shown
against the trailing 4-week average), then **"At risk"**, **"This week's plays"**, and
**"Wins"**, with footer links to the **Performance dashboard** and **Directives**.
How it is controlled:
- A coach Manager turns it on for the company in Settings (`enable_digest`, off by default). It
is a premium feature: when your subscription does not include it, Settings says so instead of
sending nothing silently.
- It goes to active coach Managers with an email address. Each manager has a personal
**"Do not email me the weekly digest"** opt-out; other managers keep receiving it.
- **"Send me a digest preview"** generates the email on demand so you can see this week's version
before Monday.
## Writing the weekly directive
Under **Sales → Sales Coach → Directives**, create one and set:
1. **Name and type**: push a product, make an offer, or send a message.
2. **Products or categories** it covers.
3. **Dates**: defaults to a one-week window starting today.
4. **Priority weight**: how hard it competes with other coaching (directives always rank near the
top).
5. **"Why / how to argue it"**: your reasoning, in your words. The AI weaves this into the talk
track every salesperson sees, so the argument travels with the instruction.
6. **Discount authority** (offers only): the maximum percentage any salesperson can apply through
the card. The server enforces this ceiling on every accept.
**Targeting**: everyone, a customer segment by tag, buyers of a category, or specific customers.
The directive appears as the **This week** card on every matching quotation and in each
salesperson's My Coach brief. When it expires, **"Duplicate next week"** rolls it forward seven
days with one click.
Write the "why" as if briefing your best salesperson. That text is what turns a push into an
argument the customer actually hears.
## Reviewing "wrong data" flags
When a salesperson dismisses a card as "wrong data", the row lands in the dashboard's review
queue. The usual causes are product links that need correcting and thresholds that need tuning
for your business; both live in [Configuration](/docs/sales-coach/configuration).
### Security and data · https://nanti.ai/docs/sales-coach/security-and-data
Exactly what leaves your Odoo per coaching request, what never does, how the key is handled, what happens when the service is unreachable, and where AI processing runs.
Applies to: Odoo 19 · Community and Enterprise
## The trust model, in one paragraph
The module runs entirely inside your own Odoo, wherever you host it. All analysis of your sales
history happens there, on your server. The only network traffic is **outbound HTTPS** from your
Odoo to the coaching service; the service has **no inbound access to your database**, ever.
## What leaves your Odoo
One compact JSON payload per coaching request, a few kilobytes, containing:
- Your admin-written business profile text and industry preset, locale, and currency name.
- **About the customer**: an opaque reference plus the computed snapshot values (days since last
order, cadence, average order value, revenue trend, range coverage, risk class). The customer's
display name is included only while the **Send customer names** setting is on; turn it off to
send opaque references only.
- **About the current order**: the total, and per line the product name, quantity, unit, unit
price, and discount.
- **The detected signals**: each signal's type, severity, and estimated value, with internal
record identifiers stripped out. Only the figures and names needed to phrase the advice are
sent.
- **Request metadata**: a support fingerprint (a salted one-way SHA-256 hash of the database
identifier and company id, truncated, no personal data) and the installed module version, used
for support and version tracking.
Coach Chat questions travel the same way: a chat turn carries your typed question, the last 20
messages of that conversation, and the same customer-scoped aggregate context as a coaching
request. No new category of data leaves, and no chat transcript is stored by the coaching
service.
## What never leaves
Raw order history, your full product catalog, price lists, line-by-line margins, emails,
documents, and anything about customers who are not on the current quotation. The engine computes
locally and sends aggregates.
## Transport and the key
HTTPS only. The API key travels in a request header on **server-side** calls from your Odoo; it
is never exposed to browser code, and in Odoo only Administrators can see or edit it.
## When the service is unreachable
If the backend is down, the key is missing or invalid, the plan limit is reached, or AI is simply
turned off, the panel serves locally computed cards with built-in wording and shows an
**"offline coaching"** badge. Coaching degrades gracefully; stock Odoo is never affected.
## Caching and retention
- The 6-hour response cache lives **inside your own Odoo database**, on your server, and expired
entries are vacuumed daily.
- Coaching requests and responses are **not stored** by the coaching service; they are processed
transiently in memory, with zero retention.
- Usage telemetry is metadata only (timestamps, endpoint, model, token counts, latency, status),
never request content. Our data policy retains raw events for 12 months and monthly aggregates
after that. Questions about your data go to [hello@nanti.ai](mailto:hello@nanti.ai), our one
contact address for everything.
## The AI provider
AI text is generated by **Anthropic's Claude**, which does not train on API data and processes
requests transiently under a data processing agreement. The coaching service runs on Google Cloud
in the EU (europe-west1, Belgium). Subprocessors: Google Cloud, Anthropic, and Razorpay for
billing (no card data is seen or stored by us). We do not train models on your business data;
that is a standing commitment, not a plan tier.
## Opting things out
| Flag | Effect |
| --- | --- |
| Customer: "coach opt-out" | The customer generates no coaching cards at all. |
| Product: "exclude from coaching" | The product never appears in any play. |
| Setting: Send customer names, off | AI payloads carry opaque customer references only. |
| Setting: Enable AI, off | Everything runs deterministically inside your Odoo; nothing is sent. |
## Languages
The panel UI ships in English and Spanish. Card wording follows the **Language mode** setting:
the salesperson's language (default), the company language, or a fixed language you choose.
### Plans and pricing · https://nanti.ai/docs/sales-coach/plans-and-pricing
What Nanti Sales Coach costs: the Free, Team, and Business per-seat plans, how enrichments meter AI usage, and exactly what happens at every limit.
Applies to: Odoo 19 · Community and Enterprise
Pricing is per seat, billed monthly, in USD. A **seat** is a salesperson who uses the coach;
seats are counted as distinct active users in a month, on one subscription per company (not per
Odoo database). Seats start at one. Every seat adds AI **enrichments** to one monthly pool the
whole team draws from, and deterministic (rule-based) coaching is included without limits on
every plan.
## The plans
| | Free | Team | Business | Enterprise |
| --------------------------------------------------------------------- | ------------ | ----------------- | ----------------- | ---------- |
| Monthly price | $0 | $24 per seat | $39 per seat | Custom |
| Seats | 1 user | from 1 | from 1 | Custom |
| Enrichments per month | 25 | 300 × seats, pooled | 400 × seats, pooled | Custom |
| Deterministic coaching: all plays, Coach Score, directives, offers | ✓ | ✓ | ✓ | ✓ |
| AI-written coaching (uses enrichments) | ✓ | ✓ | ✓ | ✓ |
| Performance dashboard | – | – | ✓ | ✓ |
| Coach Chat (the Ask tab) | – | – | ✓ | ✓ |
| Weekly owner digest | – | – | ✓ | ✓ |
| Expiry push play | – | – | ✓ | ✓ |
| Priority support | – | – | ✓ | ✓ |
| Free onboarding session with us | – | 30 minutes | 1 hour | Tailored |
| SLA | – | – | – | ✓ |
Every new account starts with a **7-day full-feature trial**; when the trial ends, the account
continues on the Free plan's limits. Billing is monthly only; there are no annual plans yet.
## Seats, packs, and top-ups
The subscription is made of quantities you can change month to month:
- **Seats**: add or remove salespeople whenever you need; the monthly price follows. Each seat
brings its plan allowance (300 or 400 enrichments) into the shared pool.
- **Enrichment packs**: 500 enrichments for $25 a month. Add packs to the subscription to raise
every month's pool, or buy one once as a **mid-month top-up** at the same price when a month
runs hot. Quotas added to the subscription renew every month until you drop them.
## Billing
A monthly subscription, charged upfront by credit card with recurring payments, handled and
secured by Razorpay. Prices are in USD and all-inclusive: taxes and card fees are included,
and nothing is added on top. The month's quota stays available for the whole paid month, and
payments are not refunded. Cancel any time by writing to us; billing stops after the current
month.
Early-access accounts get their subscription set up with us directly. Start from the
[early-access list](https://nanti.ai/sales-coach#early-access) or write to hello@nanti.ai.
## How enrichments work
One enrichment is the AI studying one customer on one quotation or order: the analysis, the
reasoning, and the words to say. The rules, as the in-product usage popover states them:
- For the next **6 hours** that customer's coaching is served from your own Odoo's cache:
reopening the document, refreshing the panel, editing lines, or a teammate viewing the same
customer costs nothing.
- You spend another enrichment only for a **different customer**, after the 6-hour window, or
when you press **Fresh look (1)** to force a new AI pass (its tooltip: "Get a brand-new AI
read of this customer - uses 1 enrichment."). The ↻ refresh button beside it re-reads from
cache and is free.
- A **chat question counts as 2** enrichments, from the same pool as card wording.
- Connection checks are free.
## The meters in the panel
The panel header shows the plan badge, and managers see the tenant meter
(**"ENRICHMENTS: used / limit · resets date"**). On the Free plan the counter reads
**"ENRICHMENTS: used/limit"**; if the team is over its licensed seats, managers also see
**"SEATS: active / licensed"**. A salesperson with a
[per-salesperson cap](/docs/sales-coach/configuration) set sees a personal
**"YOU: used/cap"** meter.
The module never computes plan access locally: it only displays the entitlement verdict the
backend reports, so what the panel shows is always what the subscription actually allows.
## At the limits
Coaching never hard-stops. At every limit the panel keeps serving deterministic cards with
built-in wording. Nothing extra is ever charged automatically: a mid-month top-up is always
your choice. What each state looks like:
| State | In the panel | In Settings → Test connection |
| --- | --- | --- |
| Monthly pool used up | "This month's enrichments are used up - coaching resumes on the 1st." | "This Nanti.ai tenant has reached its monthly enrichment limit." |
| Personal cap reached | No error by design: the footer shows "deterministic coaching" and the "YOU" meter marks the cap. | (not reported here) |
| Over licensed seats | "You're above your team's licensed seats - AI coaching is reserved for licensed users. Ask your manager for a seat." | "This Nanti.ai tenant is over its licensed seat count." |
| Subscription suspended | "Subscription inactive - AI coaching is paused." | "This Nanti.ai tenant is suspended." |
| No key or no subscription | "AI coaching needs an active Nanti subscription." | "The API key is missing or invalid." |
| Feature not in the plan | "This AI feature is not included in the current plan." (surfaces name it, e.g. "Coach Chat is part of the Business plan") | Included features are listed in the success message. |
Plan changes are applied by the backend and reach the module on its next check; there is nothing
to reinstall.
### Configuration · https://nanti.ai/docs/sales-coach/configuration
Every setting an administrator can change, with its real name, default, and what it does. One settings record per company, under Sales, Sales Coach, Settings.
Applies to: Odoo 19 · Community and Enterprise
All settings live on one record per company under **Sales → Sales Coach → Settings**, and that is
the only place the coach is configured. Odoo's general Settings page carries a **Coach
configuration** pointer with an **Open Sales Coach settings** button, nothing editable, so every
company has one authoritative settings record. The form always edits the record of the company
selected in Odoo; its company field is read-only. Only coach Managers can open it; only Odoo
Administrators can see the API key.
## Connection and AI
| Setting | Name | Default | What it does |
| --- | --- | --- | --- |
| Backend URL | `backend_url` | `https://coach.nanti.ai` | Where this Odoo server sends AI coaching requests. |
| API key | `api_key` | empty | The private tenant key used by the Odoo server. Administrator-only, never sent to the browser. |
| Enable AI | `enable_llm` | on | Turn AI enrichment on or off. When off, salespeople still get deterministic local coaching cards. |
| Send customer names | `send_customer_names` | on | Allow customer names in the compact AI payloads. Turn off to send only opaque references. |
| Language mode | `language_mode` | user | Which language the AI writes in: the salesperson's, the company's, or a fixed one. |
| Fixed language | `fixed_language` | empty | The exact language used when the mode is fixed. |
| Business profile | `business_profile` | empty | A short description of your business that helps the AI argue like your sales manager. |
| Industry preset | `industry_preset` | generic | generic, food distribution, wholesale, or retail supply. |
| Coach Chat | `enable_chat` | off | The Ask tab: free-text questions about the current customer. A premium AI feature; a chat question counts as 2 enrichments. |
| Per-salesperson AI cap | `user_monthly_cap` | 0 | Optional monthly enrichment cap per salesperson; 0 means no cap. At the cap, that user keeps deterministic coaching until next month. |
| Weekly digest | `enable_digest` | off | The Monday-morning manager digest email. See [For managers and owners](/docs/sales-coach/managers#the-weekly-owner-digest). |
## Analysis window and panel
| Setting | Name | Default | What it does |
| --- | --- | --- | --- |
| Lookback days | `lookback_days` | 365 | How many days of confirmed sales history the coach studies. |
| Min orders for pattern | `min_orders_for_pattern` | 3 | Purchases required before the coach trusts a repeat pattern. |
| Max cards | `max_cards` | 7 | Cards shown immediately; the rest sit behind "Show N more". |
| Debounce (ms) | `debounce_ms` | 1200 | How long the panel waits after line edits before recomputing. |
| Cache TTL (hours) | `cache_ttl_hours` | 6 | How long AI wording can be reused for the same context. The cache lives in your own database. |
| History source | `history_source` | orders | Confirmed sales orders are the history source today. |
| Coach on confirmed orders | `coach_on_confirmed_orders` | off | Coach confirmed sale orders while they are still editable; turn off to keep confirmed orders as a read-only recap. Locked or done orders always keep the recap. |
## Signal thresholds
| Setting | Name | Default |
| --- | --- | --- |
| Missing-regular factor | `missing_regular_factor` | 0.8 |
| Quantity-drop threshold | `qty_drop_threshold` | 0.6 |
| Low-basket threshold | `basket_low_threshold` | 0.5 |
| New-line target | `new_lines_target` | 2 |
| Affinity minimum lift | `affinity_min_lift` | 1.3 |
| Affinity minimum support | `affinity_min_support` | 5 |
| Allow heuristic links | `allow_heuristic_links` | off |
| Enable expiry push | `enable_expiry_push` | off |
| Expiry window (days) | `expiry_window_days` | 21 |
| Enable Coach Score | `enable_coach_score` | on |
| Discount warning floor (%) | `max_discount_warn` | 15.0 |
| Margin warning floor | `min_margin_warn` | 0.0 (0 = cost-based margin check off) |
| Volume-nudge band | `volume_nudge_band` | 0.10 |
| Show credit to salespeople | `show_credit_to_salespeople` | off (managers always see credit) |
Lower thresholds make the coach speak sooner; higher ones make it stricter. The defaults are
tuned for repeat-order businesses.
## Per-signal and per-KPI toggles
Every play has its own on/off switch (`enable_sig_*`), and every snapshot KPI has one too
(`enable_kpi_*`). All shipped plays and the six core KPIs default to **on**; the four optional
KPIs (overdue balance, margin profile, year-to-date revenue, premium share) default to **off**,
and overdue balance and margin render only when the underlying data exists. The expiry push
play additionally requires the master `enable_expiry_push` switch above; the payment nudge play
additionally requires Accounting to be installed, and shows to salespeople only with
"Show credit to salespeople" on.
Four KPI switches are visible in Settings but their tiles are still in development and do not
render yet: overdue balance, margin profile, YTD revenue, and premium share. Leaving them on
has no effect today; they are wired for what ships next. (The credit watch, margin guard, and
volume nudge plays formerly listed here are live now.)
## Product and customer flags
| Flag | Where | Effect |
| --- | --- | --- |
| Strategic for coaching | Product | The coach may suggest it as a range-growth play. |
| Exclude from coaching | Product | The product never appears in any play. |
| Clearance | Product | Stock the coach may push to category buyers, when expiry push is on. |
| Coach opt-out | Customer | The customer generates no coaching at all. |
## Access
Access to the panel is by security group, not by setting: the **User** group to use the coach,
the **Manager** group to configure it. See
[Getting started](/docs/sales-coach/getting-started#who-can-do-what).
### Troubleshooting · https://nanti.ai/docs/sales-coach/troubleshooting
Connection and key errors with their exact messages, quiet coaching explained, what to do about wrong-looking suggestions, permission problems, and offline mode.
Applies to: Odoo 19 · Community and Enterprise
## Connection and key errors
The **Test connection** button in Settings reports exactly what is wrong. The messages, verbatim,
and the fixes:
| Message | Fix |
| --- | --- |
| "The backend URL is not valid. Use a full http:// or https:// address." | The Backend URL needs a full scheme and host. |
| "The API key is missing or invalid." | Set or replace the tenant key. Only an Odoo Administrator can edit it. Test connection validates the stored key server-side, so a coach Manager without administrator rights gets a real verdict, not a false version of this error. |
| "This Nanti.ai tenant is suspended." | Contact us. |
| "This Nanti.ai tenant has reached its monthly enrichment limit." | The panel keeps working in deterministic mode; the pool resets monthly. |
| "This Nanti.ai tenant is over its licensed seat count." | Seats are counted as distinct active users in the month. Reduce active users or add seats; over-seat salespeople keep deterministic coaching and see a note to ask their manager for a seat. |
| "The backend is rate limiting this tenant. Try again shortly." | Wait a moment and retry. |
| "The backend is unreachable from Odoo right now." | Check network egress and DNS from your Odoo server to the backend, then confirm with Test connection. The module retries once on server errors before degrading. |
## The coach is quiet
- **"No coaching plays right now."** with plenty of history is normal, not a fault. The engine
stays silent unless it has a well-evidenced play: never wrong, sometimes silent.
- **"New customer - no history yet."** Most per-customer plays need at least three confirmed
orders in the lookback window (both numbers are
[configurable](/docs/sales-coach/configuration)). Until then the customer gets company-wide
coaching only: directives and strategic products.
- **No panel at all**: the user is missing the **Sales Coach / User** security group. Grant it.
## A suggestion looks wrong
Dismiss it with the **"wrong data"** reason. That flags the card for a manager, who sees it in
the Performance dashboard's review queue. The usual root causes:
- A product link needs correcting (a wrong premium or alternative pairing).
- A threshold is mistuned for your business: the missing-regular factor, quantity-drop threshold,
low-basket threshold, or the minimum orders for a pattern.
- The wording was rephrased by the AI but the number is stale: numbers always come from your
data, so a wrong number means stale stats or cache. Press **Fresh look (1)** to force a
fresh AI pass.
The AI rephrases; it does not invent. If a figure is off, the fix is in the data or the
thresholds, not in the AI settings.
## Permission problems
- Only **Administrators** can see or edit the API key.
- Only coach **Managers** (or Odoo Sales Administrators, who imply Manager) can open Settings,
Directives, product flags, and the Performance dashboard. Anyone else gets "Only Sales Coach
managers can open the dashboard."
- Users without the coach User group get "You do not have access to Nanti Sales Coach."
## Offline mode
The footer badge **"offline coaching"** means the panel is degraded for one of the reasons above,
or AI is turned off. Locally computed cards keep working with built-in wording, and stock Odoo is
unaffected. Fix the connection, then confirm with **Test connection**; the badge returns to
"Coached by Nanti AI" on the next coaching pass.
Coach Chat degrades the same way: the Ask tab says **"Chat needs a connection. Cards still
work."** while the service is unreachable, and **"Coach Chat is turned off."** when the setting
is off. Cards are never blocked by chat.
## A discount was rejected on accept
"This coach action asks for X%, but the directive only authorizes Y%." The accept tried to exceed
the directive's discount authority, which the server enforces as a hard ceiling. Use the
authorized percentage, or have a manager raise the directive's authority.
## Still stuck
Write to [hello@nanti.ai](mailto:hello@nanti.ai?subject=Sales%20Coach%20support) with the exact
message you see. We answer plainly.
## Context Layer for Odoo
### Introduction · https://nanti.ai/docs/context-layer/introduction
The Context Layer for Odoo (OCL) is the verified meaning layer between any AI and Odoo. A connector moves data; the context layer tells the AI what the data means, which records count, which fields mislead, and whether an action is safe. This is what it is, what it is not, and where the alpha stands.
Applies to: Technical alpha · Odoo 19
AI gets Odoo wrong for one reason that compounds: Odoo's technical schema does not explain
business meaning. The field names, models, and relations are all there, but nothing in the schema
says that `account.move` is four different business documents, that `amount_total` is not a
universal revenue metric, or that a stock quantity means nothing without a warehouse. Hand an AI
the raw schema and it will answer confidently and, often, wrongly.
The Context Layer for Odoo (working label **OCL**) is the fix. It is a **verified meaning layer**
that sits between any AI and Odoo.
```text
Connector = transport moves data in and out of Odoo
OCL = meaning says what the data means and which records count
AI = reasoning answers or acts, with fewer unsupported assumptions
```
A question flows through all three:
```text
question
-> the connector exposes Odoo
-> OCL supplies the smallest verified context for that question
-> the AI answers or acts, with fewer unsupported assumptions
```
## What OCL tells an AI
- what Odoo records **mean**;
- which records **count**;
- which tempting fields or interpretations are **wrong**;
- which **filters and joins** matter;
- which **company, currency, and security** boundaries apply;
- when **ambiguity** must be clarified;
- when a **write is unsafe**.
Each of those is a different [kind of knowledge](/docs/context-layer/kinds-of-knowledge), and each
fact is proven, not asserted by a language model. See
[Verification and trust](/docs/context-layer/verification-and-trust).
## What OCL is not
- not a chatbot;
- not another connector;
- not generic RAG over Odoo documentation;
- not a replacement for Odoo permissions;
- not a claim that AI-generated writes are automatically safe;
- not a complete ontology for every Odoo module.
OCL does not move data and does not reason. It supplies meaning to whatever connector moves the
data and whatever model does the reasoning. It works alongside your stack, not instead of it.
## Where the alpha stands
This is an **early technical alpha** targeting **Odoo 19** only (Community and Enterprise). A
released semantic kernel exists, with executable verification, immutable content-hashed
releases, provenance, deterministic retrieval over released content, and risk-preserving context
assembly. The
consumption surfaces (MCP, REST, Python, TypeScript, static context packs, signed offline caches)
are implemented.
The **open standard is public today** at
[github.com/Nantiai/ocl-standard](https://github.com/Nantiai/ocl-standard) (Apache-2.0): clone it
and, in minutes, run the CLI and wire an example MCP server over ten candidate entries. See the
[Quickstart](/docs/context-layer/quickstart). The **verified 44-entry runtime** over your real
Odoo 19 data comes with runtime access.
It is honest about its edges: the tenant system is an implemented foundation, not a finished
hosted product; the benchmark is designed and running but its independent scoring is not yet
published; and "context layer" is a working category label, not a final name. For the current
state in detail, see
[Coverage and release status](/docs/context-layer/coverage-and-status) and
[Current limitations](/docs/context-layer/limitations).
## Where to go next
- [Quickstart](/docs/context-layer/quickstart): clone the standard and run it in minutes.
- [Why a context layer](/docs/context-layer/why-a-context-layer): the problem, made concrete on
`account.move`.
- [Core concepts](/docs/context-layer/concepts): the layers, the assembler, and the vocabulary.
- [Consuming the layer](/docs/context-layer/consuming): the surfaces an AI reads it through.
- [The open format](/docs/context-layer/open-format): the public spec, schemas, validator, and
SDKs.
### Quickstart · https://nanti.ai/docs/context-layer/quickstart
Clone the open standard, run the CLI against ten candidate examples, and wire the example MCP server into your agent. Everything here works today with no key and no account; the verified 44-entry runtime comes with access.
Applies to: OCL standard v0 · Odoo 19
The open standard is on GitHub under Apache-2.0. You can adopt the format, inspect real context,
and demonstrate the MCP interface in a couple of minutes, with no key and no account.
Everything on this page runs against **ten public candidate example entries** for Odoo 19. That
is enough to adopt the format and build an integration. The **verified 44-entry registry** and a
hosted endpoint, which answer over your real Odoo 19 data, come with
[verified runtime access](/docs/context-layer/coverage-and-status).
## 1. Clone the standard
```bash
git clone https://github.com/Nantiai/ocl-standard.git
cd ocl-standard
```
The repository holds the format `spec/`, the `reference/` Python validator and example
implementations, the `conformance/` suite, and the `docs/` guides.
## 2. Inspect context from the CLI
Ask the example server the same questions an agent would, and read exactly what it returns:
```bash
python reference/python/ocl_examples.py get-context "What is revenue?"
python reference/python/ocl_examples.py resolve-noun "vendor bill"
python reference/python/ocl_examples.py explain-field account.move invoice_user_id
```
Every command runs against the ten candidate examples. This is the fastest way to see what a
context pack actually looks like.
## 3. Wire the MCP server into your agent
Point any MCP client at the example server. It exposes the real OCL operations over the same ten
examples:
```json
{
"mcpServers": {
"ocl-public-examples": {
"command": "python",
"args": [
"/absolute/path/to/ocl-standard/reference/python/ocl_examples.py",
"serve-mcp"
]
}
}
}
```
The public example server exposes `get_context`, `resolve_noun`, and `explain_field`. The full
contract also defines `get_join_path` and `validate_write_intent`, which are served by the
verified runtime.
## 4. Validate your own entries
Install the reference validator and check entries against the format:
```bash
python -m pip install -e sdk/python
```
```python
from ocl_spec import validate_entry, ValidationError
# document = your entry; schema = the loaded entry JSON Schema
errors = validate_entry(document, schema)
```
## Where to go next
- [Consuming the layer](/docs/context-layer/consuming): the operations, surfaces, and how they fit
a real stack.
- [Entry anatomy](/docs/context-layer/entry-anatomy): the shape of a single verified fact.
- [The open format](/docs/context-layer/open-format): the spec, schemas, conformance, and the
public-standard versus commercial-registry boundary.
- Full guide in the repo:
[USING_OCL.md](https://github.com/Nantiai/ocl-standard/blob/main/docs/USING_OCL.md).
### Why a context layer · https://nanti.ai/docs/context-layer/why-a-context-layer
Why raw Odoo schema is not enough for AI, made concrete on account.move. The three costs of handing an AI raw schema and records, and why a connector alone moves data but does not supply meaning.
Applies to: Technical alpha · Odoo 19
## Schema is not meaning
Odoo exposes technical structure. Technical structure lists the tables and fields; it does not
encode what they mean for the business. That gap is invisible until an AI falls into it, and then
the answer looks right and is wrong.
Take one table, `account.move`. Its schema shows fields like `move_type`, `state`,
`amount_total`, and `invoice_user_id`. What the schema does not say:
- `account.move` is **not** "the invoices." It is customer invoices, vendor bills, refunds, and
journal entries all at once. A **customer invoice** requires `move_type = out_invoice`; a
**vendor bill** requires `move_type = in_invoice`.
- Financial impact requires `state = posted`. Counting draft moves as real revenue is a common,
plausible mistake.
- `amount_total` is **not** a universal revenue metric. Different document types and definitions
make "just sum `amount_total`" wrong more often than right.
- The salesperson who owns an invoice is `invoice_user_id`, not a guessed `user_id`.
Every one of those is a business fact the schema cannot tell you. An AI with only the schema
guesses, and its guess is confident whether or not it is correct.
## The three costs of raw schema
Hand an AI raw schema and records and you pay three costs, every day:
1. **Wrong interpretation.** A plausible-looking field can be the wrong business field, and a
valid relational path can still produce the wrong metric.
2. **Rediscovery, every time.** Large prompts and the same discovery work repeated on every
question, in every tool, in every session. Nothing learned is kept.
3. **Unsafe actions that validate.** A write can be semantically wrong and still technically
valid. State, company, currency, and record rules change what an action means.
The state of a record, the company, the currency, and the record rules all change what the answer
should be. None of that lives in the schema.
## A connector is not enough
A connector, including an MCP server, answers **how** an AI can read or write Odoo records. It
is transport. It does not answer **what** those records mean, which of them count, or how they
combine into a correct answer. That is interpretation, and it is a different job.
Connectors are necessary and good at their job. But a connector that faithfully returns
`account.move` rows has done nothing to stop the AI from summing the wrong ones. The context layer
is the missing half: it supplies the verified meaning the connector never carried, so the same
model, on the same connector, answers with fewer unsupported assumptions.
That is the whole thesis. A connector moves data; the context layer supplies meaning; the AI
reasons. Next: [Core concepts](/docs/context-layer/concepts).
### Core concepts · https://nanti.ai/docs/context-layer/concepts
The vocabulary of the context layer: entries, the three truth layers (base ontology, tenant overlay, tenant memory), the runtime assembler, context packs, contracts, and how one question becomes one small verified context.
Applies to: Technical alpha · Odoo 19
The context layer has a small vocabulary. Learn these six words and the rest of the docs read
easily.
## Entry
An **entry** is one verified semantic fact: a claim, the evidence behind it, executable
assertions that prove it, the scope it applies to, a confidence estimate, and provenance. It is
the atom of the layer. The full shape is in [Entry anatomy](/docs/context-layer/entry-anatomy),
and the different shapes of claim are in
[Kinds of knowledge](/docs/context-layer/kinds-of-knowledge).
## The three truth layers
Meaning comes from three layers, each a different source of truth.
| Layer | What it holds |
| --- | --- |
| **Base ontology** | Verified semantics per Odoo version and module: what models and fields mean, which filters and joins are correct, which metrics are defined how, and the warnings that go with them. This is the released kernel. |
| **Tenant overlay** | The differences that make one database its own: detected customizations and instance quirks, proposed and then confirmed with the tenant rather than assumed. |
| **Tenant memory** | Confirmed business facts and prior decisions, kept with provenance and scope, and revocable. Confirmed once, never rediscovered. |
The base ontology is the released, immutable part. The overlay and memory are the **tenant
system**, which is an implemented foundation in the alpha, not a finished hosted product. See
[Current limitations](/docs/context-layer/limitations).
## The runtime assembler
The **assembler** turns one question into one small **context pack**: the applicable facts,
joins, filters, and warnings for that question, with provenance, sized for the model. It is never
a registry dump. Assembly is **risk-preserving**: a critical warning cannot be silently dropped
to fit a token budget.
```text
question -> assembler -> context pack -> any AI
(selects the smallest applicable, verified context)
```
## Context pack
A **context pack** is the assembler's output: a request-scoped JSON (or text) artifact that
conforms to the public context-pack schema. It is what the AI actually reads before it answers or
acts. More in [Context packs and offline](/docs/context-layer/context-packs).
## Contract and applicability
Every entry declares what it **applies to**: Odoo versions, editions, required modules,
localizations. A **contract** ties a set of released entries to a guaranteed scope. Together they
are how the layer stays honest across setups: a fact proven for Odoo 19 Community with `account`
installed is offered only where that holds, and withheld everywhere else.
None of these layers replace Odoo. The context layer supplies interpretation and safety context;
Odoo's access rights, record rules, ORM constraints, and business methods remain authoritative
for what is allowed and how it executes. See
[Security and boundaries](/docs/context-layer/security-and-boundaries).
## How it fits together
1. A connector exposes Odoo (transport).
2. The assembler reads the three truth layers and builds the smallest verified context pack for
the question.
3. The AI reasons over that pack and the connector's data, with fewer unsupported assumptions.
The layer never moves data and never reasons. It supplies meaning to whatever does.
### Entry anatomy · https://nanti.ai/docs/context-layer/entry-anatomy
An OCL entry is one verified semantic fact about Odoo. It carries a claim, the evidence behind it, executable assertions that prove it, the versions and modules it applies to, a confidence estimate, and provenance. This is the whole shape, field by field.
Applies to: OCL format v0 · Odoo 19
An entry is the atom of the context layer: one small, self-contained fact about what something
in Odoo means and how to use it safely. Everything else, packs, retrieval, context assembly, is
built out of entries.
The example below is a real entry from the public format examples. It is a **format
demonstration**, not a released registry entry, so read it for its shape rather than as a
guaranteed fact.
```json title="entry.customer_invoice.json"
{
"document_kind": "ocl.entry",
"schema_version": "0.1.0",
"id": "ocl.example.odoo19.account.customer_invoice",
"type": "noun_mapping",
"status": "evidence_attached",
"applies_to": {
"odoo_versions": ["19.0"],
"editions": ["community", "enterprise"],
"requires_modules": ["account"]
},
"claim": {
"summary": "A customer invoice is an account.move whose move_type is out_invoice.",
"business_noun": "customer invoice",
"aliases": ["sales invoice"],
"model": "account.move",
"domain": [["move_type", "=", "out_invoice"]],
"limitations": ["Add state = posted only when the task requires accounting impact."]
},
"evidence": [
{
"id": "move_type_source",
"kind": "source_code",
"locator": "https://github.com/odoo/odoo/blob/7beeb76.../account_move.py#L142-L159",
"observed_at": "2026-07-11"
}
],
"assertions": [
{ "id": "out_invoice_selection", "kind": "selection_contains", "mode": "snapshot",
"target": { "model": "account.move", "field": "move_type" }, "expected": ["out_invoice"] },
{ "id": "domain_executes", "kind": "domain_executes", "mode": "live_read",
"target": { "model": "account.move", "path": "claim.domain" }, "expected": true }
],
"confidence": {
"score": 0.99,
"basis": "pinned_official_source",
"rationale": "The discriminator is explicit; live execution remains pending."
},
"provenance": {
"created_by": "stage1-source-normalizer",
"created_at": "2026-07-11",
"change_reason": "Public v0 format example."
}
}
```
## The claim
The `claim` is the meaning itself, in a form an AI can act on. For a noun mapping it names the
`business_noun` ("customer invoice"), its `aliases`, the `model` it lives on, and the `domain`
that selects the right records. `limitations` records the fine print: here, that `state = posted`
belongs only when the task actually needs accounting impact, so an AI does not silently exclude
draft invoices when the question did not ask it to.
Domains are portable. A condition is `[field, operator, value]`, logical operators are `"&"`,
`"|"`, `"!"`, and runtime values use a structured placeholder such as `{"$var": "today"}` rather
than an unquoted expression. That keeps a claim readable by any consumer, not just Odoo.
## Applicability
`applies_to` is the scope: the Odoo versions, editions, required modules, and localizations a
claim holds for. It is what makes the layer honest across setups. A fact proven on Odoo 19
Community with the `account` module installed says exactly that, and the assembler will not hand
it to an AI working a database where it does not apply.
## Evidence
`evidence` is where the claim comes from: pinned source code (a file, a commit, line numbers),
official documentation, a live observation, or expert review. Evidence is referenced by id from
the assertions, so every check points back at what it is checking against. The customer-invoice
entry cites the exact lines of Odoo's `account_move.py` at a pinned commit.
## Assertions
`assertions` are the executable part: code that proves the claim on a real Odoo. Each has a
`kind` and a `mode`.
- **`snapshot`** assertions check normalized metadata, for example that `move_type` really offers
`out_invoice` as a selection value (`selection_contains`).
- **`live_read`** assertions run against a live database, for example that the claim's domain
actually compiles and executes (`domain_executes`).
Syntax validation alone does not prove a domain runs on a model; that is exactly what a
`live_read` assertion is for. This is the mechanism behind the layer's core promise: a fact is
only as good as the checks that pass for it.
## Confidence
`confidence` is a separate estimate with a `score`, a `basis`, and a `rationale`. It is
deliberately **not** the same thing as trust state. An entry can cite a pinned official source
and still be only `evidence_attached` because its live assertions have not run yet, exactly as
the example shows: confidence `0.99`, status `evidence_attached`.
Sounding sure is not the same as being right. A high confidence score never promotes an entry
to verified. Only passing the entry's full release policy, including live assertions and human
review where the policy requires it, does that. See
[Verification and trust](/docs/context-layer/verification-and-trust).
## Provenance
`provenance` records who created the entry, when, who reviewed it, and why it changed. Combined
with immutable releases, this is what lets a consumer trust that a fact has not quietly shifted
under them between one answer and the next.
## The shape, in one line
> A claim, proven by assertions against cited evidence, scoped by applicability, rated by
> confidence, and signed by provenance.
Every kind of knowledge in the layer, noun mappings, negative knowledge, field semantics, joins,
security notes, and disambiguations, uses this same envelope. What changes between them is the
shape of the `claim`. See [Kinds of knowledge](/docs/context-layer/kinds-of-knowledge).
### Verification and trust · https://nanti.ai/docs/context-layer/verification-and-trust
What "verified" means in the context layer, and what it deliberately does not mean. The entry trust states, the difference between confidence and verification, immutable content-addressed releases, and provenance.
Applies to: OCL format v0 · Odoo 19
The context layer exists to solve one problem: an AI given raw Odoo schema guesses, and its
guesses look plausible whether they are right or wrong. So the whole point of an entry is that its
meaning is **proven**, not asserted by a language model. This page is how that proof works.
"Verified" must never mean "an LLM wrote plausible YAML." A fact earns the word by passing
executable checks and, where the policy requires it, human review, never by sounding confident.
## Trust states
An entry moves through explicit states. It never skips to the end.
```text
draft -> evidence_attached -> asserted -> verified
| |
v v
degraded -> deprecated -> retired
```
- **`draft`**: the claim is written but not yet backed.
- **`evidence_attached`**: sources are cited, but the checks have not run.
- **`asserted`**: the entry's declared assertions pass at their stated level.
- **`verified`**: the entry has passed its full release policy, including live and behavioral
assertions and human review where applicable.
- **`degraded` / `deprecated` / `retired`**: the branches for a fact that stopped holding, was
superseded, or was withdrawn.
`asserted` and `verified` are different on purpose. Assertions passing is necessary but not
sufficient: verification is the whole release policy, not one green check.
## Confidence is separate
Every entry also carries a confidence score. It is an estimate of how sure the claim is, with a
basis and a rationale. It is **not** the trust state and can never raise it. An entry can cite a
pinned official source and score high confidence while still sitting at `evidence_attached`
because its live assertions have not been executed. Keeping the two apart is what stops a
confident-sounding guess from being treated as a proven fact.
## Executable verification
Verification is code, not prose. An entry's assertions run against real Odoo:
- **snapshot** assertions check normalized metadata (does this field really offer this selection
value?);
- **live_read** assertions run against a live database (does this domain actually compile and
execute on this model?).
A claim that code cannot fully prove, a business convention, a policy, an interpretation, needs
source evidence or explicit human confirmation before it can advance. This is why the layer is
categorically different from documentation or from an LLM generating YAML: the claim and the check
that proves it travel together.
## Immutable, content-hashed releases
Released entries are frozen. Once an entry is released it is immutable, and releases are
**content-hashed**: a release is identified by a hash of its contents, so any change produces a
different release rather than silently mutating an existing one. A consumer can therefore pin a
release and know the meaning it is reading today is byte-for-byte the meaning it read last week.
Note that release membership is established by the catalog and its release locks, separately from
an entry's trust state. "Released" and "verified" are related but distinct: an entry is verified
by passing its policy, and it is part of a release by being in a signed, hashed catalog.
Verification is re-run against multiple Odoo 19 anchors so a release is not proven against a
single moving target. See
[Coverage and release status](/docs/context-layer/coverage-and-status) for the anchors and what
is released today.
## Provenance
Every entry records who created it, who reviewed it, when, and why it changed. Provenance plus
immutable releases is what makes the layer auditable: a fact can be traced from the answer an AI
gave, back through the entry it used, to the evidence and the checks that proved it.
### Kinds of knowledge · https://nanti.ai/docs/context-layer/kinds-of-knowledge
The context layer does not store one kind of fact. It stores noun mappings, default filters, join paths, field semantics, negative knowledge, disambiguation, security notes, gotchas, and write policies, each a different shape of meaning an AI needs and raw schema does not carry.
Applies to: OCL format v0 · Odoo 19
Raw Odoo schema tells an AI what fields exist. It does not tell it what a record means, which
records count, how tables should be joined, which tempting fields lie, or when an action is
dangerous. Each of those is a different kind of knowledge, and each has its own entry `type`. All
share the one [entry envelope](/docs/context-layer/entry-anatomy); what differs is the shape of
the `claim`.
The examples below are drawn from the public format examples. They are format demonstrations, not
released registry facts.
## Noun mapping
Maps a business noun to the exact model and record population that means it.
> A **customer invoice** is an `account.move` whose `move_type = out_invoice`.
> A **vendor bill** is an `account.move` whose `move_type = in_invoice`.
The same `account.move` table is a customer invoice, a vendor bill, a refund, or a journal entry
depending on `move_type`. A noun mapping is what stops an AI from counting all of them as "the
invoices."
## Default filter
Records the context-dependent filter that a class of questions needs, and, just as importantly,
when not to apply it.
> For accounting-impact analysis, restrict `account.move` to `state = posted`.
> Draft-workflow questions must not use this filter blindly.
## Join path
A typed relationship path, so an AI joins tables the correct way rather than guessing a plausible
one.
> To get from `sale.order.line` to `res.partner`:
> `sale.order.line → order_id → sale.order → partner_id → res.partner`.
A different join path can silently answer a different question. Typing the path removes the guess.
## Field semantic
Explains what a field actually means, rather than trusting its label.
> On `account.move`, the salesperson is `invoice_user_id`, not a guessed `user_id`.
## Negative knowledge
Encodes a tempting interpretation that is wrong, the trap itself, so an AI is warned off it.
> Treating one plausible-looking total field as a universal revenue metric mixes different
> document types and business definitions. Resolve the metric definition and record population
> first.
Most of what makes AI wrong on Odoo is not missing facts, it is confident wrong ones. Negative
knowledge is the layer writing down the specific mistakes worth preventing.
## Disambiguation
Marks a concept that has more than one valid business meaning, and the question that resolves it.
> "**Revenue**" can mean general-ledger revenue or invoice-based sales, on different date bases.
> The correct behavior is often to ask which definition applies, not to pick one silently.
For genuinely ambiguous questions, asking a clarifying question is the right answer, and the
layer says so rather than forcing a single universal definition.
## Security note
Records a visibility or boundary rule that changes what an answer means.
> Records visible under `allowed_company_ids` are not proof of globally complete records.
Company, currency, and access boundaries change the answer. The layer carries them so an AI does
not mistake "what I can see" for "what is true."
## Gotcha
Flags a context-sensitive computed value that answers a different question than intended when its
scope is left implicit.
> Reading a stock quantity without defining company, warehouse, or location scope can answer a
> different question than the one asked. Make the scope explicit first.
## Write policy
Represents a high-risk business transition and how to perform it safely.
> Posting an `account.move` is a **critical** operation. Prefer the model's business method after
> authorization and validation; require an explicit record set, Odoo authorization, and human
> approval.
A write policy lets the layer warn that an intended action looks unsafe. It never bypasses Odoo
permissions and never makes an AI-generated write automatically safe. Odoo remains the authority
for what is allowed and how it executes. See
[Security and boundaries](/docs/context-layer/security-and-boundaries).
### Consuming the layer · https://nanti.ai/docs/context-layer/consuming
The five operations any AI reads the context layer through, and the surfaces that expose them: MCP, REST, Python, TypeScript, static context packs, and signed offline caches. What is implemented today, and how distribution opens.
Applies to: Technical alpha · Odoo 19
The context layer is designed to be read the same way regardless of which AI, connector, or
transport you use. Everything reduces to five operations.
## The five operations
| Operation | What it answers |
| --- | --- |
| `get_context` | For this question, give me the smallest applicable, verified context pack. |
| `resolve_noun` | What model and record population does this business noun map to? |
| `explain_field` | What does this field actually mean, beyond its label? |
| `get_join_path` | What is the correct typed path from this model to that one? |
| `validate_write_intent` | Is this intended write risky, and what is the safe way to do it? |
`get_context` is the main entry point; the other four are focused lookups an agent can call
mid-reasoning. `validate_write_intent` is **advisory**: it can warn, but it never authorizes an
action and never implies permission. Odoo remains the authority.
## Surfaces
The same five operations are exposed through several surfaces, so the layer fits whatever an AI
stack already speaks:
- **MCP**: the operations exposed as tools to an MCP client, served locally.
- **REST**: the same operations over HTTP, as `POST /v1/`.
- **Python**: a runtime client, plus a public entry-**validation** API (below).
- **TypeScript**: a dependency-free client wrapping the REST operations.
- **Static context packs**: a request-scoped JSON artifact you assemble ahead of time and hand
to a model, no live service required.
- **Signed offline caches**: an entitlement-scoped, signed cache served locally, for air-gapped
or offline use.
## The reference clients
The public kit's shapes are stable enough to show. The Python package exposes entry validation:
```python
from ocl_spec import ValidationError, validate_entry
# `document` is your entry; `schema` is the loaded entry JSON Schema.
errors = validate_entry(document, schema)
```
The TypeScript client (`OCLClient`) wraps the REST operations:
```ts
const client = new OCLClient(baseUrl);
await client.getContext(question, scope);
await client.resolveNoun(noun, scope);
await client.explainField(model, field, scope);
await client.getJoinPath(fromModel, toModel, scope);
await client.validateWriteIntent(model, operation, scope);
```
`scope` carries the Odoo version, edition, and modules, so the layer returns only facts that apply
to the database in question.
The open standard is on GitHub, Apache-2.0: clone it, run the CLI, and wire the example MCP
server (`get_context`, `resolve_noun`, `explain_field`) over **ten candidate example entries**
today. See the [Quickstart](/docs/context-layer/quickstart). The **verified 44-entry runtime**,
the full five-operation service, and a hosted endpoint answer over your real Odoo 19 data and
come with runtime access. The TypeScript client ships in the repo as the contract; it is not on
npm yet, pending a stable endpoint. To run against real data,
[request verified runtime access](/contact).
## Choosing a surface
- Building an **agent or assistant**? Wire the example MCP server now (see the
[Quickstart](/docs/context-layer/quickstart)), or use REST and call `get_context` per question
against the verified runtime.
- Working in **Python**? Validate your own entries today with `ocl_spec`; the runtime client
arrives with runtime access.
- Need **offline or air-gapped**? Use a static context pack or a signed offline cache. See
[Context packs and offline](/docs/context-layer/context-packs).
The format the operations speak is public and specified. See
[The open format](/docs/context-layer/open-format).
### Context packs and offline · https://nanti.ai/docs/context-layer/context-packs
A context pack is the assembler's output for one question: the smallest applicable, verified context, sized for a model. How packs work, why assembly is risk-preserving, and how signed offline caches serve the layer without a live connection.
Applies to: Technical alpha · Odoo 19
## What a context pack is
A **context pack** is what the AI actually reads. For one question, the assembler selects the
applicable entries, joins, filters, and warnings, attaches provenance, and emits a single
request-scoped artifact that conforms to the public context-pack schema.
A pack is deliberately **small**. It is not a dump of the registry; it is the minimum verified
context that answers the question. That keeps token cost down and keeps the model's attention on
what matters.
## Risk-preserving assembly
Selecting "the smallest context" has a hard rule: **critical warnings cannot be dropped to meet a
token budget.** If a question touches a posting operation, a company boundary, or a misleading
field, the warning that goes with it stays in the pack even under pressure. Assembly trims
redundancy, never safety.
Retrieval and assembly over the released kernel are deterministic: the same question against the
same pinned release produces the same pack. That is what makes a pack reproducible and
auditable.
## Static packs
Because a pack is a self-contained artifact, you do not need a live service at answer time. You
can assemble a pack ahead of time and hand it to a model directly, in a prompt, a file, or a
pipeline. This is the simplest way to give an AI verified Odoo meaning with no runtime dependency.
## Signed offline caches
For offline or air-gapped use, the layer can be served from a **signed offline cache**: an
entitlement-scoped, content-hashed cache that a licensed user verifies, installs, and serves
locally. The signature is what lets you trust a cache you received rather than fetched live, and
the content hash is what lets you pin exactly which released meaning it carries.
The offline cache is implemented and tested locally. Because a signed cache carries the verified
registry, it is delivered through verified runtime access, not a public download. The open
standard on [GitHub](https://github.com/Nantiai/ocl-standard) covers the format and the
ten-example demonstration; [request runtime access](/contact) for a signed cache of the verified
kernel.
## When to use which
- **Static pack**: one-shot or batch questions, prompt engineering, no live service wanted.
- **Live operations** (MCP or REST): interactive agents that call `get_context` per question.
See [Consuming the layer](/docs/context-layer/consuming).
- **Signed offline cache**: air-gapped, on-premise, or offline deployments that still need the
released kernel.
### The open format · https://nanti.ai/docs/context-layer/open-format
OCL format v0, an experimental open-format specification at schema version 0.1.0: the entry and context-pack schemas, the validator, the conformance suite, and the reference SDKs. Plus the public standard versus commercial registry boundary that makes it safe to adopt.
Applies to: OCL format v0 · schema 0.1.0
For the context layer to be a standard rather than a product feature, the **format** has to be
open: anyone should be able to read, validate, and produce entries and context packs without
depending on us. That is what OCL format v0 is, and it is live at
[github.com/Nantiai/ocl-standard](https://github.com/Nantiai/ocl-standard) under Apache-2.0.
OCL format v0 is an experimental open-format specification, currently at schema version
`0.1.0`. The contracts are draft and may change until independent implementations exercise them.
It is not frozen, not v1, and not yet an established standard.
## What the format defines
- **`entry.schema.json`**: one semantic fact: its scope, evidence, assertions, confidence, and
provenance. See [Entry anatomy](/docs/context-layer/entry-anatomy).
- **`context-pack.schema.json`**: the request-scoped output an AI consumes.
- **`pack.schema.json`** and source-snapshot schemas: a deterministic collection of entries and
the normalized sources their assertions check against.
- **Benchmark schemas**: cases, suites, and scored three-arm artifacts, so evaluations are
themselves specified. See [Benchmark methodology](/docs/context-layer/benchmark).
## Portable domains
Domains are expressed as JSON so any consumer can read them, not just Odoo. A condition is
`[field, operator, value]`; prefix logical operators are `"&"`, `"|"`, `"!"`; and runtime values
use a structured placeholder rather than an unquoted expression:
```json
[
["move_type", "=", "out_invoice"],
["state", "=", "posted"]
]
```
Syntax validation does not prove a domain compiles on a model. That is what a live `domain_executes`
assertion is for, which is why the format keeps checks and claims together.
## Validator and conformance
The kit includes a **validator** and a **conformance suite**. The conformance suite proves that
an implementation can parse the entry schema, enforce evidence references and trust-state
invariants, and reproduce the content-hashed exemplar set:
```bash
python conformance/v0/run.py
```
The exemplars it reproduces are **candidate format demonstrations**. They are not the commercial
verified registry and carry no Odoo lifetime guarantee.
## Reference SDKs
Small reference SDKs show how to work with the format. The Python package exposes entry
validation (`validate_entry`); the TypeScript client (`OCLClient`) wraps the runtime operations.
See [Consuming the layer](/docs/context-layer/consuming) for their shapes.
## Public standard, commercial registry
This is the model that lets OCL be adopted freely while the verified truth stays a product.
| Public / open | Private / commercial |
| --- | --- |
| The format spec and JSON Schemas | The complete curated verified registry |
| The validator and conformance suite | The candidate registry and verification harness |
| Small Python and TypeScript SDKs | Source fingerprints and release matrices |
| Ten candidate example entries and an example MCP server | The private retrieval index and ranking |
| The conformance suite | Private benchmark cases and tenant overlays |
The line is simple: **the format is open so anyone can build on it; the verified registry, the
harness that proves it, and the tenant system are ours.** Adopting the standard never requires our
registry, and our registry is what the commercial product delivers.
The spec, schemas, validator, conformance suite, ten candidate example entries, an example MCP
server, and the reference SDKs are all public now at
[github.com/Nantiai/ocl-standard](https://github.com/Nantiai/ocl-standard), Apache-2.0. See the
[Quickstart](/docs/context-layer/quickstart). What is not in the repo is the commercial side:
the verified registry, the production assembler, the verification harness, and the tenant
system. A hosted runtime endpoint is not live yet and comes with runtime access.
### Benchmark methodology · https://nanti.ai/docs/context-layer/benchmark
How the context layer is measured: a three-arm evaluation that holds the model, connector, permissions, and tasks constant and changes only whether OCL context is present. The design is public; independent blind review is pending and results are forthcoming.
Applies to: Technical alpha · Odoo 19
The question a benchmark has to answer is commercial, not cosmetic:
> Does the context layer cause materially more correct and safer Odoo outcomes than a strong
> connector plus raw schema, at an acceptable context, latency, and cost?
It is not a prompt beauty contest and not a set of screenshots. This page describes the method.
Live model outputs have been generated. Independent blind human scoring is the final evidence
gate, and it is **pending**. Until that review is complete and passing, we publish no accuracy
percentage, no chart values, and no claim that a gate has passed. What follows is methodology.
## Three arms
Every arm uses the **same** model, connector capabilities, user permissions, tenant fixture, tool
budget, and task wording. Only the context differs.
| Arm | What the model gets |
| --- | --- |
| **A** | Connector only: tool descriptions and returned records, no schema dump, no OCL. |
| **B** | Connector plus the strongest realistic raw schema and concise generic Odoo guidance. This is the primary baseline. |
| **C** | Exactly B, plus an assembled OCL context pack. No extra tools or permissions over B. |
**B versus C is the comparison that matters.** The connector, tools, permissions, and model are
identical across those two arms, so any difference is attributable to the context layer alone and
nothing else. Arm A is a floor; optional diagnostic arms (generic documentation RAG, full
ontology dumping) may run, but claims center on the three stable arms.
## What each case measures
Tasks are scored across levels, because an eloquent wrong answer is still wrong:
1. **Interpretation**: the correct business noun, model, discriminating domain, time field,
state, company and currency considerations.
2. **Query plan**: valid fields, joins, aggregation grain, signs, and filters, without silently
changing the question.
3. **Executed result**: the plan runs and returns the independently computed correct value.
4. **Explanation**: the answer states scope, assumptions, and limitations without inventing
facts.
5. **Write intent**: the right business method, preconditions, permission awareness, and
required approval, avoiding dangerous raw writes.
## Metrics
Primary metrics: exact executed-result correctness, semantic query-plan correctness, critical
unsafe-action rate, and clarification correctness on ambiguous tasks. Secondary metrics include
join and grain correctness, state/date/company/currency correctness, negative-knowledge
adherence, hallucinated field or model rate, context token count, latency, and estimated cost, plus
context precision and recall (were the assembled entries the ones actually needed).
Results are reported as **absolute percentage-point change and raw counts**, never as a lone
relative percentage.
## Ground truth and honesty controls
Ground truth is produced independently of the OCL entry wording: controlled fixtures with known
expected values, Odoo business methods and reports where they are the authority, and independently
reviewed queries, with result digests committed before the OCL entries are evaluated. For
convention-dependent questions such as "revenue," the expected correct behavior may be to ask a
clarifying question, and scoring rewards that rather than forcing one universal definition.
Blind scoring is a review operation, not an LLM self-score. Answers are assigned opaque evaluation
ids, the reviewer sees neither the case id nor which arm produced an answer, and the private map is
restored only for deterministic scoring afterward.
## Before any comparison is published
A claim is only published when the benchmark version and dates are stated, all arms used
equivalent capabilities, task-family and aggregate results are shown with raw counts and run-to-run
variance, failures and regressions are included, model ids and settings are disclosed, token and
cost methodology is stated, and no case was added after seeing only the OCL result. The wording
will say what was measured, never "AI now understands all of Odoo."
When results clear that bar, they will appear here and on the
[Context Layer](/context-layer) page. Until then, this page is the method and nothing more.
### Security and boundaries · https://nanti.ai/docs/context-layer/security-and-boundaries
What the context layer is authoritative for and what it is not. Odoo keeps every permission and execution decision; OCL supplies interpretation and advisory safety context. How company, currency, and access boundaries are handled, and how tenant data is treated.
Applies to: Technical alpha · Odoo 19
The context layer is **interpretation infrastructure**. It is not a connector, not an
authorization layer, and not an execution authority. Getting that boundary right is a security
property, not a detail.
## Odoo stays the authority
Odoo's access control lists, record rules, ORM constraints, and business methods remain
authoritative for what a user may see or do and how an operation executes. The context layer never
bypasses, weakens, or replaces any of them. It supplies meaning and safety context on top of a
system that still enforces its own rules.
`validate_write_intent` can warn that an intended action looks risky and point at the safe
method. It never returns or implies authorization, and it never makes an AI-generated write
automatically safe. Whether the action is permitted, and whether it runs, is Odoo's decision.
## Boundaries change the answer
Several boundaries change what a correct answer even is, and the layer carries them explicitly so
an AI does not mistake "what I can see" for "what is true":
- **Company**: records visible under the active companies are not proof of globally complete
records. Multi-company scope changes counts and totals.
- **Currency**: amounts are only comparable once currency is resolved.
- **Access**: a user's record rules can hide rows; an aggregate over visible rows can silently
answer a narrower question.
These travel as [security notes and gotchas](/docs/context-layer/kinds-of-knowledge) so the
warning reaches the model rather than living only in a developer's head.
## Tenant data
The tenant system is built to be careful with a customer's database:
- **Metadata-first**: it works from structural metadata, with no raw business records collected by
default. Bounded, privacy-safe aggregates can be enabled where useful.
- **Reviewed overlays**: detected customizations are proposed, then approved, corrected, or
ignored by the tenant. Nothing is assumed.
- **Governed memory**: confirmed facts are kept with provenance and scope, and are revocable.
- **Export and deletion audit**, and **tenant isolation**, are implemented and tested.
These are implemented local-alpha foundations. The hosted customer product, the review UI, and
production security controls are still being built. See
[Current limitations](/docs/context-layer/limitations). For the Odoo-side data handling of our
shipping products, see the security hub.
## Scope of released claims
Released entries are scoped to **stock Odoo 19**. Custom modules, localizations, tenant policy,
access rules, company, and currency can all change the correct answer for a specific database.
The layer states the scope it was proven against; it does not claim to have proven your
customizations, which is what the tenant overlay is for.
### Coverage and release status · https://nanti.ai/docs/context-layer/coverage-and-status
What the technical alpha releases today on Odoo 19: a use-case-focused semantic kernel of 44 immutable, verified entries, plus 15 Purchase-to-Accounting candidates not yet released. The four verification anchors, and what is deliberately not claimed.
Applies to: Technical alpha · Odoo 19
This page is the honest inventory: what is released, what is a candidate, and what is not claimed.
## The released kernel
The alpha releases a **use-case-focused semantic kernel** of **44 immutable, verified entries** on
Odoo 19, spanning:
- an **Accounting truth kernel**;
- focused **Sales** and **Inventory** bridges;
- a deep **Purchase** kernel.
These entries are loaded through independently verified, **content-hashed release locks**: the
release is pinned by a hash of its contents, so the meaning you read is fixed and auditable.
44 verified entries is a use-case-focused kernel, not complete Accounting, Sales, Inventory, or
Purchase coverage. The layer states exactly what it proves. It never claims to cover every module
or every question.
## Candidates, not counted
A further **15 Purchase-to-Accounting entries are candidates**. Their behavioral verification
passes across all four anchors, but they are **not released** and are **not** part of the public
44-entry registry. We do not count candidates as released facts.
## The four verification anchors
Verification is re-run against four Odoo 19 anchors, so a release is not proven against a single
moving target:
1. release-day Odoo 19 Community;
2. release-day Odoo 19 Enterprise path;
3. current Odoo 19 Community;
4. current Odoo 19 Enterprise.
## What the system has today
The released kernel sits on real machinery: executable verification, immutable content-hashed
release locks, provenance, deterministic retrieval over released content, and risk-preserving
context assembly. The consumption surfaces (MCP, REST, Python, TypeScript, static context packs,
and signed offline caches) are implemented. See
[Consuming the layer](/docs/context-layer/consuming).
## Version target
**Odoo 19 is the only current stock-version target**, Community and Enterprise. Earlier versions
(17, 18) and any future version are not advertised. Earlier-version compatibility packs would
follow only after the first proof passes.
## What is not claimed
- Not complete coverage of any module.
- Not a finished hosted tenant product (the tenant system is an implemented foundation).
- No published benchmark result yet (see [Benchmark methodology](/docs/context-layer/benchmark)).
- Not proven against your custom modules, localizations, or company and currency setup beyond
stock Odoo 19.
For the full list of what remains experimental, see
[Current limitations](/docs/context-layer/limitations).
### Current limitations · https://nanti.ai/docs/context-layer/limitations
The honest edges of the technical alpha: what is experimental, unreleased, or deliberately not claimed. Read this before building on OCL so expectations match what is actually proven today.
Applies to: Technical alpha · Odoo 19
This is an early technical alpha. It is substantial and honest, which means being clear about its
edges. None of the following is a bug; it is the current state.
## Scope
- **Odoo 19 only.** Community and Enterprise, stock. Odoo 17, 18, and any future version are not
supported or advertised.
- **Use-case-focused kernel.** 44 verified entries is a focused kernel, not complete coverage of
Accounting, Sales, Inventory, or Purchase. See
[Coverage and release status](/docs/context-layer/coverage-and-status).
- **Stock-scoped claims.** Released facts are proven against stock Odoo 19. Custom modules,
localizations, tenant policy, access rules, company, and currency can change the correct answer
for a specific database. Resolving those is the job of the tenant overlay, which is a foundation,
not a finished product.
## The tenant system
- The tenant system (overlay, memory, drift, governed memory, export and deletion audit,
isolation) is an **implemented local-alpha foundation**, not a hosted commercial product. The
hosted workflow, the review UI, production security controls, and real pilots are still ahead.
## Benchmark
- Live model runs exist, but **independent blind human scoring is pending**. Until it is complete
and passing, there is no published accuracy figure, no chart, and no "gate passed" claim. See
[Benchmark methodology](/docs/context-layer/benchmark).
## Distribution
- The **open standard is public** at
[github.com/Nantiai/ocl-standard](https://github.com/Nantiai/ocl-standard) (Apache-2.0): the
format, validator, conformance suite, reference SDKs, ten candidate example entries, and an
example MCP server. See the [Quickstart](/docs/context-layer/quickstart).
- What is **not** public yet: the **verified 44-entry runtime**, a **hosted endpoint**, and the
full five-operation service over real Odoo 19 data. These come with runtime access, granted
directly. The TypeScript client ships as the contract but is not on npm yet.
## What OCL does not promise
OCL is interpretation infrastructure. It does not:
- eliminate hallucinations, or guarantee any AI answer is correct;
- make an AI-generated write automatically safe (write-intent validation is advisory and never
authorizes);
- replace or weaken Odoo permissions, record rules, or business methods;
- self-verify. Machine-testable rules and applicability are executed; meaning that cannot be
machine-tested relies on attributable evidence or authorized review.
## Naming
- "Context layer" and "OCL" are working category labels, not a final product name. Final naming is
deferred to a trademark review. OCL is an independent nanti.ai project and is not affiliated with
or endorsed by Odoo S.A.
## Getting access
Start today with the open standard: clone
[github.com/Nantiai/ocl-standard](https://github.com/Nantiai/ocl-standard) and follow the
[Quickstart](/docs/context-layer/quickstart). For the verified runtime on real Odoo 19 data,
[request verified runtime access](/contact); it is granted directly.