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.
Claude Code, Cursor or Codex can, at this point in time build a very accurate clone for most applications, but that doesn't mean that it is ready for you to put that URL on the public internet, and that gap is why I wrote this down.
https://t.co/2bS8N0Lqa0
#AI#Python
Most AI features get their first real security review after something goes wrong, usually when a customer asks a question in a procurement questionnaire that nobody on the team can answer confidently.
Where does user data go when it's sent to the model? Is it retained by the provider? Who on the team can see prompts and responses in the logs? These are reasonable questions, and the awkward part is that the honest answer is often "we'd have to check".
That moment is expensive because it usually arrives attached to a deal or a renewal, which means the review happens under pressure, on someone else's timeline.
Teams that have already mapped their data flows and access controls answer in an afternoon. Teams that haven't can lose weeks, and sometimes the contract.
If you couldn't answer those questions today without a scramble, it's better to find the gaps on your own schedule than a customer's.
Let me help you get that clarity => https://t.co/aC8s4f4jCF
#AI #CTO #TechLeadership #SoftwareEngineering #Python
There's a moment in most AI projects when someone says lets just get it working first, and well make it robust afterwards. It sounds sensible, and sometimes it is.
The trouble is that afterwards almost never arrives on its own. The feature starts earning attention, the roadmap fills up, and the robust version quietly becomes a project nobody has the time or the mandate to start.
What I've noticed is that the teams who do get to robust aren't the ones who found more time. They're the ones who decided, early and explicitly, which two or three things would never be deferred: secrets handled properly, failures visible, outputs validated.
Everything else could be rough. Those few things couldn't. Its a much smaller promise than well make it production ready later, which is exactly why it tends to get kept.
#SoftwareEngineering #AI #TechLeadership #CTO #Python
🚨 PHP2Shell: if you run PHP anywhere, patch today.
On Oct 21, PHP shipped emergency releases for two critical flaws (CVE-2026-109618 and CVE-2026-109619). Unauthenticated remote code execution, rated CVSS 9.2 by the PHP team.
The scope is wide:
→ PHP-FPM, Apache mod_php, php-cgi, FrankenPHP and LiteSpeed are all affected
→ Nearly every version back to 5.4 is exposed to at least one of the issues
→ WordPress, Drupal, Magento, Joomla and Laravel sites are exposed if the PHP underneath is vulnerable
Fixed in: 8.5.12, 8.4.27, 8.3.36 and 8.2.35. Minimal backports exist for 7.4, 8.0 and 8.1, or you can use your distro or LTS vendor’s package.
Updating is the only complete fix. The published settings reduce exposure to specific attack paths, but the PHP team is clear that they don’t make an install safe.
One more thing to check after patching: the patched runtime can kill a worker immediately at its hard memory limit, bypassing normal shutdown handlers. Test your app under memory pressure.
“We’re a Python/AI shop, this doesn’t affect us.”
Are you sure? Count the marketing WordPress site, the old admin panel, the vendor container nobody has touched in two years.
The real lesson from PHP2Shell isn’t about PHP. It’s that most teams can’t answer “what are we actually running in production?” in under an hour.
That’s exactly what I help with. I work with CTOs and engineering leads at AI/Python teams who ship fast and want to be sure it’s secure, observable and maintainable:
🔹 Architecture Call: a focused session on your biggest risk
🔹 AI Code Health Check: a fast review of the code your team (and your AI) is shipping
🔹 Production Readiness Audit: a full inventory of what runs and where it can break
🔹 Rescue & Stabilise Retainer: for when something is already on fire
If this week made you wonder what else is running in your stack, let’s talk :
👉 https://t.co/aC8s4f4jCF
#PHP #CyberSecurity #DevSecOps #Python #SoftwareArchitecture
A one-line prompt tweak can change the behavior of an agent more than a thousand-line refactor.
But in most codebases, the prompt change gets a quick glance and a thumbs up, while the refactor gets a full review and a test run.
That mismatch is where a lot of agent incidents start. Someone adjusts the wording to fix one annoying edge case, it looks harmless in the diff, and three days later the output distribution has shifted in ways nobody can trace back to a specific change.
The fix is to treat prompts, models, and output schemas as versioned code that has to pass the same gates as everything else: a typed contract the output must satisfy, an eval suite that runs on the pull request, and a canary stage before the change reaches everyone.
Thats the approach in Production AI Agents with PydanticAI, where you build a GitHub issue triage assistant chapter by chapter and wire each of those gates into the repo as you go.
Free sample chapters: https://t.co/x4j4VqtzBc
#PydanticAI #LLMOps #AIEngineering #PythonEngineering
If I could pin one mental model above every Python developer's desk, it would be this:
Python resolves a name by searching four places, in order, and stopping at the first match.
1. Local — inside the current function
2. Enclosing — any outer functions wrapping it
3. Global — the module level
4. Built-in — names like len, print, range
That's LEGB. Four layers, one direction, zero exceptions.
Almost every confusing scope bug I've seen comes down to a developer assuming Python searched somewhere it didn't, or stopped somewhere they didn't expect.
Shadowed names. Closures capturing a variable instead of a value. UnboundLocalError from a single assignment lower in the function.
The model is simple. Applying it under pressure, in real code, is the skill.
That's why I wrote a free Python Scope Guide: LEGB in depth, closures, late binding, and global vs nonlocal, with practical examples you can run yourself.
🎁 Free: https://t.co/TQGYpMkxnE
#Python #PythonTips #SoftwareEngineering #LearnPython
One habit that makes Python AI integrations much easier to live with is keeping a small set of real test cases that you run against every prompt or model change.
Not a full evaluation framework, just fifteen or twenty representative inputs with the outputs you’d consider acceptable, including a few awkward edge cases you’ve already been bitten by.
Without that, every change is judged by eyeballing two or three examples, and regressions only show up when a user finds them.
The setup is simple. Store the cases in a file, run them through your actual pipeline, and compare the results against expectations using properties rather than exact string matches: does the output parse, does it contain the required fields, is the classification one of the allowed values, is the length within bounds.
Add a case every time something goes wrong in production, and the suite becomes a record of everything your system has already learned the hard way.
It takes an afternoon to build and changes how confident you can be in a prompt tweak or a model upgrade, which for most teams happens far more often than they planned for.
Need help setting it up? Then https://t.co/aC8s4f4jCF
#Python #AI #Testing #LLM #SoftwareEngineering
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.
👉 https://t.co/2lCwqFxyy4
#EngineeringLeadership #TechStrategy #SoftwareArchitecture #ProductEngineering #Python
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.
https://t.co/tjR1EQCbCK
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 => https://t.co/aC8s4f4jCF
#AI #CTO #TechLeadership #SoftwareEngineering #Python
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: https://t.co/aC8s4f4jCF
#SoftwareEngineering #AI #TechLeadership #CTO #Python
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: https://t.co/x4j4VqtzBc
#PydanticAI #LLMOps #AIEngineering #PythonEngineering
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: https://t.co/TQGYpMkxnE
#Python #AI #SoftwareEngineering #CodeReview
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 => https://t.co/aC8s4f4jCF
#Python #AI #Security #LLM #SoftwareEngineering
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.
👉 https://t.co/2lCwqFxyy4
#EngineeringLeadership #AIEngineering #AIAgents #TechStrategy #ProductEngineering
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.
https://t.co/whXiIzjSue
#Secrets #Python #GitHub
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: https://t.co/aC8s4f4jCF
#AI #CTO #TechLeadership #SoftwareEngineering #Python
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 => https://t.co/aC8s4f4jCF
#SoftwareEngineering #AI #TechLeadership #CTO #Python
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: https://t.co/x4j4VqtzBc
#PydanticAI #LLMOps #AIEngineering #PythonEngineering