Category: AI

  • Intentional AI: Give Agents Their Own Front Door

    Intentional AI: Give Agents Their Own Front Door

    In March 2026, AWS published guidance on governing AI agent access using MCP. A month later they followed up with a deeper post on secure access patterns. Both are worth reading — but the most important thing in them isn’t an AWS-specific detail. It’s a design principle that applies to any platform: agents and humans should not share the same access surface.

    Most platforms haven’t internalized this yet. They’re about to have to.

    This post is about the access pattern most platform engineers encounter first: user → agent tool → system. A human uses a chatbot, CLI tool, or agentic interface to interact with your platform. The agent translates their intent. There’s a real person behind the session, and accountability traces back to them. If you’re building platforms where humans use agents as their interface, this is the architecture you need.

    (The second pattern — a fully autonomous background agent with no active human in the loop — is meaningfully different from a governance standpoint, and deserves its own treatment.)

    The Wrong Answer Is the Easy One

    Someone asked me recently for API access for their AI agent. The answer is no.

    Not because agents shouldn’t have access — they should. But an API key is the wrong primitive, for four reasons that compound on each other.

    User intent travels through layers and gets lost. When someone uses a product feature that internally calls an agent that calls your API, the original human intent has been translated and interpreted by an intermediary. The API call that lands on your server isn’t the user’s action — it’s the agent’s inference about what the user wanted. Consider something as simple as “replace this record.” Does that mean overwrite it? Modify it in place? Create a new version and deprecate the old one? The agent will pick one interpretation, confidently, and execute it against your production data. There is no “this doesn’t feel right” moment. There is no pause. Google DeepMind’s paper on intelligent AI delegation (arXiv:2602.11865) calls this the principal-agent problem in multi-agent systems: misalignment accumulates across delegation chains, and without explicit accountability structures at each layer, responsibility becomes impossible to trace. The user clicked a button. What your API received was something three reasoning steps removed from that click.

    Agentic calls are non-deterministic in a way that service integrations never are. Some will argue that giving an agent API access is no different from giving another service access. It isn’t the same. A service integration is deterministic by definition: a developer wrote PUT /records/123 knowing exactly what it does, and it does that every time. An agent decides at runtime which endpoints to call, in what order, with what parameters — based on reasoning, not code. The AWS guidance is explicit on this: you must assume an agent will do anything within its granted permissions, because unlike a human or a deterministic service, it operates without the judgment that normally sits between intention and action.

    Agentic use is dynamic; an API contract isn’t built for that. APIs are designed around stable, predictable consumption patterns. A developer reads the docs, understands the schema, writes code that calls specific endpoints in specific ways. That contract holds because the consumer is deterministic. Agents aren’t. They decide at runtime which tools to invoke, in what order, with what parameters — based on reasoning, not code. DeepMind’s framework frames this precisely: delegation to an agent requires “transfer of authority, responsibility, and accountability” with “mechanisms for establishing trust between parties.” An API key transfers none of that. It just opens the door.

    Agents lead to unintentional behavior at scale — and fast. A human calling your API makes a deliberate decision. An agent executing a reasoning chain may span dozens of steps, involve context the original user never saw, and produce side effects nobody anticipated. AWS documents four specific failure modes: hallucination (an agent deletes production data it inferred was temporary), prompt injection (a malicious input redirects the agent to APIs far outside its intended scope), logic errors (a plausible but wrong conclusion and acts on it), and tool poisoning (a compromised dependency exfiltrates data using the agent’s credentials). Any one of these is bad. At machine speed, with API-level permissions, any one of these is a serious incident.

    Giving an API key to an agent collapses all of this into a single undifferentiated access grant. It works until it doesn’t — and when it doesn’t, you’ll have no way to understand what happened or why.

    Separate the Path

    The fix is straightforward in principle: agents get their own access path.

    MCP is what that looks like in practice. Instead of sharing the API surface with human users, agents connect through an MCP server. That server is something the platform controls — it defines what tools exist, what they do, what they can touch. The agent doesn’t get the full API. It gets a purpose-built interface designed for what agents actually need to do.

    This separation is load-bearing. Everything else — identity, tagging, governance, rate limiting — only works reliably if the path is separate. If an agent is just another API key holder, you’re inferring things about its behavior after the fact. If it has its own path, you know.

    Figure 1: Access Path Separation
    Figure 1: Access Path Separation

    Called Via AWS MCP

    AWS made this concrete with two IAM context keys — aws:ViaAWSMCPService (a boolean, true when the request came through any AWS-managed MCP server) and aws:CalledViaAWSMCP (a string containing the specific server name, like eks-mcp.amazonaws.com). These get automatically attached to every downstream call made through their managed MCP servers. No configuration required. You can then write policies against them:

    {
      "Sid": "DenyDeleteWhenAccessedViaMCP",
      "Effect": "Deny",
      "Action": ["s3:DeleteObject", "s3:DeleteBucket"],
      "Resource": "*",
      "Condition": {
        "Bool": { "aws:ViaAWSMCPService": "true" }
      }
    }
    

    That policy is expressing something real — that an agent making a deletion decision autonomously is a different thing from a human doing the same, and should be treated differently. You can go further and lock operations to specific MCP servers: allow EKS operations only when routed through the EKS MCP server, deny them when coming through the general AWS MCP server. That level of specificity is only possible because the path is separate.

    Beyond AWS Services

    This principle extends beyond AWS. A self-managed MCP server forces this architectural boundary onto any application. Whether exposing a proprietary database, a Kubernetes cluster, or a local compute environment, the MCP server acts as your enforcement layer.

    Because all agentic traffic is forced through this checkpoint, you have the structural leverage to enforce identity context. The path is separate, not hidden — the endpoint is reachable by any MCP client with valid credentials. Separation is about enforcement, not obscurity: provenance is trustworthy because the server mints it from the authenticated session, not because the endpoint is secret. You can configure the MCP server to attach specific tags, issue scoped JWTs, and pass rigid server-injected headers when translating tool calls into backend requests. The server injects the provenance; the agent cannot spoof or bypass it. As a result, your backend receives requests structurally guaranteed to be agent-driven, with trustworthy provenance. With raw API or CLI access, that seam vanishes entirely.

    Why Not Just CLI Access — or an API Gateway?

    The obvious pushback: why not just let agents use the CLI or existing SDK tooling? It works, it’s already there, agents can authenticate through OAuth or API keys just like any other caller. A more sophisticated version of the argument: put Kong or Apigee in front of your existing API, add an X-Agent: true header convention, and call it done.

    Both approaches have the same fundamental problem: governance is client-side.

    CLI / SDK / API KeyMCP Server
    Governance locationClient-side — platform trusts the callerServer-side — platform enforces the rules
    Agent identityClient-declared — any caller can set itStructural — property of the path itself
    Resource taggingConvention — client must remember to tagAutomatic — MCP layer tags every call
    Rate limitingShared with human users — blunt instrumentPer-agent, per-tool, per-session
    ElicitationNot possible — agent executes blindlyServer can pause and require confirmation
    Capability surfaceAgent infers what to call at runtimeDefined tool schema — explicit contract

    With CLI access, the platform hands the agent credentials and trusts it to behave correctly. Rate limits, scoping, tagging, access boundaries — all of that depends on the client respecting conventions the platform has no way to enforce. If the agent creates a resource without the right metadata, that resource is untagged forever.

    API gateways don’t solve this — they just add a layer. If you’re routing agent traffic through Kong or Apigee and relying on a header to identify it as agentic, that header is client-declared. The agent sets it. Any caller can set it. Nothing enforces that the caller actually is an agent, or that it’s behaving within the bounds you intended. You’ve added observability infrastructure, not governance infrastructure. You can see the header. You can’t trust it.

    MCP moves governance to the server side. The platform controls the MCP server, which means the platform controls what tools exist, what they can do, what gets tagged, and what limits apply — regardless of what the agent does on its end. An agent can’t call an endpoint the MCP server doesn’t expose. It can’t create a resource without the provenance tag the server attaches. It can’t declare itself as something it isn’t.

    MCP Evolves Differently Than an API

    There’s one more dimension worth naming: evolvability.

    You version an API carefully. You deprecate slowly. You write migration guides. Breaking it breaks trust with every developer who integrated against it. The stability is the point — and that stability becomes a constraint when you need to move fast on the agentic surface.

    MCP decouples the two. When an MCP tool definition changes, the server communicates that change structurally through updated tool descriptions and rich error responses. An agent can ingest this metadata at runtime and adjust its reasoning for subsequent calls — something static client code simply cannot do. The underlying API remains a stable contract; the MCP surface evolves at the pace of agentic capabilities.

    This doesn’t mean you should change MCP tool schemas carelessly. Rename a parameter without any signal and an agent will fail just like any other caller. The evolvability comes from the feedback loop: the MCP server provides enough structured context for the agent to reason about what changed, rather than just receiving a 400 with no guidance. The API doesn’t move until it has to. The MCP surface moves as you learn.

    Service Accounts Get the Principal Wrong

    There’s another counterargument worth addressing directly: “just give the agent its own service account, like you would for any app-to-app integration.” This sounds reasonable — service accounts are a well-understood pattern with scoped credentials and revocation. But it misses something fundamental about what an agent actually is.

    A service account is scoped to the service. An agent is acting on behalf of a user. Those are different principals, and they need different credential models. A service account gives the agent the same permissions regardless of which user it’s acting for — which means the blast radius of any mistake, or any attack, is service-wide, not user-scoped.

    This is exactly what happened with Meta’s AI customer support agent in June 2026. Attackers simply asked the agent to link high-profile Instagram accounts to email addresses they controlled, and it complied — including taking over the dormant Obama White House account. The agent had service-level write access across accounts when the action it was performing was inherently user-scoped: changing the email associated with one specific account, on behalf of one specific session. A user-scoped credential would have prevented this altogether.

    Tag Everything — Then Enforce Against It

    Because all agentic traffic flows through the MCP layer, that’s where you guarantee every resource an agent creates gets tagged. Not as a best-effort convention, not dependent on the agent being well-behaved, not something you have to remember to implement in every downstream service. The MCP layer does it structurally, for every call, every time.

    For example, agents can be blocked from modifying records they did not create, while human-authored data can require explicit approval steps for deletion. AWS demonstrates this at the IAM layer: session tags attached during AssumeRole persist for the duration of the session and can be referenced in any downstream policy using aws:PrincipalTag. The tag applied when the agent authenticates governs its authority for the entire session.

    The compounding logic is simple: the tag is trustworthy because the path is separate. The policy is enforceable because the tag is trustworthy. When a failure occurs downstream, the provenance allows for an immediate distinction between a human-designed decision and an agent-inferred action — leading to a fundamentally different remediation path.

    Figure 3: Provenance Tagging Flow

    Govern Agentic Access at the MCP Layer, Not the API Layer

    Separate paths let you apply agent-specific controls without touching anything humans use.

    Rate limiting is the obvious case. Tightening API rate limits to protect against runaway agents punishes every user. A separate MCP path lets you apply limits calibrated for machine-speed behavior — per agent session, per tool, per model — without any of that touching the human-facing API. AWS notes in their guidance that agents can make thousands of API calls in seconds; the controls you need for that are categorically different from what you need for a human clicking through a UI.

    But rate limits are just the beginning. You can require confirmation steps before destructive MCP tool calls. You can log every tool invocation with full context independent of your API logging. You can throttle specific tools independently. You can return structured error information to agents that humans would never need — because agents can actually use it in their reasoning in ways no human reading a UI response ever could.

    MCP also supports elicitation — the ability for the server to pause a tool call mid-execution and ask the agent to confirm before proceeding. This is particularly valuable for destructive or irreversible actions: rather than the agent executing blindly, the platform can require explicit confirmation at the MCP layer, making the action intentional rather than inferred. In the user → agent → system pattern, elicitation is especially powerful: the agent can surface the confirmation back to the user before acting, keeping the human meaningfully in the loop on consequential decisions while still letting the agent handle everything else. Intentional AI isn’t about removing humans — it’s about making sure the right decisions stay with them.

    Genomics Platform: Why This Matters When Runs Are Costly

    On my team we run a genomics platform, and path separation isn’t an abstract design preference — it’s load-bearing.

    The direct API exposes everything: starting pipeline runs, creating sample records, writing analysis results, allocating compute resources. An agent with a key can do all of it — and therein lies the problem.

    A human researcher starting 10 consecutive pipeline runs through the UI made 10 deliberate decisions. They looked at each one, chose the parameters, and clicked submit. An agent starting 10 consecutive runs did so because its reasoning chain led it there — maybe correctly, maybe not. The intent behind those runs is fundamentally different, but if the agent used an API key, your platform has no way to distinguish them. Both look identical in your logs.

    This matters especially because of cost. A pipeline run isn’t just a database write — it triggers compute allocation, kicks off downstream jobs, and may cascade into dependent analyses before anyone notices something was wrong. Stopping a run mid-flight is often more disruptive than letting it finish. And once results land, they get picked up by other processes, incorporated into reports, used as the basis for the next decision. The practical irreversibility isn’t a property of the write operation itself — it’s a consequence of what the run sets in motion. The DeepMind delegation framework captures this precisely: tasks that produce cascading real-world side effects require stricter authority gradients and liability boundaries than contained, reversible ones. A single shared API surface makes that distinction impossible to enforce.

    The same goes for the data those runs produce. In genomics, the difference between a human-curated result and an agent-inferred result matters enormously — for reproducibility, for clinical trust, for downstream decisions. An agent that creates a sample record or writes an analysis output should be tagged as such, and that tagging should be structural, not inferred. If the agent used the same API surface as a researcher, you’re guessing at provenance after the fact.

    An MCP server changes this. You expose what agents legitimately need — starting runs, querying status, reading results — through a path that carries identity and enforces different limits. An agent can start runs, but the platform knows they came from an agent, can cap consecutive submissions independently of what human users are allowed to do, and tags everything created through that path automatically. The human-facing API stays untouched. The governance model for each is calibrated to the actual risk profile of each type of caller.

    This Is a Design Decision, Not a Feature

    The platforms that get this right will be the ones enterprises trust with agentic workflows — because when something goes wrong, and it will, they can answer the question: what did the agent do, who authorized it, and why? The platforms that don’t will be explaining why they can’t.

    Designing for it is not complicated in principle. Agents get their own path. That path carries identity. Everything they touch gets tagged. Governance lives at the MCP layer. The API stays a contract for humans and evolves on human timelines. The MCP surface evolves on agent timelines.

    Not designing for it means API keys, invisible agents, untraceable provenance, and governance retrofitted after something goes wrong. Every platform is going to have AI users. The question is whether you designed for that or whether you find out the hard way.


    Further reading: Understanding IAM for Managed AWS MCP Servers and Secure AI Agent Access Patterns to AWS Resources Using Model Context Protocol — both worth reading in full if you’re building any of this. Also worth looking at: Intelligent AI Delegation (Google DeepMind, arXiv:2602.11865) and auth.md from WorkOS.

    Running a platform and thinking through agentic access? I’d like to compare notes.

  • Beyond Coding: Giving Claude a Seat at the Meeting Table

    Beyond Coding: Giving Claude a Seat at the Meeting Table

    I recently came across the following article: It’s Hard to Use AI as a Team. These 3 Practices Can Help.” The authors argue that while most organizations expect AI to automatically improve teamwork, the opposite often happens—without intentional integration, AI can actually narrow participation and shift ownership away from the team.

    Specifically, the article mentioned 3 specific tips for working more efficiently with an AI:

    1. Engage with AI as a team. Instead of siloed interaction, the whole group should interact with the AI together.

    2. Use AI in flexible roles. Move beyond passive tasks and give the AI active, rotating responsibilities.

    3. Keep ownership with the group. Ensure everyone participates in maintaining and vetting the AI interaction so the team remains

    Our Experiment

    Our workspace is highly innovative and provides opportunities to experiment at work. I believe that experimentation is a key to building a superteam.

    Taking advice from the article, we and extend our use of AI from solo productivity to treating it as a literal member of the team during our design reviews.

    Since many of our teammates are remote, we facilitated this by having the presenter start a Claude Code instance and present it side-by-side during the presentation. To make Claude a truly effective teammate, we ensured it had full context of our environment:

    • Local Repository Context.Claude runs directly in the repository where the code changes are being made.
    • Integrated MCPs. It is equipped with JIRA, Confluence, and GitLab integrations.
    • Global Code Search. It has integrated access to Sourcegraph for quick searches across the broader codebase.
    • Real-Time Research. It has full web access to pull in external documentation or context.

    This setup gave the AI a visual presence in the meeting, making it a shared resource for everyone on the call rather than a hidden tool for a single developer. Here is how we applied those core practices and some insights into how they turned Claude into an effective teammate.

    The results were excellent with real tangible outcomes (see below).

    And here how we applied and some insights how we applied these core practices to turn Claude into an effective teammate.


    1. Engage with AI as a Team

    One of the biggest mistakes in AI adoption is “siloed” use. For remote teams, this is even more dangerous as it can fragment the conversation. In our meeting, we didn’t have one person “driving” Claude in secret. Because it was shared side-by-side, everyone could see the prompts and the logic as it unfolded.

    We started by introducing every member of the team to the AI: three software engineers and one bioinformatics engineer (who is also a primary user of the platform we were discussing).

    Why this mattered

    By telling Claude who was asking each question, the AI was able to adjust its technical depth and context. It spoke to the bioinformatics engineer about user-facing impact and to the developers about concurrency and cache manipulation. Because the output was visible to the whole remote group, it acted as a “living record” that kept everyone aligned.

    2. Use AI in Flexible Roles

    While the original article describes using AI in different behavioral personas (like a “Challenger” or “Customer”), we adapted this by giving our AI teammate multiple functional roles that shifted throughout the hour based on its capabilities:

    • The Verifier. Claude had access to our specs via a Confluence MCP and the code base. During the meeting, we constantly asked it to verify if our verbal proposals were actually in line with the written spec and the existing codebase.
    • The Technician. When deep technical questions arose—like how to handle cache manipulation or complex concurrency—Claude provided immediate suggestions based on our actual code.
    • The Scrum Master. We had pre-created several Jira tickets. As the discussion evolved, we asked Claude to update those tickets in real-time to ensure they captured the latest consensus and technical requirements.

    We are also looking for opportunities in the future to test out more of those behavioral personas mentioned in the research article to see how they might push our team’s critical thinking even further.

    3. Keep Ownership with the Group

    “Ownership” here doesn’t mean a single person is in charge of the AI. Instead, it means everyone participates in maintaining the interaction. The “quiet reading” phase is a staple of our reviews—we spent 15 minutes at the start reading the design and listing our own comments so that our own judgment remained the primary driver.

    We are already looking to extend this sense of collective maintenance to other tools. For instance, our Ops bots are maintained and used collectively on Ona.

    The Tangible Outcomes

    By the end of a typical 60-minute review, the “teammate” approach yields results that used to take hours/days of follow-up work. Our immediate outcomes now include:

    1. An updated Confluence spec page The spec is automatically updated to contain all discussed points and factors, ensuring no “institutional knowledge” is lost.

    2. Synced Jira tickets and epics. All relevant tickets are updated or created during the meeting, reflecting the latest consensus.

    3. Minimal follow-ups Because the AI helped us resolve technical questions and document decisions in real-time, there are no more follow-up tasks required after the meeting. We leave the room with the work actually done.

    The “On-Ramp” Challenge

    It’s important to note that this isn’t always easy or automatic. We found that it is very natural to just jump into a discussion and start a meeting without the AI.

    We often had moments where, 20 minutes in, we’d realize: “Hey, we should have started the Claude instance earlier.” We’d be sitting there with technical questions or spec uncertainties that we could have checked immediately if we had been using our “teammate” from the start. Building the habit of bringing the AI into the room before the questions arise is a significant part of the learning curve.

    The Takeaway

    Using AI as a teammate isn’t about letting the machine do the work. It’s about leveraging its ability to process vast amounts of context (code, specs, tickets) to elevate the human conversation.

    The most surprising result? Design reviews are fun now. There’s a new kind of energy in the room when you can resolve a technical debate in seconds or verify a spec on the fly. I find myself constantly looking for new opportunities to bring this “teammate” into other parts of our workflow.

    When everyone in the room—including the AI—knows their role and the context of their peers, the “unraveled strands” of a complex system start to untangle much faster.


    What do you think? Have you tried bringing an AI into your team meetings?

    Footnote – The industry is rapidly moving toward making AI a native part of the video call experience. Technologies like

    Google Beam (formerly Project Starline) are pushing this further with 3D volumetric video and AI personas like “Sophie” that can interact in real-time. Our experiment with sharing a Claude instance side-by-side is a low-friction version of this future—bringing the AI out of the private chat window and into the shared team space.

  • Field Report – AgentCon Sillicon Valley (Part 2)

    Field Report – AgentCon Sillicon Valley (Part 2)

    The Conference

    AgentCon Silicon Valley is a free, one-day, in-person conference for developers building with AI agents. This post is about my personal experience and thoughts about the evnet. This continue second part of two parts series – see my first post at https://unraveledstrands.com/2026/05/10/agentcon-silicon-valley-2026-part-1/. A major theme of the event was about sharing skills to be an agent boss – a builder who build tools and frameworks to delegate and Control agents.

    In this post, i will focus about the talks that happened at the PM session of the conference.

    • Lessons from a No-Code Library – Drew Breunig
    • Securing Coding Agents: Sandboxes, Guardrails, and Real-World Attacks – Dan Ndombe
    • From One-Shot to Agentic: Optimizing Shop Intelligence with DSPy – Kshetrajna Raghavan
    • GitHub Agentic Workflows – Peli de Halleux
    • Client side Web AI Agents for the agentic internet of the future – Jason Mayes

    Lessons from a No-Code Library – Drew Breunig

    This talk was probably my favorite of the conference. It addressed the challenges of Spec-Driven Development (SDD) with AI coding—a workflow that is now practiced everywhere in the industry in one form or another. In my team, we use Confluence and Jira as the source of truth, with spec files guiding the agent during implementation.

    However, as many others have found, the difficulty is that agents generate code faster than we can review it. Since specs are never 100% perfect, an agent will inevitably make assumptions to fill in the gaps. Without a feedback loop, these “silent decisions” are never documented or tested. This is exactly why code review subagents and concepts like “ultrareview” have become so necessary.

    The Framework: The Spec-Driven Development Triangle

    Drew Breunig explored this problem while developing whenword, a library containing almost no manual code—only specs and tests. The implementation was left entirely to an agent, which often
    results in “spec drift” as the code evolves away from the original documentation.

    To manage this, Drew introduced the Spec-Driven Development Triangle, which balances Spec, Test, and Code. He uses an LLM as a judge to compare the final implementation against the original spec. The model identifies exactly where the agent filled in gaps or deviated from the requirements, flagging those points for the developer to review.

    This approach mimics closely to Behavior-Driven Development (BDD) – Combining BDD to define the ground truth with an LLM judge to verify the implementation is a practical way to maintain
    oversight.

    Image from https://www.dbreunig.com/2026/03/04/the-spec-driven-development-triangle.html

    The Tool: plumb

    To automate this feedback loop, Drew built plumb. The tool integrates directly into the development workflow via git hooks—specifically pre-commit and post-commit hooks.

    When a developer attempts to commit code, the pre-commit hook triggers an LLM analysis of the staged changes and the
    agent’s conversation history. It identifies any “silent decisions” made during implementation and blocks the commit if there are undocumented changes. Once the developer approves the findings, plumb automatically syncs those decisions back into the specs and tests, ensuring the documentation remains an accurate reflection of the software

    I find it really inspiring on how Drew successfully took the philosophical lessons from his whenword experiment and translated them into a pragmatic, usable tool that solves a real engineering bottleneck.

    Securing Coding Agents: Sandboxes, Guardrails, and Real-World Attacks – Dan Ndombe

    Dan Ndombe is from docker and he was there to talk about docker sandboxes. Providing a secure enviornment is defintiely a very important aspect of agentic workflows and workload. you are giving agent autonomy to drive, but there must be guardrails and safety net. This is simliar to my talk at the 2026 Backgorund agent summit where i spoke of using Ona a envionrment for agents to run background implemntation.

    Docker sandbox are MicroVM-isolated environments. By using a hardware-level hypervisor, each agent gets its own dedicated Linux kernel. This is in contrast to regualr dockerc aontiners, which share the host’s kernel, a “jailbroken” agent could
    theoretically escape to the host machine via a kernel exploit.

    Features Dan highlighted include:

    • Hypervisor Isolation: Each sandbox runs in a lightweight MicroVM (using Apple Hypervisor, Windows WHP, or KVM),
      isolating the agent from the host processes entirely.
    • Network Guardrails: All egress is routed through a proxy that enforces strict domain allow-lists, preventing agents
      from exfiltrating secrets.
    • Private Docker Daemons: The sandboxes include their own private Docker engine, allowing agents to run docker build
      or docker compose (Docker-in-Docker) without needing dangerous “privileged” access to the host

    I was most interested in the egress proxy. For long-running tasks and agents operating ‘in the wild,’ preventing the agent from having direct access to secrets stored in environment variables or accessible files is going to be extremely relevant and important

    Dan Ndombe’s core message provided a powerful summary of the “Agent Boss” era: “An AI agent is only as safe as we want it to be.” It isn’t about taking away the agent’s tools, but about ensuring those tools are used within a secure, kernel-level boundary that the human ‘Boss’ controls.

    From One-Shot to Agentic: Optimizing Shop Intelligence with DSPy – Kshetrajna Raghavan

    Kshetrajna Raghavan, a Principal Engineer at Shopify, delivered what I found to be the best case study for scaling agents in production. He shared the journey of “Shop Intelligence”—Shopify’s system for extracting structured data from millions of highly customized, non-standard merchant stores.

    His talk perfectly illustrated a lession that I have learnt and applying at work: Being an Agent Boss requires intense pragmatism.

    Given enough attempts, compute, and liberty, an advanced AI could probably solve the majority of the technical issues
    we face. But at what cost? If a task costs more to execute than the value it generates, it ceases to be a practical solution. Are we building Lamborghinis to do the job of a tow truck?

    Image from https://cleantechnica.com/2023/12/24/texas-dps-is-less-amused-with-the-model-y-superheavy-than-we-are/

    Shopify’s journey highlighted this exact tension:

    1. The “One-Shot” Wall
      Initially, Shopify used single, large API calls to OpenAI models to analyze store content. While this worked for
      simple cases, it was unsustainable. Not only was the cost of processing millions of stores astronomical, but a single
      prompt couldn’t handle the messy reality of diverse store layouts.
    2. Moving to the ReAct Loop
      The first major shift was moving from a static “blob” of text to an autonomous agent using a ReAct (Reasoning and
      Action) loop. Instead of guessing based on a single snapshot, the agent could explore the store—deciding which pages
      to visit and which data points were missing.
    3. The Swarm of Sub-Agents
      To manage the complexity of exploration, they broke the “do-it-all” agent into a swarm of specialized sub-agents. One
      agent might focus on brand identity, while another focuses on product categorization. This modularity doubled their
      precision, but relying on third-party APIs for this many distinct agent interactions was still prohibitively
      expensive.
    4. Self-Hosting and Compiling with DSPy
      The final, most impressive step was using DSPy to programmatically optimize the entire pipeline so they could bring it
      in-house. Instead of manually tuning prompts for OpenAI, they treated their workflows as code that could be compiled
      against specific metrics. This optimization allowed them to move away from third-party APIs entirely. By spinning up their own H100 clusters and
      leveraging self-hosted Qwen models, the economics of the system fundamentally changed. The results were incredible:
    • 75x Cost Reduction: The combination of an optimized agentic architecture and self-hosted models drove the cost down
      by a factor of 75, while actually improving data quality.
    • Universal Coverage: This efficiency allowed Shopify to scale the system to analyze every single store on the
      platform, something that would have been financially impossible on a per-token API model.

    Raghavan’s core message was that “architecture compounds.”
    Success didn’t come from a single breakthrough, but from the steady evolution from one-shot API calls to a self-hosted swarm of sub-agents, all programmatically optimized for the real world.

    I am also applying this approach in my own work. We started using agentic workflows for basic debugging and have since built on those guardrails and architecture to allow agents to handle more complex tasks, like background agents performing code implementation and self-improvement. While we haven’t found a need to self-host models yet, switching to cheaper models (like MiniMax or AWS Nova Lite) for basic tasks has helped manage costs and made the system much more practical for everyday use.

    GitHub Agentic Workflows – Peli de Halleux

    I find GitHub Agentic Workflows to be an easy and practical way for engineering teams already using Github to adopt agentic workloads. This is especially true for large enterprises since which often have more restrictions and compliance requirements to follow. Also applying AI in a more controlled environment like ci/cd process should has a less negative consequences when things goes sour as oppose to a customer facing agent

    The Agent Factory in Action

    Peli described a setup that feels a bit wild but makes total sense: AI writing software that writes AI software. In Peli’s Agent Factory, they’ve stopped trying to build one giant, monolithic agent. Instead, they have over 100 little “bite-sized” workflows doing highly specific jobs.

    Of course, when you have over 100 agents running around a repo, no human can read all their outputs. Peli’s solution to this is to just add more agents. They use “meta-agents” whose entire job is to watch the other agents and make sure they are behaving.

    screenshot from https://github.github.com/gh-aw/blog/2026-01-12-welcome-to-pelis-agent-factory/

    Something that ties perfectly back to what Drew Breunig’s talk was: GH AW has moved to a spec-only contribution model. This is the whenword experiment playing out at an enterprise scale. If you want to contribute to the Agent Factory, you don’t write code. You write a Markdown file—a spec—that describes exactly what you want the agent to do. The system then “compiles” that natural language into a secure GitHub Action.

    It’s an interesting look at the future. Seeing a giant like GitHub bet so heavily on spec-driven contributions makes
    me think this isn’t just a neat trick—it’s probably the only way we are going to safely manage these systems at scale.

    I managed to speak with Peli after his talk to get some extra tips and tricks for improving agentic CI/CD workflows. He recommended checking out qmd (https://github.com/tobi/qmd) as a code wiki. We also briefly discussed how to avoid reward hacking in the CI/CD process—a conversation that eventually inspired me to write my post on

    Specs vs. Code for Security

    This also made me think a lot about security. In a normal open-source or enterprise repo, you have to read every line of code to make sure someone hasn’t introduced a vulnerability. But with spec-driven development, you’re just auditing the intent. Because the actual implementation is generated within strict, pre-defined guardrails, a spec-driven contribution might actually be way more secure than a human-written one.

    Client side Web AI Agents for the agentic internet of the future – Jason Mayes

    Find out more https://github.com/jasonmayes/WebAIAgent

    I caught the final session of the conference, which focused on client-side Web AI agents. The speaker used flight booking as an example. Finding a flight usually requires navigating rigid, static filters—picking dates, checkboxes, and price sliders. The demo showed how a user could instead use voice to surface flights, with the UI changing to fit the user’s intent. This shift to client-side LLMs highlights a few distinct points for running AI in production:

    • Latency: Processing the prompt locally on the device’s hardware removes the round-trip delay to cloud APIs.
    • Task-Appropriate Models: As the Shopify talk noted, you don’t need a state-of-the-art model for every single task. A webpage doesn’t require a massive, generalized model; it just needs one capable of mapping user intent to specific local functions.
    • Production Economics: Moving inference to the client device removes the cloud infrastructure costs of running agents at scale.

    This architecture could change how we approach the specificity paradox in web design. Currently, developers and designers spend time building custom user experiences for every edge case, trying to predict what a user might do next. With native cognitive capability in the browser, websites could simply expose their tools and data structures via protocols like WebMCP, allowing a local model to parse the page and handle the specific workflow a user requests in that moment. This also points to a practical reason behind Google’s open-source strategy with Gemma 4. If lightweight models are going to run natively within the browser environment (like Chrome’s built-in AI architecture), the model weights must live on local consumer devices. Making Gemma open-weight aligns with a framework where rendering and agent orchestration happen entirely on the client side.

    Closing thoughts

    AgentCon was timely and highly relevant. It’s clear that being an “Agent Boss” will be a mandatory skillset in the AI
    era. This means taking responsibility for the agent’s environment—whether that’s a secure sandbox, a CI/CD pipeline,
    or a web browser. Ultimately, our success will be defined by how well we provide the right context and guardrails to
    turn autonomous actions back into human-led intent.

  • Field Report – AgentCon Silicon Valley 2026 (Part 1)

    Field Report – AgentCon Silicon Valley 2026 (Part 1)

    AgentCon Silicon Valley is a free, one-day, in-person conference for developers building with AI agents.

    One of the peaks of living in the bay is that every week there will be a tech conference that is worth going to. Last week, I attended AgentCon Silicon at the Computer History Museum, Mountain View California, which btw, is a fantastic venue. Because there was so much great content to digest, I’m breaking my report into two parts to stay within a reasonable “context window” for an article.

    The Conferecne

    AgentCon 2026 happened on May 4th (yes lots of Star War reference) is a small to mid size conference with two to three concurrent tracks happening at the same time. The key sponsors were:

    The conference was great – Majority (if not all) of the speakers were engineers and developers. Content were all very applicable to my daily work. The full schedule can be found at the event’s page.

    All together I went to a total of 9 talks. I am very glad that there were always seats available and attendances were pretty evenly distributed between concurrent talks.

    These are the talks I attended:

    Part 1 (this entry, morning session)

    • Will The Real Autonomous Agent Please Stand Up – Patrick Chanezon, Dona Sarkar
    • Your agent needs a sandbox, not a desert – Samuel Colvin
    • How to Build Auditable Agents Using Context Graphs – Nyah Macklin

    Part 2 (afternoon session)

    • Agents Don’t Know What They Don’t Know – Rob Zuber
    • Lessons from a No-Code Library – Drew Breunig
    • Securing Coding Agents: Sandboxes, Guardrails, and Real-World Attacks – Dan Ndombe
    • From One-Shot to Agentic: Optimizing Shop Intelligence with DSPy – Kshetrajna Raghavan
    • GitHub Agentic Workflows – Peli de Halleux
    • Client side Web AI Agents for the agentic internet of the future – Jason Mayes

    Will The Real Autonomous Agent Please Stand Up – Patrick Chanezon, Dona Sarkar

    The session was covered by two excellent speakers – and I would have loved to hear them speak longer

    To open AgentCon, Dona Sarkar shared two key concepts that framed the rest of the day. She spoke about the evolution of becoming an ‘Agent Boss’ and, at the same time, reminded us to check if we’re building real AI innovation or simply
    settling for ‘faster horses.’ Building faster horses are fine – if that free us up to work on transformative AI. These two thoughts really resonated throughout the sessions.

    If I had asked people what they wanted, they would have said faster horses,” – Not Henry Ford.

    Dona also explored the various form factors AI will manifest in. Her talk was funny, entertaining, and highly informative.

    Patrick Chanezon

    Unfortunately, due to a work matter, I missed the first half of his talk. However, in the portion I caught, he spoke about the shifting roles of ICs and Managers in the agentic evolution. Doubling down on the theme of “Agent Bosses,” he explained that an IC’s success now depends on how effectively they can oversee their agents. He also referenced a key ACM article suggesting that as AI automates entry-level tasks, the industry must adopt a “preceptor” model to ensure junior developers still gain the critical judgment needed to become the next generation of senior engineers. To be successful in this new landscape, you will need the following skills:

    (taken from https://www.youtube.com/watch?v=0HI3OIi-YJY)

    Your agent needs a sandbox, not a desert – Samuel Colvin

    Samuel Colvin (the creator of Pydantic) introduced Monty, a Rust-based Python interpreter designed for safe agentic use. Unlike CPython, it operates on a “deny-all” security model, starting with zero capabilities. Because it is an interpreter, it boasts microsecond startup times. It’s an ideal tool for basic Python tasks, text manipulation, and math within a secure environment.

    How to Build Auditable Agents Using Context Graphs – Nyah Macklin

    Nyah discussed leveraging Neo4j to build context and memory graphs. This was one of my favorite talks because it was immediately applicable to my current projects. I am so happy to see Neo4j releasing these tools as completely open-source rather than “fauxpen” source.

    If you are serious about becoming an “Agent Boss,” a scalable, distributed context and memory system is a must. Research (such as the CommGPT paper) and practical application consistently show that you can achieve significantly better performance by providing a robust Knowledge Graph and RAG system rather than relying solely on fine-tuning.

    Key Takeaway: The primary advantage of GraphRAG over traditional vector-based RAG is its inherent ability to map and understand complex, interconnected relationships within data. In her talks she introduced and mentioned that while neo4j-labs/agent-memory use semantic search for core retrieval, they leverage Knowledge Graph structures for organization, deduplication, and context assembly.

    Closing Thoughts on Part 1

    The morning sessions at AgentCon made one thing clear: we are moving past the “AI as a chatbot” phase and into the “AI as a workforce” era. Becoming an Agent Boss isn’t just a catchy phrase; it’s a fundamental shift in how we think about code, memory, and security. Whether it was the security of Rust-based sandboxes or the structural power of Knowledge Graphs, the bar for “real innovation” is being set higher every day.

    I’m still processing the implications for the future of the engineering profession, but the transition toward an Agentic SDLC is clearly well underway.

    Coming Next in Part 2

    In the next entry, I’ll dive into the remaining six talks from the afternoon tracks, focusing on the practical “how-to” of securing and optimizing agents.

    Here’s what I’ll be covering:

    • Agents Don’t Know What They Don’t Know: Handling uncertainty with Rob Zuber.
    • Lessons from a No-Code Library: Drew Breunig on simplifying complexity.
    • Securing Coding Agents: A deep dive into guardrails and real-world attacks.
    • Optimizing Shop Intelligence: Using DSPy to move beyond one-shot prompts.
    • GitHub Agentic Workflows: How the industry giants are orchestrating agents.
    • Client-side Web AI Agents: The future of the agentic internet.

    Stay tuned—Part 2 will be live shortly!

  • Notes on Autonomous Development – Prevent AI Reward Hacking In Gitlab CI/CD

    Notes on Autonomous Development – Prevent AI Reward Hacking In Gitlab CI/CD

    “When a measure becomes a target, it ceases to be a good measure.” – Goodhart’s Law

    I frequently run autonomous agents in the background to handle development tasks. I am using a Trunk-Based Development modelwhere the agents are able to merge to branch via auto-merge when the CI/CD pases. However, I have observed agents attempted “reward hack” – skipping tests or bypassing checks to satisfy their goal of completing a task.

    To ensure the integrity of my CI/CD pipeline, i have implemented the following guardrails

    Enforcing “Pipeline Must Succeed”

    This is a baseline requirement, but it is insufficient on its own. There is nothing stopping an agent from removing or editing a test suite to ensure the pipeline passes, thereby triggering an undeserved merge.

    Utilizing CODEOWNERS

    The CODEOWNERS file assigns ownership to specific files or directories. By combining this with branch protection rules, you can ensure that any changes to critical files require manual approval from a human owner.

    It is vital to include self-protection rules. You must prevent the agent from modifying the CODEOWNERS file itself, as well as any related CI configuration files. If an agent can edit your CI YAML, it can simply “silence” the test steps. Inside your CODEOWNERS file, you should add:

    Inside your CODEOWNERS file, add:
    CODEOWNERS @your-username
    very_important_tests.py @your-username
    .gitlab-ci.yml @your-username
    # Include any other nested CI includes or scripts
    ci/scripts/* @your-username

    Using Pipeline Execution Policies

    If we look at industries with the higest stakes for software engineering (i.e Aviation / Space) – external verification becomes very important. Relying on a single repository to guard itself is starting to feel like a loop that’s too easy to break. It’s either insufficient or just really inefficient to manage.

    GitLab’s Pipeline Execution Policies allow teams to enforce mandatory, immutable CI/CD jobs across specific projects. These policies ensure that critical validation gates cannot be bypassed or modified by an autonomous agent, as the configuration lives outside the agent’s reach.

    Futhermore, pipeline execution policy jobs can be assigned to one of the two reserved stages:

    • .pipeline-policy-pre: Runs at the very beginning of the pipeline (before the .pre stage). This is ideal for security scans or IaC (Infrastructure as Code) validation to prevent unwanted code from executing.
    • .pipeline-policy-post: Runs at the very end (after the .post stage). This is the place for integration tests, ensuring test coverage levels are maintained, and preventing “spec drift.”

    Other Mechanisms and Conclusion

    There are several other tools to enhance CI/CD verification that are worth exploring:

    • External Status Checks: Requiring a “green light” from an external service.
    • Webhooks: Triggering secondary validation layers.
    • Scan Result Policies: Blocking merges if new vulnerabilities are detected.
    • Push Rules: Prohibiting specific file changes or naming conventions.

    Software development is evolving, and our CI/CD practices must evolve with it. We have moved from simple “build and test” routines to a world where we are governing autonomous intelligence. It is a challenging, yet incredibly exciting time to be a software engineer.

  • Design Pattern Matters -Level up your Lambda Code (including AI Generated Code) with these 3 patterns

    Design Pattern Matters -Level up your Lambda Code (including AI Generated Code) with these 3 patterns

    3 Essential Design Patterns for Robust AWS Lambda Functions

    When you first start with AWS Lambda, it’s easy to write simple, single-file scripts. But to build robust, enterprise-grade serverless applications, you need to apply proven software design patterns. These patterns help you create code that is testable, maintainable, and scalable.

    This post will explore three essential design patterns—and their common anti-patterns—that will immediately elevate your Lambda functions.


    Dependency Injection and the Principle of Separation of Concerns

    Perhaps the most important principle for writing clean Lambda functions is Separation of Concerns. While not a formal design pattern itself, the principle is simple: always separate your core business logic from the Lambda handler code. The pattern we use to achieve this separation is Dependency Injection (DI).

    The Anti-Pattern: Mixing Logic in the Handler

    Developers often write all business logic directly inside the handler, creating the database client and mixing it with validation and event parsing. This makes the code impossible to test without creating complex mock AWS events.

    Python

    # ANTI-PATTERN EXAMPLE
    import boto3
    
    def lambda_handler(event, context):
      # Dependency is created and used directly inside the handler
      dynamodb_client = boto3.client('dynamodb')
        
        # Business logic is mixed with event parsing
      user_data = event['detail']
      if not user_data.get("email"):
        raise ValueError("Email is required.")
            
        # Database interaction is hardcoded
      dynamodb_client.put_item(
        TableName='Users', 
        Item={'email': {'S': user_data['email']}}
      )
      return {"status": "User created"}
    
    

    The Pattern: Inject Your Dependencies

    You implement Separation of Concerns by designing your core logic functions to accept their dependencies (like a database client) as arguments. The Lambda handler is then only responsible for creating those dependencies and “injecting” them.

    Python

    # business_logic.py
    # This function is pure, testable, and knows nothing about Lambda.
    def process_user_signup(user_data: dict, db_client):
      if not user_data.get("email"):
        raise ValueError("Email is required.")
      db_client.put_item(TableName='Users', Item=...)
      return "User created"
    
    # --- lambda_handler.py ---
    import boto3
    from business_logic import process_user_signup
    
    # Initialize client once for reuse
    dynamodb_client = boto3.client('dynamodb')
    
    def lambda_handler(event, context):
      user_data = event['detail']
        # The dependency is "injected" into the core logic
      result = process_user_signup(user_data, dynamodb_client)
      return {"status": result}
    
    

    With this pattern, you can easily unit-test process_user_signup by passing it a simple dictionary and a mock database client.

    Treat software like a well-run kitchen. Each chef has a single responsibility—like a software component. This is how complex systems deliver a quality product, whether it’s a meal or an application.

    2. The Dispatcher Pattern for Routing Events

    The Anti-Pattern: The if/elif/else Chain

    A single Lambda is often triggered by different event variations from the same source (e.g., a DynamoDB Stream sends INSERT, MODIFY, and DELETE events). The most common anti-pattern is a long, cumbersome if/elif/else chain in the handler. This is hard to read and brittle to change.

    Python

    # ANTI-PATTERN EXAMPLE
    def lambda_handler(event, context):
      for record in event['Records']:
        event_name = record['eventName']
        if event_name == 'INSERT':
          print("Handling INSERT event...")
          # ... insert logic ...
        elif event_name == 'MODIFY':
          print("Handling MODIFY event...")
          # ... modify logic ...
        elif event_name == 'DELETE':
          print("Handling DELETE event...")
          # ... delete logic ...
        else:
          print("Warning: Unknown event type.")
    
    

    The Pattern: Use a Dictionary as a Dispatcher

    A cleaner approach is to use a dictionary as a “router” to map an event key to a specific handler function. This makes your handler readable and easy to extend.

    Python

    # event_handlers.py
    def handle_insert(record): print("Handling INSERT event...")
    def handle_modify(record): print("Handling MODIFY event...")
    def handle_unknown(record): print("Warning: Unknown event type.")
    
    # --- lambda_handler.py ---
    from event_handlers import handle_insert, handle_modify, handle_unknown
    
    EVENT_ROUTER = {
      'INSERT': handle_insert,
      'MODIFY': handle_modify,
    }
    
    
    def handle_records(records):
      for record in records
        event_name = record['eventName']        
        handler_func = EVENT_ROUTER.get(event_name, handle_unknown)
        handler_func(record)
    
    
    def lambda_handler(event, context):
      handle_records(event['Records']
      ...
    

    Adding support for DELETE events is now as simple as creating a handle_delete function and adding one line to the EVENT_ROUTER.

    A switchboard (AI generated, probably wrong lol) – routes the conversation to the intended recipients.

    Expanding the Pattern: Handling Logical Outcomes

    The dispatcher pattern isn’t limited to routing based on an event’s type. It’s an even more powerful tool for handling different outcomes from your business logic, such as success, validation errors, or downstream failures. This allows you to create clean, explicit paths for every possible result of an operation.

    The Scenario: A Payment Processing Function

    Let’s imagine a Lambda function that processes a payment. This single operation can have multiple distinct outcomes. A common but messy way to handle this is with a large if/elif/else block directly in the handler. This code can get hard to read and test because the business logic, error handling, and response formatting are all tightly coupled in one place.

    Dispatching Based on Status

    With the dispatcher pattern, we separate these concerns. The core logic function determines the outcome, and the handler dispatches that result to a dedicated function responsible for formatting the response.

    Step 1: Define Outcome-Specific Handlers

    First, create a separate handler for each possible outcome. Their only job is to create the final HTTP response.

    # outcome_handlers.py
    
    def handle_success(result: dict):
      """Handle successful payment."""
      print(f"SUCCESS: Payment processed for transaction ID {result['transactionId']}.")
      ... # code for handling success outcome
      return {"statusCode": 200, "body": "Payment successful"}
    
    def handle_validation_error(error_message: str):
      """Handle validation error."""
      print(f"VALIDATION_ERROR: {error_message}")
      ... # code for handling success outcome
      return {"statusCode": 400, "body": error_message}
    
    def handle_gateway_error(error_details: str):
      """Handle Gateway Error"""
      ... # code for handling error
      return {"statusCode": 502, "body": "Payment provider error"}
    
    # The router maps an outcome status to a handler function
    STATUS_ROUTER = {
      'SUCCESS': handle_success,
      'VALIDATION_ERROR': handle_validation_error,
      'GATEWAY_ERROR': handle_gateway_error,
    }
    
    

    Step 2: Define the Core Logic and the Dispatcher Handler

    Next, the process_payment function contains the business rules and uses early returns to exit as soon as a rule fails. The main lambda_handler calls this function and uses the STATUS_ROUTER to dispatch the result.

    # lambda_handler.py
    import json
    from outcome_handlers import STATUS_ROUTER
    
    def process_payment(request_body: dict) -> tuple[str, dict | str]:
      """
      Core business logic that returns a status and a result.
      It uses early returns to handle failures.
      """
      amount = request_body.get('amount')
        
      # Rule 1: Validate amount exists and is positive
      if not amount or not isinstance(amount, (int, float)) or amount <= 0:
        return ('VALIDATION_ERROR', "Amount must be a positive number.")
    
      card_token = request_body.get('card_token')
        
      # Rule 2: Validate card token exists
      if not card_token:
        return ('VALIDATION_ERROR', "Card token is required.")
    
      # --- All validation passed, proceed to core action ---
      print(f"Charging payment gateway ${amount}...")
        
      success = payment_gateway.charge(amount, card_token)
        
      if not success:
        return ('GATEWAY_ERROR', '...')
      return ('SUCCESS', {'transactionId': 'txn_12345'})
    
    
    def lambda_handler(event, context):
      """
      Main handler that dispatches work based on the outcome of the payment processing.
        """
      body = json.loads(event.get('body', '{}'))   
      status, result = process_payment(body)   
      handler_func = STATUS_ROUTER.get(status)
      return handler_func(result)
    
    

    Why This is Better

    This design is better as it provides clear separation of concerns:

    • Business Logic (process_payment): Knows how to validate and process a payment. It knows nothing about HTTP status codes or JSON response bodies.
    • Response Formatting (handle_* functions): Know how to create specific HTTP responses for different outcomes. They know nothing about business logic.
    • Orchestration (lambda_handler): Knows how to connect the two. Its only job is to call the logic and dispatch the result.

    3. Repository and DTOs for Consistent Data Handling

    The Anti-Pattern: Inconsistent Payloads and Duplicated Queries

    In a serverless system, lambdas communicate via message queues and shared databases. This can lead to data inconsistencies if not managed properly. This pattern uses two techniques to enforce data contracts: one for data moving between services (in-flight) and one for data in your database (at-rest).

    Use Data Transfer Objects (DTOs) for Message Payloads

    The Problem: JSON payloads sent between Lambdas have no enforced structure. If a producer Lambda changes a key name (userId to user_id), the consumer Lambda breaks at runtime.

    The Solution: Define a strict contract using a Data Transfer Object (DTO), implemented as a Python dataclass. This DTO lives in a shared library or Lambda Layer.

    • Producer: Creates a DTO instance and serializes it to JSON.
    • Consumer: Deserializes the JSON back into a DTO instance. This fails immediately if the structure is wrong.
    • Note: There can be multiple consumer and producer

    Python

    # shared/contracts.py
    from dataclasses import dataclass, asdict
    import json
    
    @dataclass
    class UserSignupDTO:
      user_id: str
      email_address: str
    
      def to_json(self): return json.dumps(asdict(self))
    
      @classmethod
      def from_json(cls, s: str): return cls(**json.loads(s))
    
    # In the consumer Lambda:
    # payload = UserSignupDTO.from_json(record['body'])
    # print(f"Processing user: {payload.email_address}")
    
    

    This approach prevents runtime errors from data mismatches, acts as self-documentation, and enables IDE autocompletion.


    Use the Repository Pattern for Database Access

    The Problem: If multiple Lambdas access the same database table, you get duplicated query logic (e.g., the same boto3 call in five functions). Changing the query means updating it everywhere.

    The Solution: Use the Repository Pattern. Create a single class (e.g., UserRepository) that contains all database access logic for that entity.

    • All database queries for a specific table are methods within this single class.
    • Lambdas call methods on the repository object instead of writing raw queries.

    Python

    # shared/database.py
    import boto3
    
    class UserRepository:
      def __init__(
        self, 
        table_name="Users",
        ddb=boto3.resource('dynamodb')
      ):
        self.table = ddb.Table(table_name)
    
      def get_by_id(self, user_id: str):
        response = self.table.get_item(Key={'userId': user_id})
        return response.get('Item')
    
    # In any Lambda function:
    # user_repo = UserRepository()
    # user = user_repo.get_by_id("user-123")
    
    

    This keeps your code DRY (Don’t Repeat Yourself), makes maintenance easy (change logic in one place), and abstracts the database details from your business logic.


    Design Pattern Provides A Blueprint For AI

    The great news is that we live in the age of Large Language Models (LLMs). These models understand design patterns and now that you understand why these patterns are important, you don’t have to implement them from scratch. You can use clever prompting to have an AI partner do the heavy lifting.

    More importantly, this method also prevents “AI code drift.” By consistently instructing an AI to use a specific pattern for a task—like always using the Repository Pattern for database access—you enforce architectural standards across your codebase. This ensures the code remains predictable and maintainable as the project evolves, regardless of who (or which agent/model) writes the prompt.

    Therefore, instead of asking “write me a lambda,” you can now ask:

    Prompt for DI: “Refactor this Python Lambda handler that uses dependency injection. Separate the core business logic from the handler and make the DynamoDB client an injectable dependency.”

    Prompt for Dispatcher: “Write a Python Lambda handler that uses the dispatcher pattern to process DynamoDB Stream events. It should have separate functions for ‘INSERT’ and ‘MODIFY’ events and use a dictionary to route them.”

    Prompt for Repository/DTO: “Generate a Python UserRepository class that uses Boto3 to interact with a DynamoDB table named ‘Users’. Also, create a UserDTO dataclass to represent the user payload.”

    Ultimately, understanding design patterns lets you write better prompts and critically evaluate the AI-generated code, making the AI a more effective tool.

  • MCP Version 2025-06-18 Changes: Confused No More!

    MCP Version 2025-06-18 Changes: Confused No More!

    Hey there! In the midst of the Juneteenth holiday break, the Model Context Protocol (MCP) didn’t slow down. In its latest 2025-06-18 specification, MCP introduced significant enhancements to bolster its security posture. I’m especially interested in how these updates directly addresses a long-standing OAuth vulnerability: the Confused Deputy problem.” Let’s dive in!

    The Confused Deputy Problem With MCP

    Working with AI agents that connect to various tools can bring new security challenges, particularly the “confused deputy” problem. This issue arises when a system, entrusted with certain permissions, is tricked into misusing that authority, often by directing an action to the wrong target. Here are the main ways this can manifest with MCP:

    Confused Deputy Scenario 1 (The “Wrong Legitimate Server” Mix-up):

    Your agent is a trusted assistant. It has permission to do things, like reading documents from Google Docs. A “confused deputy” happens when your agent tries to do something, but accidentally directs its action (and its granted permissions) to the wrong server, even if that server isn’t malicious.

    Example: Your company has two MCP servers that can read Google Docs:

    • Finance MCP Server: (https://finance.mycompany.com/mcp) – This server is meant for highly sensitive financial documents.
    • HR MCP Server: (https://hr.mycompany.com/mcp) – This server is meant for confidential HR documents.

    Both servers might offer a tool called “Google Doc Reader” with very similar descriptions. Your agent intends to read a sensitive financial report from Google Docs using the Finance MCP Server. However, due to a slight confusion (e.g., similar tool descriptions), your agent might mistakenly try to send the request (and its Google Docs access token) to the HR MCP Server. The HR server, though not malicious, is not authorized to see financial documents, creating a data leak or compliance issue.

    Confused Deputy Scenario 2 (The “Malicious Look-Alike Server” Trick):

    This scenario, highlighted in GitHub Issue #544, focuses on a more direct phishing attempt where a user is tricked into connecting to a malicious server from the start.

    Example: An attacker publishes a seemingly legitimate article or guide titled “MCP Configuration Best Practices from MyCompany Inc.” This guide subtly promotes configuring a malicious MCP server address (e.g., https://financc.mycompany.com/mcp – a typo, or https://mycompany-docs.net/mcp) in your MCP client application.

    • The Deception: The user, believing they are following official guidance, unknowingly configures their MCP client to use the malicious server’s address.
    • OAuth Flow Triggered: When the agent tries to perform its first action, the OAuth authorization flow begins. To the user, everything seems legitimate – the authorization prompts, the scopes requested – because the malicious server is designed to mimic the real one.
    • The Confusion (and Risk): Upon completing the authorization, your MCP client obtains an OAuth access token. The core of the confused deputy problem here is that the user, confused by the deception, has essentially granted legitimate authority (the OAuth token) to the wrong server. Your client then unknowingly sends this legitimate token to the attacker-controlled MCP server. Once the malicious server has your token, it can then use it to exfiltrate your sensitive data from Google Docs or other services your token has access to.

    How MCP Version 2025-06-18 Helps

    The 2025-06-18 MCP update brings about these changes to fight these problems:

    1. MCP Servers as OAuth Resource Servers

    What it means: MCP servers now function as “OAuth 2.0 Resource Servers.” This means their core responsibility is to validate the access tokens presented by MCP clients to determine if a request for a protected resource (like using a tool or accessing data) should be allowed. They are the guardians of their own services.

    How it helps (Exact Example of Validation): This makes the overall security setup much clearer and stronger. When an MCP server receives a request from an agent, it will perform critical checks on the access token provided in that request. Specifically, it will verify:

    • Signature: Is the token genuinely issued by a trusted Authorization Server and has it not been tampered with?
    • Expiration: Is the token still valid, or has its lifespan expired?
    • Issuer (iss claim): Was the token issued by an Authorization Server that this specific MCP server trusts?
    • Audience (aud claim): Was the token explicitly intended for this specific MCP server? (This is where RFC 8707’s resource parameter comes into play, as detailed below.)
    • Scope (scope claim): Does the token grant the necessary permissions (e.g., read:document, write:database, summarize:report) for the particular action the agent is trying to perform on this server?

    By performing these precise validations, the MCP server ensures that only genuinely authorized agents, with tokens specifically issued for it and with the correct permissions, can access its protected tools and data. This dramatically enhances security by ensuring every interaction is rigorously checked against industry-standard security rules.

    2. MCP Client to Indicate Resource (Using RFC 8707)

    What it means: When your MCP agent asks for permission (an access token) to use a tool on an MCP server, it now must explicitly tell the permission provider (Authorization Server) exactly which resource (MCP server) it plans to talk to.

    How it helps (Directly addresses the “Wrong Server” / Prompt injection Mix-up):

    • Let’s go back to our example. When your agent wants to read a financial document, it asks for a Google Docs access token, but specifically tells the system: “This token is for the Finance MCP Server (https://finance.mycompany.com/mcp) only.”
    • The token then gets a special “audience” tag saying it’s only for finance.mycompany.com/mcp.
    • If your agent then gets confused and accidentally tries to use this token with the HR MCP Server (https://hr.mycompany.com/mcp), the HR server (which also follows these new rules) will check the token. It will see that the token is not meant for it, and reject the request.
    • This prevents the HR server from ever seeing your sensitive financial documents, even if your agent made a mistake in routing.

    Example: Resource Server Payload and Token Parameters

    To make this more concrete, let’s look at how the resource parameter is used in a client’s request and how the aud (audience) claim appears in the access token that the Resource Server (your MCP server) then receives and validates.

    The Client’s Request (Client asking for a Token)

    When your MCP agent needs an access token to interact with, say, the Finance MCP Server, it makes a request to the Authorization Server. This request will include the resource parameter, as mandated by RFC 8707:

    HTTP

    GET /authorize?
      response_type=code
      &client_id=your_mcp_client_id
      &scope=read:document
      &resource=https://finance.mycompany.com/mcp  <-- THIS IS THE KEY PART
      &redirect_uri=https://your_mcp_client/callback
    
    

    (Note: This is a simplified authorization request. A full flow involves exchanging an authorization code for a token.)

    Here, the resource parameter explicitly tells the Authorization Server: “I need a token specifically for the resource located at https://finance.mycompany.com/mcp.” A malicious server “https://finance.mycoy.com/mcp” trying to impersonate https://finance.mycompany.com/mcp, wont be able to request for a token to the resource located at

    The Access Token Payload (What the MCP Server Receives)

    If the Authorization Server supports RFC 8707, it will issue a JSON Web Token (JWT) as an access token. This token will contain an aud (audience) claim in its payload, identifying its intended recipient.

    The payload of such an access token (after decoding, as tokens are usually Base64 encoded) would look something like this:

    JSON

    {
      "iss": "https://auth.mycompany.com",          // Issuer (the Authorization Server)
      "sub": "user_id_12345",                       // Subject (the user or client using the token)
      "aud": "https://finance.mycompany.com/mcp",   // Audience: THIS MUST MATCH THE RESOURCE SERVER
      "exp": 1717603200,                            // Expiration Time
      "iat": 1717602900,                            // Issued At Time
      "scope": "read:document"                      // Permissions granted
      // ... other claims
    }
    
    

    The Resource Server’s Validation (What your MCP Server Does)

    When the Finance MCP Server (https://finance.mycompany.com/mcp) receives this access token, it performs critical validation steps. As an OAuth Resource Server (and specifically following MCP’s requirements), it must check the aud claim in the token’s payload.

    • If aud is https://finance.mycompany.com/mcp: The token is for this server. The server can proceed to process the request (assuming other validations like signature, expiration, etc., also pass).
    • If aud is https://hr.mycompany.com/mcp (or anything else): The token is not for this server. The Finance MCP Server will reject the request, typically with an “Unauthorized” (401) error, because the token’s audience does not match its own identifier.

    This mechanism is what directly prevents the “confused deputy” problem we discussed, ensuring that even if an agent mistakenly tries to send a token to the wrong server, that server will identify that the token isn’t intended for it and deny access.

    Note-Worthy: Server-Side Elicitation

    The MCP Version 2025-06018 also included specs for a new MCP capability where servers can initiate requests for more information or to confirm actions during a task.

    What it’s for: While it doesn’t directly solve the confused deputy problem, this allows MCP servers to dynamically clarify ambiguous requests or get your explicit consent before performing critical, sensitive, or ambiguous actions. It can, therefore, act as a crucial safety net by ensuring your actual intent aligns with the agent’s proposed action, providing a vital “human-in-the-loop” checkpoint.

    Important Considerations:

    Github discussion emphasizing that no sensitive information should be sent via elicitation
    • NEVER Send Sensitive Info Directly: It’s vital that users never send sensitive data (like passwords, credit card numbers, or PII) through an elicitation prompt. This information is likely to be logged, creating a major security risk. The MCP specification strictly prohibits servers from requesting such data via elicitation.
    • Not for Authentication: Elicitation is not a way for servers to ask for your login credentials. Authentication is handled securely and separately by trusted Identity Providers (IdPs). For scenarios requiring authentication or increased permissions, elicitation might trigger a redirect to a secure browser-based flow (as detailed in the upcoming GitHub Pull Request #475), ensuring sensitive login data never directly passes through the MCP channel.

    Final Notes

    Broader Security & Community Notes

    The MCP update also clarifies general security rules and offers new best practices to help developers build safer MCP systems. (See https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices)

    In terms of adoption of 2025-06-18 MCP spec, as of Jun 22nd, MR for these changes are made but remain Open for the official SDKs. VSCode is reportedly looking into it. at https://github.com/microsoft/vscode/issues/248418.

    It’s also worth noting that, 2025-06-18 MCP specification isn’t publicly mentioned in the cline GitHub repository or in the Cursor forum.

    Your Role in Keeping Things Secure

    While these new MCP features are powerful, your carefulness is still crucial. No technology can completely replace user vigilance.

    Even with these updates, the “confused deputy” problem can still arise if you unknowingly make the initial connection to an MCP server that is truly malicious or an unintended target. The protocol’s security features, like RFC 8707, are designed to prevent the misuse of tokens after they are issued for a specific resource. However, if a user is tricked (e.g., through a convincing phishing attack that directs them to configure a malicious look-alike server address), they might legitimately authorize the wrong server from the outset. This is why:

    • Choose Trusted Servers: Always use MCP servers from reputable sources and meticulously verify their exact URLs.
    • Be Aware: Understand what your agent is doing and what permissions it has.
    • Review Requests: Pay close attention to any questions or confirmations your agent asks you through elicitation, especially for sensitive actions.

    In short, MCP gives you stronger tools, but using them safely means staying aware and making smart choices about who you let your agent interact with.

    Have feedback or want to discuss your experience with MCP 2025-06-18? Leave a comment or reach out!

  • MCP is not a fad, it is certain to happen

    MCP is not a fad, it is certain to happen

    Imagine you are building a factory to manufacture cars. You need complex, specialized components like the engine, or parts that require a multi-step process to manufacture, like the tyres.

    If the engine supplier were to simply dump a pallet of raw, unassembled parts on your factory floor, your car assembly line would have to stop while your workers frantically tried to figure out how to build the engine, test it, and prepare it for installation. Your factory would have to become an expert in engine assembly, a job it was never designed to do.

    Similarly, tyre manufacturing is a multistep process requiring high heat for vulcanization. That’s an environment you won’t want to accommodate in your factory space.

    In both cases, your factory is forced to do the hard, specialized work of preparing raw materials. You will not be efficient.

    AI Applications – An Assembly Of Model, Tools, and Data

    Building an AI application is much like designing a modern assembly line. In both cases, you assemble different components to generate your ultimate product. For today’s AI, this means integrating three key components: the core model (like an LLM), a set of external tools, and a constant flow of data.

    To make this assembly truly functional, models are now trained in “tool calling.” This technique allows the LLM to overcome its static knowledge by recognizing when a query requires help from an external tool—like an API or a database. The model learns to call the necessary tool and integrate its response, transforming itself from a simple text generator into a dynamic agent that can act on live data.

    Agent and the Brain

    However, this powerful feature creates a significant, hidden burden for the developer. While the AI calls the tool, the developer is responsible for building and maintaining the entire environment for it. For every single tool, they must manage its specific dependencies, understand its unique authentication and data formats, and write brittle “glue code” to make it compatible with the main application. This is like designing a chaotic assembly line where every machine needs a different power outlet, its own specialized mechanic, and a unique instruction manual. It is highly inefficient and bloats the core application with third-party complexity.

    This is precisely the problem the Model Context Protocol (MCP) is designed to solve.

    MCP: Solving Complexity Through Standardization and Compartmentalization

    The Model Context Protocol (MCP) solves this problem by simultaneously introduces two powerful concepts: a universal standard for integration—the equivalent of a universal utility port for every workstation (usb c for AI apps) —and a decoupled architecture where each tool operates in its own self-contained environment. This combination of a standard interface and a compartmentalized structure is what provides the key benefits.

    1. Simplified Integration Through Standardization

    First, MCP provides a common language for how an AI application communicates with any tool. While API specifications have existed before, MCP standardizes the layer above that: the protocol and context an AI agent needs to reliably call a tool, understand its capabilities, and use its output. This dramatically simplifies the initial work of integrating a new tool into the assembly line.

    2. Independent Evolution Through Compartmentalization

    Second, and arguably more powerful, is how MCP forces a clean separation between the application and the tool (or prompt, or resources). An MCP Server is a self-contained application. This means the AI application doesn’t need to know anything about the tool’s internal environment, its programming language, or its software dependencies. All of that complexity is managed entirely within the MCP Server.

    Hidden Complexity

    This creates a clear and powerful division of labor, which brings us back to the assembly line. The engine manufacturer can completely re-design their own factory—using new machines, new software, and new processes. But as long as the final engine they ship has the same standard mounting points and data connectors, your car factory’s assembly line doesn’t need to change at all. The engine can evolve independently.

    Similarly, a tool provider can completely update their service and its dependencies within their own MCP Server. As long as the MCP interface remains consistent, the AI application that calls it requires no modification. This decoupling is what allows for a truly scalable and maintainable ecosystem, where developers can build complex applications by assembling robust, independent components without inheriting their internal complexity.

    MCP is not a fad, it is certain to happen

    This brings us to a concluding thought, one that could serve as the title for this entire post: The Model Context Protocol is not a temporary fad; it is an evolutionary concept for building complex systems.

    The reason for this is simple: if MCP didn’t exist, something like it would have to be invented. The principles it embodies—compartmentalization and specialization—are fundamental to solving complexity. We see this pattern repeated everywhere, in both natural and man-made systems.

    Complex systems – Life, Software, Trade

    We see this most profoundly in biology, a system that has been self-optimizing for millions of years. Evolution itself discovered that the most robust path to creating complex organisms was through compartmentalization: specialized cells form tissues, and tissues form organs. Each component hides its immense internal complexity, communicating and collaborating through standardized biological and chemical signals.

    We see it in modern software architecture, where developers have moved from monolithic applications to microservices—small, independent services that communicate through standardized APIs, allowing each one to evolve without breaking the entire system. We even see it in global trade with the invention of the simple shipping container. Before this standard interface, logistics were a nightmare of custom work. The container allowed the entire global system of ships, cranes, and trucks to specialize and scale.

    In every case, a standard interface enables a clear division of labor, allowing a system to grow in sophistication without collapsing under its own weight.

    MCP is the application of this universal, time-tested principle to the assembly line of AI. This is not theoretical. The value of this compartmentalized approach is why major industry players like Google, Microsoft, OpenAI, and Anthropic are rapidly adopting MCP.

    Credits: https://x.com/sundarpichai/status/1906484930957193255

    All That Glitter Is Not Gold

    While the Model Context Protocol (MCP) presents a compelling vision, its initial design appears to prioritize functionality at the expense of a robust security framework. The current specifications leave key implementation details ambiguous, such as mandating OAuth 2.1 for authorization—a protocol that has yet to see wide industry adoption. Furthermore, researchers have identified critical risks, including prompt injection, the potential exposure of credentials from MCP servers, and supply chain attacks via malicious third-party tools. As the ecosystem evolves to mitigate these threats, security must remain a paramount consideration for developers adopting the protocol.

    P.S – This is a high level opinion on MCP. Stay tuned for future articles with actual technical examples and description!