<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Mahmoud ElZouhery</title>
  <subtitle>Solution Architect and Engineering Manager with 20+ years delivering enterprise software, ERP and cloud across the UK, US, Saudi Arabia and Egypt.</subtitle>
  <link href="https://elzouhery.com/feed.xml" rel="self"/>
  <link href="https://elzouhery.com/"/>
  <updated>2026-08-12T00:00:00Z</updated>
  <id>https://elzouhery.com/</id>
  <author>
    <name>Mahmoud ElZouhery</name>
    <email>elzouhery@hotmail.com</email>
  </author>
  <entry>
    <title>Giving an AI agent write access to your ERP</title>
    <link href="https://elzouhery.com/writing/giving-claude-write-access-to-an-erp/"/>
    <updated>2026-08-12T00:00:00Z</updated>
    <id>https://elzouhery.com/writing/giving-claude-write-access-to-an-erp/</id>
    <summary>Agents that read your Odoo data are useful. Agents that act on it need a different kind of design discipline.</summary>
    <content type="html">&lt;p&gt;Connecting a language model to an ERP so it can &lt;em&gt;answer questions&lt;/em&gt; is a weekend project. Connecting one so it can &lt;em&gt;create the invoice&lt;/em&gt; is a different exercise entirely, and most of the difficulty has nothing to do with the model.&lt;/p&gt;
&lt;p&gt;I&#39;ve spent this year building MCP plugins and agentic workflows over Odoo — HR, sales, invoicing, timesheets — and these are the design rules I keep coming back to.&lt;/p&gt;
&lt;h2&gt;Read and write are different products&lt;/h2&gt;
&lt;p&gt;The temptation is to expose one set of tools and let the agent figure out intent. Don&#39;t. Read operations and write operations have different failure modes, different blast radii, and should be designed as if they were built by different teams.&lt;/p&gt;
&lt;p&gt;A wrong read is a wrong answer, and the human catches it. A wrong write is a record in a system of truth that somebody&#39;s month-end depends on. The bar isn&#39;t &amp;quot;usually correct.&amp;quot;&lt;/p&gt;
&lt;h2&gt;Design the tool, not the prompt&lt;/h2&gt;
&lt;p&gt;Most agent failures I&#39;ve debugged were tool design failures wearing a prompt costume.&lt;/p&gt;
&lt;p&gt;If your &lt;code&gt;create_invoice&lt;/code&gt; tool takes fifteen optional parameters and silently defaults the rest, the model will produce plausible invoices with wrong defaults, and it will do so confidently. The fix isn&#39;t a longer system prompt telling it to be careful. The fix is a tool that refuses to run without the fields that matter, and returns an error the model can actually act on.&lt;/p&gt;
&lt;p&gt;Practical version of this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Make illegal states unrepresentable at the tool boundary.&lt;/strong&gt; If a sale order can&#39;t be confirmed without a customer, the tool should reject it, not the prompt.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Return errors that describe the remedy&lt;/strong&gt;, not just the failure. &lt;code&gt;Missing partner_id — call list_contacts to resolve the customer name first&lt;/code&gt; gets recovered from. &lt;code&gt;400 Bad Request&lt;/code&gt; doesn&#39;t.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep tools narrow.&lt;/strong&gt; Ten specific tools beat one general one with a mode parameter, every time.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Separate proposing from committing&lt;/h2&gt;
&lt;p&gt;The pattern that has worked best: the agent proposes a fully-formed action, a human commits it, and the commit path is a different code path from the proposal path.&lt;/p&gt;
&lt;p&gt;This sounds like it defeats the purpose. It doesn&#39;t. The expensive part of back-office work is almost never the click — it&#39;s assembling the context, finding the right records, and getting the fields right. An agent that does 95% of that and stops is delivering nearly all of the value at a fraction of the risk.&lt;/p&gt;
&lt;p&gt;Over time you move specific, well-understood operations from &lt;em&gt;propose&lt;/em&gt; to &lt;em&gt;auto-commit&lt;/em&gt;, one at a time, based on observed accuracy. What you don&#39;t do is start there.&lt;/p&gt;
&lt;h2&gt;Idempotency is not optional&lt;/h2&gt;
&lt;p&gt;Agents retry. Networks fail mid-call. A model that doesn&#39;t get a clean response will try again, because that&#39;s the sensible thing to do.&lt;/p&gt;
&lt;p&gt;If your write tools aren&#39;t idempotent, you will get duplicate records, and you&#39;ll find out during a month-end close. Every write operation needs either a natural idempotency key or an explicit one the agent must pass. This is the single most common gap I see in agent integrations that worked fine in testing.&lt;/p&gt;
&lt;h2&gt;Log the reasoning, not just the result&lt;/h2&gt;
&lt;p&gt;When an agent creates a record, log what it was asked, which tools it called, and in what order. Not for observability theatre — for the conversation that happens three weeks later when someone asks why this invoice looks odd.&lt;/p&gt;
&lt;p&gt;Without that trail, an AI-driven process is unauditable, and unauditable processes don&#39;t survive their first real mistake.&lt;/p&gt;
&lt;h2&gt;The actual lesson&lt;/h2&gt;
&lt;p&gt;None of this is about models getting better. Every rule above is the same engineering discipline you&#39;d apply to any integration that writes to a system of record — constrained interfaces, explicit contracts, idempotency, audit trails.&lt;/p&gt;
&lt;p&gt;The novelty is that the caller is non-deterministic and unusually good at producing plausible-looking wrong input. That doesn&#39;t call for new principles. It calls for the existing ones, applied without the shortcuts we&#39;ve been getting away with when the caller was another service we controlled.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>The deployment playbook that ended our environment drift</title>
    <link href="https://elzouhery.com/writing/deployment-playbook-environment-drift/"/>
    <updated>2026-07-09T00:00:00Z</updated>
    <id>https://elzouhery.com/writing/deployment-playbook-environment-drift/</id>
    <summary>The most boring document I&#39;ve ever written, and the one that saved the most time.</summary>
    <content type="html">&lt;p&gt;When I took ownership of a company&#39;s full Azure footprint — dev, staging and production across several departments — the problem wasn&#39;t cost or performance. It was that no two environments were the same, and nobody could tell you why.&lt;/p&gt;
