# 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.