AI coding agents have become genuinely good at Odoo legwork: finding the model behind a screen, reading the module that overrides it, writing a query, drafting a patch. Odoo users on Reddit and elsewhere report customizations done in an hour that used to take days. The same reports carry the same warnings: the agent invents field names, proposes fixes that would work on standard Odoo but not on a customized one, and sounds equally confident either way.
So the interesting question is not whether an agent can help with a production Odoo. It is how to connect one so that its mistakes stay cheap. This is how our public demo does it, and the pattern we set up on customer systems. The pattern works whether you use our service, build your own setup, or just want to know what to ask a vendor.
Fences are properties of the connection, not of the prompt
Every AI setup has a system prompt that says something like "never modify accounting data". That line is useful for quality: it makes the agent behave well most of the time. It is not a control. A language model follows instructions probabilistically, it reads text from tickets, emails and database records that can contain instructions of their own, and it can simply be wrong about its own situation.
We saw the last one on our own demo. After we added a way for visitors to approve new records, part of the demo's prompt still described the connection as read-only, and when someone asked for a correction the model confidently answered that the demo was strictly read-only and could not write anything. The prompt was out of date, and the model repeated it. Nothing unsafe happened, because nothing about safety depended on the prompt, but it is a good illustration of why a sentence in a prompt cannot be the thing standing between an agent and your books.
A fence is something the agent's credentials physically cannot do. The test is simple: if the model were fully compromised and tried its hardest, would the action still fail? If the answer depends on the model choosing to comply, it is a policy, not a fence.
Fence one: a read-only database role
The agent reads your Odoo through a PostgreSQL role that can only run SELECT, granted table by table. It is not the role Odoo itself uses, and it is not a superuser. On top of that:
- Secrets are withheld at the column level. Password hashes, API keys, two-factor secrets, OAuth tokens and mail server credentials are never granted, so
SELECT *on the users table fails andSELECT loginworks. Tables that exist only to hold secrets are not granted at all. - Every session is read-only by default, with a short statement timeout and memory limits, so a runaway query stops on its own.
- The service in front of it accepts one plain
SELECTat a time, runs it in a read-only transaction, and caps the rows and cell sizes inside PostgreSQL before anything reaches the model.
This is where most of the agent's work happens. Diagnosing why a report is wrong, why stock does not match, or why a bank line will not reconcile is almost entirely reading. A read-only role makes all of that safe to hand to an agent at any hour.
Fence two: propose, approve, apply, log
When a change is needed, the agent does not make it. It prepares a proposal: the exact records and values, or the code change on a branch, with a dry run on a copy showing what would happen. The proposal is validated by ordinary code, not by the model: the model and fields must be on an allow list, values must have the right types, referenced records must exist.
Then a person decides. In our public demo, only an approval applies it, and the approval step is where the write credentials live. The agent never holds them. On a customer system, the proposed change and its dry run are posted on the support ticket, and a person at Allodi applies it only after approval.
Two details make the difference between a nice diagram and a reliable system:
- Apply exactly once. This is built for the demo. Each proposal carries an identifier that the write step records in the same transaction as the change. A double click, a retried request or a dropped connection cannot apply it twice. If the result is unknown, it is reported as unknown and never silently retried.
- Log where the data lives. Code changes are git commits with a human author and a ticket reference. Data changes leave a note in the chatter of the record they touched, so anyone looking at that invoice or product in Odoo later can see what changed and why, without opening another system.
What always goes to a person
Some categories are handled by a person regardless of how simple the request looks, because their mistakes are expensive or cannot be taken back:
| Category | What the agent does | What a person does |
|---|---|---|
| Money | Reads invoices, payments, journal entries and reconciliations to diagnose | Posts, cancels, edits and reconciles, and changes prices |
| Tax | Explains how a tax was computed | Changes rates, fiscal positions and anything tied to a filing, on the customer's written instruction |
| Deployments | Writes code on a branch and tests it on a copy | Reviews, merges, upgrades modules and restarts production |
| User access | Reads users, groups and access rules to diagnose | Adds, removes or changes users, groups, access rights and security settings |
| Migrations and restores | Rehearses them on copies | Runs a migration or a restore on production |
| Messages | Drafts | Releases anything sent to a customer, vendor or employee |
| External services | Runs tests with payment, carrier, bank feed and tax connectors in a logging mode | Anything that reaches the real service |
A rollback does not unsend an email
The fence people most often forget is outbound side effects. It is tempting to test a fix inside a database transaction and roll it back afterwards. The rollback undoes the rows. It does not undo the email Odoo already handed to the mail server, the SMS that went out, the label bought from a carrier, the webhook that fired or the document sent to a printer.
So before anything runs against a real database, even a test, every path that leaves Odoo is listed and switched to a mode that logs "would send" instead of sending. Copies used for testing are neutralized first: Odoo 16 and later include an odoo-bin neutralize command that disables outgoing mail servers, scheduled actions, payment providers and similar integrations on a database. After a test we check the outgoing mail queue to confirm nothing tried to leave. Retries never contain a send. This rule exists because we have seen what happens without it.
Where your data goes
Your database stays on your server. The agent inspects it in place through the read-only role; dumps are not uploaded anywhere. It is still worth being precise: the query results the agent reads are sent to the AI model to reason about, and the model runs at the AI provider (for us, Anthropic). Any vendor offering an AI agent over your data should be able to tell you exactly which data leaves, to whom, and under what terms. If the answer is vague, ask again.
Try to break it
We run the same pattern on a public demo: a sample company on Odoo 19 with Odoo's own demo data. You can ask the agent anything about the company, see every SQL query it ran under each answer, and ask it to add records, add a custom field to a form or correct an existing record. It prepares a card showing exactly what would change, and nothing happens until you press Approve. After that you can open the Odoo web client as a view-only visitor and see your change.
The demo adds fences a customer system does not need, because it is open to the public: content checks on everything typed, per-visitor and daily limits, a daily spending cap on the model, a nightly reset, and an Odoo instance that cannot open a network connection to anything outside its own server. Ask it to email a vendor or delete an invoice and watch what it does.
A short checklist for any AI setup on Odoo
- Does the agent's database user have any write privilege at all? It should not.
- Are password hashes, API keys and tokens unreadable to it at the column level?
- Is there a statement timeout and a row cap on its queries?
- Who holds the write credentials, and is it ever the agent?
- Can a change be applied twice by a retry?
- Is every change attributable to a named person, in git or in the record's chatter?
- Is every outbound channel disabled or logged during tests, and is the outbox checked afterwards?
- Which data is sent to the AI model, and under which provider terms?
If each answer is a mechanism rather than an instruction, the agent can be as fast as it likes.
How the same fences work day to day on a customer's system, from ticket to approved change, is on how our Odoo support works.
Sources
- Odoo documentation: neutralized databases
- PostgreSQL documentation: GRANT (including column privileges)
- PostgreSQL documentation: default_transaction_read_only and statement_timeout
- OWASP Top 10 for Large Language Model Applications: prompt injection
- Allodi: how it works (approval flow and fences)
- Allodi: live demo