&lt;p&gt;Staging had a configuration production didn&#39;t. One team&#39;s dev environment had drifted so far it was effectively a fourth environment. Every release involved a small archaeology project.&lt;/p&gt;
&lt;p&gt;The fix wasn&#39;t a tool. It was a document.&lt;/p&gt;
&lt;h2&gt;Drift is a decision-making problem&lt;/h2&gt;
&lt;p&gt;Environment drift is usually described as a technical failure — someone clicked something in the portal. That&#39;s the mechanism, not the cause.&lt;/p&gt;
&lt;p&gt;The cause is that when an engineer hits a blocker at 6pm before a release, the portal is the fastest path to unblocked, and nothing in the process makes the slower path more attractive. Drift is what a rational person does under deadline pressure when the correct route is undefined.&lt;/p&gt;
&lt;p&gt;So the question isn&#39;t &amp;quot;how do we stop people clicking in the portal.&amp;quot; It&#39;s &amp;quot;what makes the repeatable path the fastest path.&amp;quot;&lt;/p&gt;
&lt;h2&gt;What went into the playbook&lt;/h2&gt;
&lt;p&gt;Not a wiki with everything. One document, short enough that people actually read it, covering four things:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Provisioning.&lt;/strong&gt; How an environment comes into existence, defined as code, with the exact command. Not &amp;quot;use Terraform&amp;quot; — the command, the repo, the variables file. If creating an environment requires a decision, the playbook makes the decision.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Release gates.&lt;/strong&gt; What must be true before a build moves to the next environment, expressed as checks rather than as approvals. An approval is a person&#39;s judgement under time pressure; a check is a check. Approvals still existed, but they stopped being the mechanism of quality.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rollback.&lt;/strong&gt; Written before it was needed, tested when it wasn&#39;t. The single biggest driver of portal-clicking is a failed release with no defined way back. Once rollback was a known, rehearsed one-liner, the panic edits mostly disappeared.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ownership.&lt;/strong&gt; Who owns each environment, and specifically who to wake up. Ambiguous ownership means everyone&#39;s fix is somebody else&#39;s surprise.&lt;/p&gt;
&lt;h2&gt;Why it was adopted&lt;/h2&gt;
&lt;p&gt;I&#39;ve written process documents that nobody read. This one got picked up across every team, and I think the reason is that it was shorter than the alternative.&lt;/p&gt;
&lt;p&gt;The playbook wasn&#39;t competing with a better process — it was competing with improvisation. If following it takes fifteen minutes and improvising takes forty-five plus an incident, adoption isn&#39;t a cultural problem. Make the correct path measurably faster and the argument makes itself.&lt;/p&gt;
&lt;p&gt;The parts I got wrong were the parts where I asked people to do something slower &amp;quot;because it&#39;s correct.&amp;quot; Those got skipped, quietly, exactly as they deserved to be.&lt;/p&gt;
&lt;h2&gt;The unglamorous conclusion&lt;/h2&gt;
&lt;p&gt;Most of what I&#39;d call good architecture at the infrastructure level is removing reasons for things to go wrong, rather than adding capability. Nobody puts &amp;quot;wrote a deployment playbook&amp;quot; on a conference slide. It still returned more hours than any framework decision I made that year.&lt;/p&gt;
</content>
  </entry>
  <entry>
    <title>Taking over an account that&#39;s already on warning</title>
    <link href="https://elzouhery.com/writing/taking-over-an-account-on-warning/"/>
    <updated>2026-06-18T00:00:00Z</updated>
    <id>https://elzouhery.com/writing/taking-over-an-account-on-warning/</id>
    <summary>What I did in the first two weeks of a rescue engagement, in order — including the parts that were uncomfortable.</summary>
    <content type="html">&lt;p&gt;There&#39;s a particular kind of phone call in this job. A flagship account is in trouble, the customer has put something in writing, and someone needs to take it over. In my case the stack was unfamiliar, the team was demoralised, and the customer had stopped assuming good faith.&lt;/p&gt;
