Solutions Architect · Senior Python & AI Engineer · AI Audits · Helping teams fix what they shipped too fast

Netherlands
Joined July 2021
I'm Nuno Bispo, a Senior Software Engineer and Solutions Architect with 15+ years building Python systems, AI integrations, and production backends. I write for Real Python and Unstract. I build and ship hardware. I've seen what breaks in production and what doesn't. I take on a small number of advisory engagements with teams that are serious about getting their Python and AI stack right.
4
317
When a business rule lives in fourteen places, it will be wrong in at least one. Take something as small as how your system treats an email address, a price, or a phone number. One service trims whitespace and lowercases. Another doesnt. A third rejects plus signs. Nobody chose these differences. They built up as different people copied slightly different checks into different files. Then a customer cant log in, or an invoice is off by a cent, and someone spends a day finding out which version of the rule was running. Pydantic's annotated types let you define a rule once and reuse it everywhere. You declare what a valid email or a valid money amount looks like in a single place, and every model that uses that type gets the same behavior. When the rule changes, say a new rounding policy or a stricter ID format, its one edit and one review. The change reaches every service that depends on it. For a team thats growing, that matters more than it sounds. New engineers don' t have to learn which version of the rule is the real one, and your definition of valid data has one owner. Practical Pydantic covers how to build these reusable types into Python systems, along with the validation patterns around them. 👉 leanpub.com/practical-pydant… #EngineeringLeadership #TechStrategy #SoftwareArchitecture #ProductEngineering #Python
16
You ship on a Friday afternoon. Staging was green, and the checkout dashboard looks fine for an hour. Then the incidents channel lights up: a slice of customers can’t check out, error rates are ugly in one region, and the person who knows the rollback path is already on a train. By Sunday you’ve reverted, filled in the postmortem template, and promised yourself this won’t happen again. At Monday standup, someone says it out loud: “We’ll be more careful next time”. That sounds responsible. It still isn’t a strategy. Care doesn’t scale, and review culture, useful as it is, doesn’t change the shape of the next bad deploy: one binary, all of production, and a hope that you’ll notice fast enough. Canary is one way to shrink that blast radius. For a mid-size product team, the job after the burned weekend is deciding whether it’s your next move (yes, no, or not yet). If you’ve already Googled “canary vs blue green,” you’re in the right place. developer-service.blog/canar…
18
One of the quieter costs of shipping an AI feature fast is that the vendor relationships get decided by default. The team picked a model provider, an embedding service, and a vector store because they were the quickest to get working, and nobody has revisited whether those choices still make sense now that real usage, real costs, and real reliability data exist. The decision was never wrong, exactly. It just was never made on purpose. That matters because switching costs grow quietly. Prompts get tuned to one models quirks, data formats get shaped around one providers API, and an outage or price change that would have been a minor inconvenience at launch becomes a serious conversation six months later. Teams that look at this early still have options. Teams that look late mostly have constraints. If you havent reviewed how tightly your AI feature is tied to a single provider, its worth knowing before the providers next incident or price change forces the question => developer-service.blog/work-… #AI #CTO #TechLeadership #SoftwareEngineering #Python
2
43
Theres a difference between a team that has a process and a team that has a habit. A process is written down, scheduled, and usually followed until a deadline gets tight. A habit survives the deadline. The teams I'd trust with a production AI system are rarely the ones with the most elaborate review checklists. Theyre the ones where someone asks did we check what happens if this fails? Without being prompted, every time, because it has become how they think. That kind of habit can't be installed with a policy. It comes from people who have been burned once and remember it, and from leaders who make asking the awkward question feel normal instead of like an obstacle to shipping. When that culture exists, the technical practices tend to follow: logging, validation, fallbacks, review. When it doesnt, the practices get adopted enthusiastically after an incident and quietly dropped six months later. If you want to know which kind of team you have: developer-service.blog/work-… #SoftwareEngineering #AI #TechLeadership #CTO #Python
16
A good runbook entry for an AI agent is surprisingly short. It names the symptom, the dashboard to check first, the most likely cause, and the one command that rolls it back. Thats it. No theory, no it depends. Heres what that looks like in practice. Symptom: the triage agent starts labeling most issues as high priority. First check: the output distribution chart, compared against last weeks baseline. Likely cause: a model or prompt change that shifted the distribution without breaking the schema. Fix: roll back the canary and rerun the eval suite against the previous version. Writing that entry is easy only if the system was built to support it. You need typed outputs to compare, evals to rerun, and a rollout setup that makes rollback a single step. Thats the idea behind Production AI Agents with PydanticAI: you build a GitHub issue triage assistant chapter by chapter, and each piece of engineering exists so that 3am has a short, boring answer. Free sample chapters: leanpub.com/production-ai-ag… #PydanticAI #LLMOps #AIEngineering #PythonEngineering
1
1
59
AI can write your Python now. It can't understand your scope bugs for you. Copilot, Cursor, ChatGPT: they all generate code that looks right. And most of the time, it is. But when a generated function quietly shadows a variable, or a closure captures the wrong loop value, the code still runs. Tests pass. Review looks clean. The bug ships. The more code we generate, the more it matters that we can read it the way Python does. Not skim it. Reason about it. Spot the line where a name resolves somewhere it shouldn't. That's a fundamentals skill, and AI tools make it more valuable, not less. I put together a free Python Scope Guide covering the LEGB rule, closures, late binding, and global vs nonlocal: so you can review AI-written (and human-written) code with confidence. 🎁 Free: python-variable-scope.develo… #Python #AI #SoftwareEngineering #CodeReview
1
105
Prompt injection is usually discussed as if it were an exotic attack, but in most Python integrations it starts with something mundane: user-supplied or third-party text getting concatenated straight into the prompt with the same authority as your own instructions. A support ticket, a scraped web page, a PDF that went through OCR. Once that text is in the prompt, the model has no reliable way to tell your instructions from instructions someone else embedded in the data. You cant fully solve this at the prompt level, so the defence has to live in your code. Keep untrusted content clearly delimited and labelled as data, give the model the least access it needs to do the job, and never let its output trigger a sensitive action, like sending an email, running a query, or calling an internal API, without checks you control. If a manipulated model response can reach something that matters, the design is the problem, not the prompt. A useful exercise is to list every place untrusted text enters your LLM calls and every action the output can cause, then ask what the worst case is if the model follows the wrong instructions. Most teams that do this find at least one path they didnt know existed. Let me help you validate your design => developer-service.blog/work-… #Python #AI #Security #LLM #SoftwareEngineering
1
3
81
An AI agent that acts on bad data costs more than one that returns a bad answer. A chatbot that gets a field wrong shows a user a wrong sentence. An agent that gets it wrong books the meeting, sends the email, updates the CRM record, or calls the refund API. The error leaves the conversation and lands in a system of record. Most of these failures start at one of two points. A tool returns a response shape the agent didnt expect, such as a null price or a date in a different format. Or the model produces a tool call with a missing argument or a string where a number belonged. The agent doesnt stop to ask. It continues with whatever it has and builds the next step on top of it. By the time someone notices, the cost is no longer the original bug. Its the cleanup: finding every record the agent touched, reversing the ones that can be reversed, and explaining the rest to a customer. Validating tool inputs and outputs is the cheapest control you have. Define what a valid call and a valid result look like, reject anything else before the agent acts on it, and log the rejection with the exact field that failed. That turns a silent corruption into a retry or an escalation to a human. Practical Pydantic shows how to build that validation layer for Python systems, including patterns for structured LLM output and agent tooling. 👉 leanpub.com/practical-pydant… #EngineeringLeadership #AIEngineering #AIAgents #TechStrategy #ProductEngineering
1
2
68
The questionnaire asks whether you scan for secrets. You do. GitHub secret scanning is on. The last report shows no findings on main. On paper, the control exists. That report is almost certainly looking at the wrong place. Secret scanning that only inspects the current tree, or only the tip of the default branch, will miss credentials that were committed, then “fixed” by deleting the file or rewriting the line. Git still has the blob: the stored file content that remains in the object database even after you delete the path from main. Anyone with a clone can git show an old commit and recover the value. If the token was never revoked at the issuer, it still works. That is the audit gap: a green checkbox on HEAD is weak evidence of remediation. “We removed it from the repo” does not mean “it cannot be used.” This article goes trough a walk history with a scout, present hits without re-leaking them, rotate anything that ever appeared, then put prevention in front of the next push so the next audit is not a fire drill. Check history, not just the current tree. developer-service.blog/you-d… #Secrets #Python #GitHub
1
47
A useful test for any AI feature is whether the team could hand it to a new senior engineer tomorrow and have them productive within a week. Not because anyone is leaving, but because the answer reveals how much of the system lives in documentation, tests, and clear structure versus in the heads of the two or three people who built it. Most teams discover the answer only when someone goes on leave or changes jobs. The risk isnt the departure itself. Its that systems which depend on tribal knowledge tend to accumulate decisions nobody wrote down: why a prompt is phrased a certain way, which edge case a validation rule exists for, what the fallback is when the provider is slow. That context is expensive to rebuild and easy to lose, and the cost shows up as slow onboarding, cautious changes, and incidents that take longer to diagnose than they should. If youre not confident a new engineer could pick up your AI layer cold, a review that surfaces and documents whats actually there is a good first step: developer-service.blog/work-… #AI #CTO #TechLeadership #SoftwareEngineering #Python
24
Theres a particular kind of technical debt that never shows up on a backlog, because nobody ever decided to take it on. It accumulates in the gaps between what the team assumed and what the system actually does. Someone assumed the retries were handled somewhere else. Someone assumed the input was validated upstream. Someone assumed the old key had been rotated. Each assumption was reasonable on its own, and none of them was ever checked. That kind of debt is harder to pay down than the visible kind, because you cant schedule work against something you dont know exists. The only way to find it is to go looking, deliberately, with the specific intent of testing what everyone believes to be true instead of what everyone remembers deciding. The most useful question in any engineering review is also the least comfortable one: what are we assuming here that weve never actually verified? Sometimes the best way is an external audit => developer-service.blog/work-… #SoftwareEngineering #AI #TechLeadership #CTO #Python
21
Picture a GitHub issue landing in a busy open-source repo at 2am. A user reports a bug, but the title is vague and the description rambles. Your triage agent reads it, assigns a label, suggests a priority, and drafts a first response for the maintainers. Now picture the three ways that goes wrong. The model returns a label that doesnt exist in your taxonomy. A new model version quietly starts rating everything as high priority. Or a malformed issue sends the agent into a retry loop that costs ten times the usual amount. In a prototype, you find out when someone complains. In a production system, a typed contract rejects the bad label, an eval catches the priority drift before it merges, and a cost alert fires before the loop gets expensive. That scenario is the thread running through Production AI Agents with PydanticAI. You build the triage assistant chapter by chapter and add each safeguard as the failure it prevents becomes real: typed output contracts, eval suites in CI, cost and latency observability, canary rollouts, and a runbook for when something still slips through. Free sample chapters: leanpub.com/production-ai-ag… #PydanticAI #LLMOps #AIEngineering #PythonEngineering
2
60
Python won't tell you when you're reading the wrong variable. It just reads it. Confidently. Silently. Wrong. No exception. No stack trace. Just output that looks fine until it very much isn't. Scope is the #1 source of these bugs: and one of the least explicitly taught parts of Python. I put together a free guide that fixes that: the LEGB rule, closures, late binding, global vs nonlocal: explained clearly, with real examples. python-variable-scope.develo… #Python #SoftwareEngineering #PythonDevelopment
21
Scanning your own repositories for leaked secrets sounds like something you'd only need once, right after a scare. In practice it's one of those checks that's cheap enough to run continuously and valuable enough that skipping it is hard to justify once you've seen what turns up. API keys committed years ago and never rotated, tokens pasted into a config file during a late-night debug session and forgotten, credentials sitting in commit history even after the file itself was deleted. A basic version of this is easy to build in Python: regex patterns for common key formats, a scan across the full git history rather than just the current state of the repo, and a report of what was found and where. Tools like truffleHog and gitleaks already do this well, but understanding the approach well enough to customise detection for your own stack's specific secret formats is worth the time, especially for internal tools using less common providers that generic scanners don't recognise. The uncomfortable part isn't building the scanner. It's running it on a codebase that's been live for a while and actually looking at what comes back => developer-service.blog/work-… #Python #Security #SoftwareEngineering #DevOps #AI
60
Open base.html in a project you already have running. Somewhere near the top there is a greeting, {{ user.username }}, and somewhere else a loop over messages. Search the views. The about page never passes user. The product page never passes messages. Both templates still render the logged-in person and the message from the last form. Django ran a few small functions while it was building the template context, and each function handed back a dictionary. That is a context processor. You have been using them since the first template that mentioned user. developer-service.blog/djang… #Django #ContextProcessor #Python #Middleware
16
A system that can't explain its own failures is a system you're debugging blind. When something breaks in production, the first question isn't "what happened". It's "can I even tell". Some systems answer immediately: a specific field, a specific value, a specific reason. Others answer with a stack trace three layers removed from the actual cause, or worse, a wrong result with no error at all. That gap isn't about tooling maturity. It's about whether the system was built to know what "correct" looks like in the first place. A system with no explicit contract for its data has nothing to compare against when something goes wrong: so it can't tell you what went wrong. It can only tell you that something did. This shows up as a tax on every incident. Engineers spend time reconstructing context that the system should have had from the start: what was the input, what should it have been, where did the two diverge. None of that is hard to capture. It just has to be designed in, not reconstructed after the fact. Teams that build explicit data contracts get that context for free, every time. The system already knows what correct looks like. It just has to say so. Practical Pydantic covers how to build Python systems that can explain their own failures: so your team spends less time reconstructing context and more time fixing the actual problem. 👉 leanpub.com/practical-pydant… #EngineeringLeadership #TechStrategy #SoftwareArchitecture #ProductEngineering #SystemDesign
16
A habit worth building into any team shipping AI features is a regular, scheduled moment to ask what would we find if we looked closely right now rather than waiting for a reason to look. Most reviews happen reactively, triggered by an incident or a complaint, which means the team is always learning about problems at the worst possible time, after it already cost something. Scheduling the review before theres a reason to changes the dynamic entirely. Its cheaper, calmer, and it catches the kind of drift that builds up slowly and never triggers an obvious alarm: a prompt thats quietly stopped matching reality, a dependency thats fallen behind, a security assumption that was true at launch and isnt anymore. If your last look at your AI stack was the week you shipped it, thats probably overdue. Work with Me → developer-service.blog/work-… #AI #CTO #TechLeadership #SoftwareEngineering #Python
20
Something that doesnt get said enough is that most security problems in production codebases arent caused by ignorance. Theyre caused by a reasonable decision made under time pressure that nobody ever circled back to. The secret got hardcoded because there wasnt time to set up a proper vault that week. The exception got swallowed because the error was annoying during a demo. These werent careless choices. They were temporary ones that quietly became permanent. That distinction matters because it changes how you fix the problem. You dont fix a temporary decision that calcified by lecturing the person who made it: you fix it by building a regular habit of circling back, before the gap between what was intended and whats actually running gets wide enough to be dangerous. The codebases that stay healthy arent the ones written by people who never cut corners. There the ones maintained by people who go back and check which corners got cut => developer-service.blog/work-… #SoftwareEngineering #AI #TechLeadership #CTO #Python
24
Two years ago I would have told you the hard part of building an AI agent was getting the prompt right. I dont believe that anymore. The hard part is everything that happens after the prompt works: the moment someone sends input you didnt anticipate, the moment a dependency changes underneath you, the moment its been running quietly wrong for three days and nobody noticed because nothing was watching. Prompting got easier. Operating the thing you built didnt. That shift in what I believe the hard part actually is became Production AI Agents with PydanticAI. You build one real system across the book,a GitHub issue triage assistant, with typed output contracts that fail loudly on drift, eval suites wired into CI, cost and latency observability, canary rollouts, and a runbook that works without you in the room. Free sample chapters: leanpub.com/production-ai-ag… #PydanticAI #LLMOps #AIEngineering #PythonEngineering #Python
3
7
93
How much time have you lost to a bug that turned out to be one misunderstood line of scope? For me, it's easily been days: spread across years, in small 30-minute chunks I never bothered to add up. A wrong value here. An unexpected UnboundLocalError there. Each one solvable in minutes once you know what's happening, but murder to diagnose blind. The frustrating part is that Python's scoping rules are completely consistent. There's no randomness, no edge case that breaks the model. The LEGB rule always applies. Closures always capture variables, not values. The behavior is 100% predictable: if you know the rules going in. Most developers don't, because nobody sits down and teaches it properly. It's assumed knowledge that was never actually taught. I wrote a free guide to fix that: LEGB, closures, late binding, global and nonlocal, all explained clearly with real examples. 🎁 Free: python-variable-scope.develo… #Python #SoftwareEngineering #PythonDevelopment #Debugging
1
24