&lt;p&gt;That engagement ended with documented positive feedback. Here&#39;s the order I did things in, because the order mattered more than any individual decision.&lt;/p&gt;
&lt;h2&gt;Week one: stop talking about the plan&lt;/h2&gt;
&lt;p&gt;The instinct when you inherit a troubled account is to produce a recovery plan. Resist it for a few days. A plan written before you understand the failure is a second thing the customer will watch you miss.&lt;/p&gt;
&lt;p&gt;What I did instead:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Read every ticket, not the summary of every ticket.&lt;/strong&gt; Summaries are written by people protecting themselves. The raw tickets told me the real story: the same three integration points failing repeatedly, and each fix being applied at the symptom.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Talked to the engineers alone, before the managers.&lt;/strong&gt; Not to gather evidence — to find out what they&#39;d already flagged and been overruled on. In almost every troubled project I&#39;ve walked into, someone junior called it correctly six months earlier and got talked out of it. Finding that person is the fastest route to the actual problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Asked the customer what &amp;quot;fixed&amp;quot; looks like.&lt;/strong&gt; Concretely. Not &amp;quot;improved quality&amp;quot; — which release, which behaviour, observed by whom. A formal warning is usually a communication failure wearing a technical costume. The customer often can&#39;t tell you what would satisfy them until you make them say it out loud.&lt;/p&gt;
&lt;h2&gt;Week two: fix one visible thing, completely&lt;/h2&gt;
&lt;p&gt;Not the biggest thing. The most &lt;em&gt;visible&lt;/em&gt; thing that can be genuinely finished.&lt;/p&gt;
&lt;p&gt;Credibility on a warned account is spent, and you rebuild it with completed items, not progress percentages. One thing shipped, verified, and closed does more for the relationship than a Gantt chart showing eleven things at 60%.&lt;/p&gt;
&lt;p&gt;This is also where you find out whether your team can actually finish anything, which is diagnostic information you need early.&lt;/p&gt;
&lt;h2&gt;The uncomfortable parts&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I stopped some work that people were emotionally invested in.&lt;/strong&gt; Two workstreams were consuming capacity and producing nothing the customer had asked for. Ending them cost me goodwill inside the team that took months to earn back.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I over-communicated to the point of it feeling excessive.&lt;/strong&gt; Weekly written status, every week, whether or not there was good news — especially when there wasn&#39;t. A warned customer reads silence as concealment. The week I had nothing good to report was the most important status update I sent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I didn&#39;t defend the past.&lt;/strong&gt; The temptation to explain why the previous decisions were reasonable given the constraints is strong and completely counterproductive. The customer doesn&#39;t need the history litigated. They need to see the next thing land.&lt;/p&gt;
&lt;h2&gt;What I&#39;d tell someone walking into one&lt;/h2&gt;
&lt;p&gt;Ramping up on an unfamiliar stack while the account is on fire is survivable — the technology is rarely the bottleneck. The bottleneck is that everyone involved has stopped trusting the process, and trust is rebuilt in small completed increments, publicly, on a schedule.&lt;/p&gt;
&lt;p&gt;The stack you can learn in a fortnight. The trust takes the whole engagement.&lt;/p&gt;
</content>
  </entry>
</feed>
