In our Culture and Methodologies category, dive into Agile, career development, team management, and methodologies such as Waterfall, Lean, and Kanban. Whether you're looking for tips on how to integrate Scrum theory into your team's Agile practices or you need help prepping for your next interview, our resources can help set you up for success.
The Agile methodology is a project management approach that breaks larger projects into several phases. It is a process of planning, executing, and evaluating with stakeholders. Our resources provide information on processes and tools, documentation, customer collaboration, and adjustments to make when planning meetings.
There are several paths to starting a career in software development, including the more non-traditional routes that are now more accessible than ever. Whether you're interested in front-end, back-end, or full-stack development, we offer more than 10,000 resources that can help you grow your current career or *develop* a new one.
Agile, Waterfall, and Lean are just a few of the project-centric methodologies for software development that you'll find in this Zone. Whether your team is focused on goals like achieving greater speed, having well-defined project scopes, or using fewer resources, the approach you adopt will offer clear guidelines to help structure your team's work. In this Zone, you'll find resources on user stories, implementation examples, and more to help you decide which methodology is the best fit and apply it in your development practices.
Development team management involves a combination of technical leadership, project management, and the ability to grow and nurture a team. These skills have never been more important, especially with the rise of remote work both across industries and around the world. The ability to delegate decision-making is key to team engagement. Review our inventory of tutorials, interviews, and first-hand accounts of improving the team dynamic.
The Jobs Matrix for AI: Four Boxes Instead of 40 Tools
Why Your GPU Fleet Is Both Full and Idle: Building a Capacity Orchestration Layer
Software organizations require rules to ensure predictability, knowledge sharing, and sound technical decisions. However, excessive rules, processes, and standards can obstruct progress if they eclipse desired outcomes. This longstanding tension in software engineering is now more pronounced as AI accelerates code generation, solution exploration, and implementation of change. The main challenge is not the number of rules, but how they fit with context, autonomy, and responsibility. Some organizations have minimal constraints, others tightly control decisions, and a few adapt principles to specific situations. Noticing these models helps engineering leaders assess their organization, understand associated risks, and move toward more effective technical governance. Software Architecture Is Also an Organizational Problem Software architecture is often defined by technical elements such as components, APIs, databases, infrastructure, quality attributes, and architectural styles. While these matter, they exist within a wider context. People design and maintain software, influenced by business constraints, communication structures, incentives, policies, and authority levels. Architecture is not only a technical artifact; it also reflects the environment in which teams make technical decisions. This broader perspective reflects the socio-technical nature of software engineering and is consistent with standards such as ISO/IEC/IEEE 42010, which recognize that architectural concerns encompass organizational, economic, regulatory, and community factors. Conway's Law shows that organizational communication structures often mirror system design. If organizational structure shapes software, then the distribution of authority, management of disagreement, response to failure, and governance of technical decisions likewise influence architecture. Therefore, engineering culture and governance are architectural concerns, not just management issues. Culture Shapes How Engineering Decisions Are Made If architecture is formed by its organizational context, organizational culture also directly influences engineers’ daily decisions. Sociologist Ron Westrum offers a system for understanding how organizations respond to problems and opportunities, particularly in high-risk environments. His model focuses on information flow, including cooperation, safe communication of bad news, responsibility management, failure investigation, and acceptance of new ideas. Westrum identified three main cultural patterns: pathological (power-oriented), bureaucratic (rule-oriented), and generative (performance-oriented) organizations. This distinction matters in software engineering, where architectural decisions depend on information moving across teams, technical boundaries, and leadership levels. Organizations that conceal problems, discourage disagreement, or fragment responsibility make different technical choices than those that discuss risks openly, encourage collaboration, and treat failures as chances to learn. DORA adopted Westrum's model in its research and consistently links generative, high-trust cultures to stronger software delivery and organizational performance. Culture isn't just the environment around engineering work; it shapes how teams interpret technical information, who can challenge decisions, how rules are enforced, and how architecture evolves. PathologicalBureaucraticGenerativePower orientedRule orientedPerformance orientedLow cooperationModest cooperationHigh cooperationMessengers “shot”Messengers neglectedMessengers trainedResponsibilities shirkedNarrow responsibilitiesRisks are sharedBridging discouragedBridging toleratedBridging encouragedFailure leads to scapegoatingFailure leads to justiceFailure leads to inquiryNovelty crushedNovelty leads to problemsNovelty implemented Mode 1: Wild West Engineering Wild West Engineering refers to environments with weak governance, unclear ownership, and few shared engineering principles. Teams may appear highly autonomous, but such autonomy lacks architectural direction, reliable feedback, and consistent accountability. Technical decisions are frequently reactive and localized, with tools and approaches selected for immediate needs or personal preference rather than the wider system context. The problem is not simply a lack of rules. Experienced teams can operate effectively with minimal rules if they share strong principles and understand the impact of their decisions. Wild West Engineering occurs when autonomy exists without sufficient shared context, accountability, or coordination. Over time, architectural knowledge becomes siloed, similar problems are solved inconsistently, and changes become riskier as no one fully understands the system. Westrum’s pathological culture is a relevant comparison: cooperation is limited, responsibilities are avoided, bad news is suppressed, and failures result in blame rather than inquiry. In software organizations, this often creates a hero culture where a few engineers become indispensable for managing disorder. AI can worsen this problem; when teams generate code, add dependencies, or make architectural decisions without shared guardrails, inconsistency and technical debt can spread quickly. Indicators Typical signs involve unclear ownership, multiple solutions for similar problems, undocumented architectural decisions, reliance on tribal knowledge, fear of changing legacy components, recurring blame after incidents, and dependence on a few key engineers. Another important sign is resignation: engineers stop proposing improvements because changing the system seems riskier than leaving existing problems unresolved. How to Move Forward Imposing heavy bureaucracy is not the solution to chaos. The first step is to establish minimum shared guardrails: clarify ownership, document key architectural decisions, define core engineering principles, set baseline expectations for security and operability, and create safer ways to discuss failures and technical debt. The goal is to support autonomy with enough structure to ensure local decisions conform to a coherent system. Mode 2: Bureaucratic Engineering Bureaucratic Engineering represents the opposite extreme, where governance is pervasive. Standards, approval processes, architectural reviews, mandatory technologies, and detailed procedures dictate how teams operate. While this structure creates predictability and consistency, problems arise when adherence to process outweighs knowing its purpose. Teams may focus on compliance rather than evaluating whether rules remain relevant. Westrum’s bureaucratic culture reflects similar traits: limited cooperation, narrow responsibilities, minimal cross-boundary collaboration, and a tendency to view novelty as problematic. In engineering, this occurs when architectural decisions are centralized, outdated standards persist, and exceptions are hard to secure. While these mechanisms may guarantee a baseline of quality, they may also limit performance. The core issue is not the presence of rules, but the erosion of judgment. Governance becomes rigid when organizations treat principles as permanent mandates and compliance as a substitute for engineering quality. AI can increase this tension, as organizations may respond to new risks with broad restrictions, approved-tool lists, or complex authorization processes. Although these measures may reduce risk, they can also hinder teams from pursuing valuable opportunities, making the organization safer but less nimble and flexible. Indicators Typical signs include rules missing explicit justification, standards defended only by “we have always done it this way,” centralized approval for minor technical decisions, difficulty obtaining exceptions, and architectural decisions that ignore local context. Status quo bias helps explain this persistence. Samuelson and Zeckhauser found that people often prefer existing or default options. In engineering organizations, this reinforces architectural inertia: once a technology, methodology, or rule becomes standard, replacing it requires far more justification than maintaining it. As a result, rules persist not because they are optimal, but because they are easier to retain. Organizational silence is another key warning sign. Morrison and Milliken define this as a collective tendency to withhold concerns when employees believe speaking up is unwise or ineffective. In engineering, this occurs when developers privately disagree with decisions but remain silent in meetings, believing that contesting established processes will not lead to change. Persistent silence should not be mistaken for agreement; it may signal that the organization discourages dissent. Another warning sign is when success is measured mainly by process compliance rather than software outcomes. Teams may meet all requirements yet become slower, less innovative, and disconnected from the initial intent of these controls. How to Move Forward To move beyond Bureaucratic Engineering, organizations should redefine governance rather than eliminate it. Leaders must frequently review the purpose of rules, distinguish primary constraints from preferences, and replace unnecessary approvals with clear principles. Teams need defined boundaries, authority to make decisions within them, and a clear process for exceptions when justified by context. A practical test is to ask whether a rule is still justified by its intended outcome. If no one can explain the risk it addresses, the value it protects, or evidence of its need, the organization may be maintaining the process for its own sake. The aim is to shift from prescriptive control to outcomes-based control, with accountability and feedback. Mode 3: Context-Driven Engineering Context-Driven Engineering integrates governance with local judgment. Rules are tools for managing risk and ensuring consistency, not ends in themselves. Teams work within defined principles, constraints, and responsibilities, while retaining the autonomy to adapt decisions to their particular technical and commercial context. The core assumption is that the same rule may yield different outcomes in different environments. This approach corresponds with Westrum’s generative culture, defined by high cooperation, shared risk, cross-boundary collaboration, inquiry following failures, and openness to innovation. In engineering, teams are expected to follow standards, understand their purpose, and recognize when exceptions are warranted. Architectural decisions are treated as explicit trade-offs, not automatic pattern applications. Teams evaluate technologies such as microservices, hexagonal architecture, event-driven systems, or specific cloud platforms based on the problem, constraints, and desired outcomes, rather than assuming they are universally correct. Applying a rule in the wrong context can lead to poor decisions. The same applies to architectural patterns: they address recurring problems in specific contexts, and applying them without considering context can create anti-patterns. For example, a lifeboat is essential in the ocean but irrelevant in a desert. Similarly, providing water is life-saving for someone dehydrated, but ineffective for someone drowning. A tool or rule's effectiveness depends on the context in which it is used. This environment requires greater maturity, not reduced governance. Autonomy must be balanced with accountability, feedback, and a readiness to revisit decisions as new information arises. AI fits well with this model, as its use can be governed based on context and risk: low-risk tasks may permit broad experimentation, while sensitive data, production changes, or high-impact decisions demand stricter controls. The objective is responsible judgment within clear boundaries, not unrestricted freedom. Indicators Key indicators include clear engineering principles, documented decision rationales, explicit ownership, and constructive disagreement. Teams should be able to explain both the rule and its rationale. Exceptions are allowed but must be justified. Failures prompt learning rather than blame, and architecture teams periodically review standards instead of letting them become outdated. Another strong indicator is that teams can contest established practices without equating disagreement with disloyalty or process violations. A context-driven organization distinguishes between non-negotiable constraints and situation-dependent choices. Security, legal requirements, data protection, and regulatory obligations remain strict, while implementation details such as architectural style, framework selection, or deployment strategy are evaluated locally. This distinction ensures autonomy does not devolve into unstructured or uncontrolled practices. How to Sustain This Model The main risk is regression. Insufficient discipline can lead to disorder, whereas excessive concern for consistency can create bureaucracy. Upkeeping this model requires ongoing review of principles, active feedback loops, documented architectural decisions, clear exception processes, and leaders willing to reexamine their assumptions. A practical test is whether the organization can answer four questions: What problem are we solving? Which constraints are truly non-negotiable? What trade-off are we accepting? What evidence would prompt us to revisit the decision? When teams can answer these questions consistently, governance functions as a learning system rather than a control mechanism. Conclusion Three Engineering Governance models inspired by Ron Westrum Engineering organizations improve not by simply adding or removing rules, but by ensuring teams understand their purpose, have the required context, and are trusted to exercise judgment within clear boundaries. Wild West Engineering lacks the structure for long-term growth, while Bureaucratic Engineering frequently leads to rigidity and limits learning. Context-Driven Engineering seeks balance through prioritizing principles over prescriptions, fostering autonomy with accountability, and encouraging decisions based on trade-offs rather than routine. As AI accelerates code generation and change, maintaining this harmony is increasingly important. The most adaptable organizations will foster responsible, context-aware decision-making.
Development in small companies and startups often slows down at the boundaries between people and teams. A developer waits for a product decision. Frontend work stalls because the API response is unclear. QA discovers that two teams interpreted the same requirement differently. Three practical habits can reduce these delays: document the feature, discuss its technical risks, and agree on interface contracts before dependent implementations diverge. Keep each step proportional to the size and uncertainty of the task. Start With the Biggest Uncertainty Before distributing work, identify what the team needs to learn first. Unclear user experience? Start with a UI prototype. Frontend developers can use mocks to explore the flow while the team clarifies requirements.Uncertain business logic, performance, or integration? Start with a focused backend investigation or technical prototype.Unclear user problem or business value? Start with product discovery, involving users, product stakeholders, and technical specialists as needed. Having a UI does not automatically make frontend work the first priority. Choose the starting point that resolves the most important uncertainty, then agree on enough shared detail to let work proceed in parallel. 1. Maintain Feature Documentation and Define Responsibilities A concise Product Requirements Document (PRD) gives the team a shared explanation of what to build, why it matters, and how to evaluate it. For a small feature, a short page may be enough. QuestionWhat to recordWhat are we building?The intended behavior, MVP scope, and explicit exclusions.Who is it for?The users and the problem they face.Why is it needed?The expected benefit to users and the business.How will we evaluate it?Acceptance criteria, success metrics, a baseline or comparison group, and a measurement period. Name the person responsible for product decisions and identify the people needed to implement and validate the feature. These are responsibilities; a small team may combine several of them in one role. ResponsibilityTypical ownerSet goals, decide scope, and resolve product questionsProduct ownerAssess feasibility, design interfaces, and implement the featureDevelopers and technical leadDefine test scenarios and verify behaviorQA and developersDesign the user experienceDesigner, where neededDefine instrumentation and evaluate impactAnalyst or another explicitly assigned team member Example: A Post Recommendation System The following is an illustrative PRD. Its numerical targets are examples to agree on for a particular product, rather than measured results or universal benchmarks. SectionExampleProblem and hypothesisUsers may struggle to find relevant posts in a chronological feed. We expect recommendations based on their interests to improve content discovery.UsersReaders discovering posts; creators whose eligible posts can be recommended.MVP behaviorReturn up to 20 eligible posts, ranked by popularity within topics inferred from likes and subscriptions. Recompute lists every 24 hours. Exclude deleted posts and posts the requesting user cannot access when serving the response. Use a general popularity list when there is insufficient interest history.Experiment supportAssign eligible users to stable control and treatment groups and record recommendation impressions and clicks. Include this in the MVP so the first version can be evaluated.Out of scopeML models, similarity between users, and advertising recommendations. Decide whether to add these after evaluating the MVP.PerformanceIllustrative target: server-side p95 response time below 200 ms for serving precomputed lists at 1,000 requests per second on a representative test dataset. Measure the batch recomputation job separately and require it to finish within the 24-hour refresh window.Acceptance criteriaTests verify ranking on a fixed dataset, the popularity fallback, access filtering, and feedback recording. Likes and subscription changes affect the next scheduled recomputation. Load tests meet the stated latency target.Success measurementIllustrative primary target: a 10% relative increase in recommendation CTR versus the control group. Define CTR as clicks divided by recorded impressions. Plan an initial two-week experiment; estimate sample size before launch and report uncertainty if the result is inconclusive. Monitor seven-day retention and API error rate as guardrails, with acceptable thresholds agreed before launch.RisksWeak relevance, overexposure of already popular posts, and load spikes. Inspect recommendation diversity, provide a fallback, and test capacity before rollout. Technical notes can accompany the PRD, with implementation decisions reviewed during technical refinement. For example, a Go service could use PostgreSQL for source data and Redis for precomputed lists. A draft API might expose GET /recommendations for the authenticated user and POST /feedback for interactions. The team still needs to define schemas, error responses, authorization behavior, and pagination before implementation. Keep release acceptance separate from business success. A correctly implemented feature can fail to improve the chosen metric. That result should inform the next product decision. What if the Product Has No Documentation? Start with the workflow you are changing and the business rules most likely to be misunderstood. Link the relevant code, record open questions, and expand the documentation as the team learns. Documenting the entire system does not need to become a prerequisite for the next useful change. After release, compare outcomes with the original hypothesis. A weak result is a reason to investigate the feature, measurement, and assumptions. Some work provides value through reliability, lower operating costs, or reduced risk, and its evaluation should reflect that purpose. 2. Conduct Technical Refinement Technical refinement, sometimes called technical grooming, connects product expectations with implementation decisions. Its output should be a workable approach and a clear record of remaining questions. Ask a developer or technical lead to review the PRD and identify: Existing constraints and integration dependencies.Performance, security, and operational risks relevant to the change.Decisions that require input from product or another team.Unknowns that need a short investigation or prototype. Bring the relevant people together to resolve those questions. If the implementation cost changes the original assumptions, revisit the scope with the product owner. Keep the Process Proportional Agree on a timebox based on scope and uncertainty. For example, a modest feature might receive two days of initial technical analysis, with a named owner and deadline for each product question. A complex migration may require several investigations. These are planning choices to review as new information appears. Record decisions, tradeoffs, unresolved questions, and owners. There is usually no need to transcribe every discussion. Finish with a small implementation plan: what can run in parallel, what must happen first, and what evidence will show that a risky assumption holds. This process can reduce avoidable rework. It does not guarantee that every issue will be discovered in advance, so leave room to revise the plan during development. 3. Develop Using the Specification-First Principle For work that crosses an API boundary, agree on the contract early. An OpenAPI description can capture HTTP operations, request and response schemas, and other interface details. The contract should also be supported by examples and documented behavior where a schema alone is insufficient. Backend and frontend engineers review the contract together, including empty states, errors, and compatibility expectations.Frontend developers build against mocks that reflect the agreed contract.Backend developers implement the API and verify that its behavior matches the contract.QA and developers prepare API contract checks and derive end-to-end scenarios from the PRD, user flows, and business rules. API contract checks and end-to-end tests serve different purposes. Matching a response schema does not establish that a complete user journey works correctly. How This Reduces Waiting Consider an illustrative recommendation feature. If frontend developers invent a response shape while waiting for the backend, they may need to rewrite rendering and error handling during integration. Agreeing on the response schema, empty-list behavior, and refresh semantics first lets both sides work against the same assumptions. Mocks still need to match the implementation. Run compatibility checks in CI and update the specification, mocks, and tests together when the contract changes. Integrate early enough to expose mismatches before release. Specification-first development also leaves room for exploratory code. A short prototype may be necessary to discover whether a proposed interface is feasible. The aim is to agree on the contract before teams invest heavily in dependent implementations. For more on the approach, see Boost Efficiency With the Specification-First Principle. Keep Documentation Useful as the Project Evolves Documentation helps future team members understand behavior and the reasons behind earlier decisions. DORA's research on documentation quality links high-quality internal documentation with organizational performance and finds that it strengthens the impact of technical practices. Keep a small set of maintained resources close to the work: The PRD and the results of the feature's evaluation.Architecture decisions, API contracts, and relevant data models.Deployment and recovery instructions.Links to changes and the decisions behind them, in GitLab, a README, or another shared system.Test scenarios and acceptance checklists. Assign ownership and update these resources when behavior changes. Outdated documentation can mislead the next person just as missing documentation can leave them guessing. Conclusion These three habits address common sources of delay: Concise feature documentation gives the team a shared goal, scope, and definition of success.Technical refinement surfaces constraints and assigns owners to unresolved questions.Agreed API contracts support parallel development and reduce integration rework. Try them on one feature. Track time spent waiting for decisions, integration rework, and time from an agreed scope to release, alongside quality and product outcomes. Use what you learn to adjust the process. The practical goal is a team that can make decisions, build, and validate changes with less avoidable waiting.
TL;DR: The AI Workflow Inventory Finalizes the A3 Delegation System You probably know your own AI shortcuts: the colleague who drafts the stakeholder update, the nightly job somebody set up before they left, the interview notes that go through a model on demand. However, I want to challenge you: that perceived knowledge can create false confidence, because it feels like knowing the team’s way of working with AI, when it is still only a diffuse understanding of the practice. Ask the team to combine those individual accounts into one list, and you may discover how little of the whole anyone can see. The AI Workflow Inventory is the artifact for that list: one row per recurring task, refined into provisional task classes to prepare the team’s next AI delegation decisions. Disclaimer: I read Charniak/McDermott’s book on “Artificial Intelligence” decades ago; of course, I use AI for research, translation, proofreading, challenging story arcs and article structures, and summarization. It is a production tool, not a substitute for thinking. Your Own Shortcuts Are the Easy Part When I published the AI Delegation Lifecycle in June 2026, the point before stage 1 was deliberately left without an artifact: the team was supposed to know which work it does with a model, at what frequency, and at what stakes, which I call a forensic analysis of your own workflow. However, as I quickly discovered, this assumption that a team could produce the analysis from memory was wrong, and updating the student reference for the A3 Delegation System earlier this month showed me its design flaw: The Routing Policy assigns an AI model tier per task class,The AI Definition of Done is written per task class (never per task), andThe Delegation Audit opens with a walkthrough of what the team has handed over. All three artifacts consume task classes, but none of the six stages of the A3 Delegation System produces them. The AI Working Agreement is the exception on the other side of the lifecycle: it records the team’s defaults and boundaries and can exist before the inventory is complete. The gap is easy to miss from the inside, because every individual on the team can answer for their own use, and nobody can answer for the sum: who else drafts with a model, which outputs leave the team that way, and which jobs run unattended. That sum, useful, undocumented, and unowned, is what I call “AI Debt,” and it works right up to the moment someone asks who is responsible for it. What One Row Looks Like in the AI Workflow Inventory The AI Workflow Inventory is a one-page canvas with an identity strip (team, owner, date, review date), a panel on where to look, a panel with five refinement rules, and the inventory table itself. Let me show you an example from a Scrum team: Number: I-01Task: Transcribe photos of Retrospective sticky notesProcess: Retrospective facilitationTask class: Transcriptions for internal useHow often: Per SprintWho runs it: Scrum MasterTool: GPT-5.6 SolOutput goes to: Stays in the teamData that enters the model: Photos of handwritten notes and names. The task is written as verb and object, the class is named by output and audience, and “output goes to” has three values: stays in the team, leaves the team, or customer-facing. The number travels to the Workflow Card later, so the row and the respective decision can be matched months apart. Two design choices carry most of the weight: The person and the tool sit in separate columns because people change and tools change while the task stays; a field that says “Anna, ChatGPT” makes it harder to update either one when Anna moves teams or the license changes.The last two fields ask different questions that teams routinely conflate: where the output goes tells you about the stakes on the way out, whereas what enters the model tells you about the exposure on the way in, and an output that “stays in the team” can still have been produced from photographs with names on them. The entry in the data field is a fact to review; it is not permission to keep uploading the names. What the canvas does not have is a life cycle stage or status column, and the omission is deliberate. The inventory records what happens today; the poster and the Workflow Cards show where a workflow stands in the A3 Delegation Life Cycle, and a retired workflow is a row in the Re-classification Log, identified by the same inventory number. A stage column would turn the pre-decision list into a second, competing status board, and agile practitioners already know how that ends from the Product Backlog in Jira and the “real” one in a spreadsheet debate. The AI Workflow Inventory takes work to maintain, but it reduces repeated documentation and decision-making: The team captures each recurring task once.Compatible tasks share one review standard and one routing decision instead of getting one each.The inventory number connects a task to every later decision about it.The life cycle status stays on the poster and in the records that already exist. Update the inventory when the work changes or when the team discovers, corrects, or refines what it has recorded. (I know, it sounds like a database schema, and I started preliminary work on how to design an application for the A3 Delegation System.) Where to Look The canvas names three sources for AI workflows, and the 30-day window matters because memory is a poor inventory tool, while last month’s outputs are evidence: Your meetings come first: which outputs of the events that repeat (planning, pipeline or campaign reviews, stand-ups, monthly reporting, Retrospectives, etc.) did a model draft, summarize, transcribe, or translate?Then your tools: each colleague looks at their own chat history from the last 30 days and brings the prompts, applied Skills, or agent tasks that come back every week or every Sprint, who wrote them, and how often they were rewritten; this is a self-report, and it has to be, since nobody should be reading anyone else’s conversations.Then your outbox: the emails, reports, tickets, and documents that left the team, and the question of which of them started as a model draft. Thirty days is the starting window. Once the obvious rows are on the page, ask about the work no chat history shows: the jobs that run unattended, the quarterly report, the thing that only happens at release time. A nightly test run, such as the one in the example below, may leave no trace in anyone’s recent chat history. The line that decides the session is the last one in that panel: list the shortcuts nobody approved as well, because that is where the AI debt sits. If people read the inventory as a check on whether they followed the rules, those shortcuts stay invisible, and the team ends up with a list that looks tidy and describes a team that does not exist. Therefore, going first with a shortcut of your own helps, as does explaining, before the first row, that recording a task does not approve its use: the session starts with understanding what happens today, and concerns that need immediate attention under the team’s existing boundaries will still be addressed. Provisional Task Classes and the Grouping Test The grouping rule creates a puzzle that a careful reader will spot: it asks whether two tasks share the same AI Definition of Done, the same reviewer, and the same model tier, and those are decisions for later stages of the AI Delegation lifecycle, which the team has not reached. The resolution is that classes in the first session are provisional. Name them by output and audience, test the grouping against whatever review standards and routing decisions the team already has, and where those are missing, leave the grouping provisional and revisit it as the team works through the stages. An AI workflow inventory can carry uncertainty; what it must not carry is a settled-looking decision nobody made. The five rules on the AI Workflow Inventory canvas are short: Capture first, judge later: The A3 decision comes in stage 1, after the row exists, and a row approves nothing.One row per recurring task: Four prompt revisions used for the same weekly update are one task, and the tool goes in its own column.Name the class by output and audience: “Transcriptions for internal use,” “status communication leaving the company.” A class name that fits any task (“AI support”) is too wide to tell anyone which standard applies. (That naming approach is not different from coding.)Same standard, same reviewer, same tier, one class: When one of the three differs, split the class.Every class goes to stage 1; every audit refines the list: A class without an A3 decision is AI debt with a date on it. Three failure patterns are worth watching for: the sanctioned-use list, where only approved use gets written down, and your existing AI debt stays invisible; the prompt or Skill catalog, with a row per prompt/Skill instead of per task, so the classes never form; and the one-time census, filled once and never refined, so that six months later the audit walks a stale list and everyone nods at rows that no longer run. Example: Four Rows From a Scrum Team Here is a full first pass from a fictitious Scrum team of seven: Transcribe photos of Retrospective sticky notes (Scrum Master, GPT-5.6 Sol, stays in the team; input: handwritten notes with names): Internal output can still involve sensitive input.Rewrite customer feedback tickets into Product Backlog item drafts (class: requirement drafts for internal use): A concrete task inside a reusable class.Draft acceptance criteria for new items (same class): A second task that may share that class, as long as reviewer, standard, and tier stay the same.Rewrite interview notes into the hiring feedback template (on demand, Scrum Master, leaves the team; input: candidate names): Informal use that an approved-use-only list would have missed. The last row is on the list only because capture came first; under a rule that lists only approved use, the person running it would have left it out, and the team would have gone into A3’s stage 1 without knowing candidate names were entering a model on demand. From Row to Workflow Card Each decided row of the AI Workflow Inventory becomes a Workflow Card within the A3 Delegation System: workflow, task class, the A3 category, and later the tier, the owner, and the audit dates; the card goes on the poster where the workflow stands today. The word “decided” matters: the row exists before the A3 “Assist-Automate-Avoid” decision; the card exists after it. A row without a card means one of two things, and the remedy differs: either nobody has decided, in which case the class goes to the A3 Framework next, or somebody decided and never recorded it, in which case the team confirms the decision and writes it down. Ask which it is before doing either. For the transcription row, that means: the AI Workflow Inventory gives it a number, a task, and a provisional class; A3’s stage 1 decides whether it is Assist, Automate, or Avoid, and knowing that GPT-5.6 Sol runs it today settles nothing; stage 3 settles the owner, and the Scrum Master who runs it this Sprint does not become the owner by default. Both decisions go on the card. Your First 60 Minutes with the AI Workflow Inventory Book the session, aim for eight rows, and treat it as a first pass. One way to split the time: State the purpose and the boundary (capture, not approval): 10 minutesWalk the three sources, including unapproved shortcuts and unattended jobs: 20 minutesForm provisional task classes with the grouping test: 20 minutesName the inventory owner and the review date, and agree on the next step: 10 minutes The next step is A3’s stage 1: take every class to the A3 decision with the people who run the tasks in the room. If a row needs attention under the team’s existing boundaries before that (candidate names in a model, say), address it; “capture first” gives the decision its own step and does not postpone it. At each Delegation Audit, the list gets two kinds of maintenance. Add the rows that appeared since the last audit, and reconcile the existing ones: does the task still run, does the same person run it, with the same tool, on the same inputs, for the same audience? When you retire a task, record that decision in the Re-classification Log under the same inventory number, and keep the link to its original inventory record. Conclusion Which recurring use would your team’s current list miss? If your team cannot describe where AI already contributes to recurring work, start with a 60-minute AI Workflow Inventory session. The list gives you a shared basis for A3 decisions and helps you find the workflows that individual recollection might miss; then choose where in the A3 Delegation System to apply the method first.
Combining Temporal and LangGraph creates a deceptively simple question: which runtime owns the state of the agent? Both preserve execution progress, but they preserve different kinds of progress. Temporal reconstructs Workflow state from Event History and reuses recorded Activity results during replay. LangGraph persists thread-scoped graph state as checkpoints and resumes from super-step boundaries. Treating those mechanisms as interchangeable creates ambiguous recovery semantics. Production integration therefore needs explicit authority for business progress, agent working state, and the handoff between them. Deployment language, storage backend, model provider, and hosting topology remain unspecified assumptions. The current Temporal LangGraph integration narrows the problem. Its public-preview Python plugin can run LangGraph nodes as Temporal Activities or deterministic Workflow code, while Temporal provides durability; the documentation recommends an in-memory LangGraph checkpointer rather than a separate PostgreSQL or Redis checkpointer. Continue-As-New can carry cached task results into the next Workflow Run. The harder two-runtime problem appears when LangGraph retains an independent persistent checkpointer while Temporal separately orchestrates the business lifecycle. That case needs an application-level consistency contract. Ownership and Handoff The cleanest ownership rule is semantic. Temporal should own business lifecycle state: whether an execution is open, waiting for approval, canceled, timed out, compensated, or complete. LangGraph should own agent working state: messages, retrieved evidence, tentative plans, tool proposals, and graph position. External systems should remain authoritative for effects in their own domains. A payment processor owns whether a charge occurred; a deployment service owns whether a release exists. Temporal and LangGraph may retain receipts, but neither should invent contradictory domain truth. This matches their native models: Event History records Workflow progress, while LangGraph checkpointers persist thread state. The handoff should be modeled as a command protocol. A business execution ID identifies the long-lived application process and can map to a stable Temporal Workflow ID. A graph thread ID identifies checkpoint lineage. A command ID identifies one requested graph advance, while a monotonically increasing application revision identifies the state version against which that command was accepted. Temporal Run ID should not become the business identifier because Continue-As-New preserves Workflow ID while creating a new Run ID. LangGraph uses thread_id to load checkpoint history. Stable application identities must therefore outlive either runtime’s individual run instance. A graph-advance Activity can enforce that contract without exposing persistence details to Workflow code: Python def advance_agent(cmd): receipt = receipts.get(cmd.command_id) if receipt and receipt.status == "completed": return receipt.result state = threads.acquire( cmd.thread_id, fencing_token=cmd.revision, ) if state.revision != cmd.expected_revision: raise StaleCommand(cmd.command_id) result = graph.invoke( cmd.input, {"configurable": {"thread_id": cmd.thread_id}, durability="sync", ) return receipts.complete(cmd, result) The key property is the admission rule. The same command ID must never become new input merely because an Activity retried. A command accepted at revision 17 remains command 17 across worker crashes and timeouts. A genuinely new turn receives a new command ID and expected revision. LangGraph’s synchronous durability mode persists each checkpoint before the next step starts, reducing checkpoint-loss risk, but it does not create a transaction with Temporal Event History. Failure and Re-Execution The critical failure window begins after LangGraph commits a checkpoint and before Temporal records Activity completion. Temporal documents the analogous edge directly: an Activity can finish, the worker can crash before reporting completion, and the Activity can then execute again. Completed Activities are not re-executed during Workflow replay, but an unrecorded completion is indistinguishable from unfinished work at the orchestration boundary. Blindly injecting the same graph input on retry can therefore advance the agent twice for one logical transition. A durable command receipt closes that ambiguity. It records command ID, thread ID, expected revision, resulting revision, checkpoint reference, status, and result reference. When checkpoint and receipt records share a database, a custom persistence adapter can commit command acceptance and checkpoint metadata in one local transaction. When stores cannot share a transaction, recovery can reconstruct a missing receipt from checkpoint metadata containing the command ID and resulting revision. That reconstruction is an application-level inference based on LangGraph checkpoint metadata and lookup capabilities. A separate receipt written only after graph completion leaves another crash window. External effects require a second deduplication boundary. Temporal recommends idempotent Activities because Activity execution may happen more than once, and idempotency keys must ultimately be enforced by the called service. The graph should therefore propose an effect before performing it. An interrupt can expose the proposal, allowing Temporal to own approval, deadlines, and cancellation while a dedicated Activity performs the mutation with a stable operation ID. LangGraph documents that an interrupted node restarts from its beginning when resumed, so code before interrupt() executes again and pre-interrupt effects must be safe to repeat. Python def await_effect(state): receipt = interrupt({ "operation_id": state["operation_id"], "proposal": state["proposal"], "revision": state["revision"], }) if receipt["operation_id"] != state["operation_id"]: raise ValueError("effect receipt mismatch") return {"effect_receipt": receipt} This arrangement also clarifies replay. Temporal replay re-executes Workflow code while matching Commands against Event History; recorded Activity results are reused. LangGraph replay from an older checkpoint instead re-executes nodes after that checkpoint, including LLM calls, API requests, and interrupts. A LangGraph fork is therefore a new computational branch, not restoration of external reality. Previously completed business effects remain attached to their original operation receipts, while newly proposed effects require fresh authorization. Operational Semantics Concurrency control must prevent overlapping Activity attempts from advancing one thread simultaneously. A lease alone is insufficient if an expired holder can still write. A monotonically increasing fencing token tied to the accepted application revision provides a stronger rule: persistence rejects writes from an older token after a newer command is admitted. This is an integration pattern rather than a built-in Temporal or LangGraph guarantee. Administrative retries, manual resumes, and human approvals should pass through the same admission path. Temporal’s documented retry model establishes the underlying reason for such protection: Activity execution can occur more than once even though successful completion is observed once by the Workflow. Retry policy should remain layered. Temporal should own Activity retry delivery, while LangGraph-level retries should remain narrowly scoped to graph operations whose repetition is safe. Independent retry loops at both layers can multiply attempts and obscure the failure budget. Cancellation also needs explicit semantics. Temporal delivers cancellation to heartbeat-enabled Activities through heartbeats, but cancellation of orchestration does not prove that an already accepted remote effect was reversed. Ambiguous operations therefore require reconciliation with the authoritative downstream system. Continue-As-New changes run identity but not business identity. Temporal starts a fresh Event History with the same Workflow ID and a different Run ID, carrying selected state forward. In a dual-runtime design, the business execution ID, graph thread ID, latest revision, outstanding commands, and unresolved effect receipts must cross that boundary. Temporal scopes Update-ID deduplication to a Workflow Run, so deduplication that must survive Continue-As-New cannot rely solely on server-side Update identity. The official LangGraph integration similarly carries serialized cached results across Continue-As-New. Operational tests should target boundaries rather than happy paths. Worker termination immediately after checkpoint commit, after an external service accepts an operation, and before Activity acknowledgment exposes duplicate-execution defects. Concurrent retries should prove that stale fencing tokens cannot write. Stale approvals should prove that proposal revision checks reject obsolete decisions. Continue-As-New tests should prove that logical identities and deduplication records survive the run transition. Temporal provides test hooks for exercising Continue-As-New, while LangGraph checkpoint history and replay make recovery assertions observable. Conclusion A Temporal-and-LangGraph agent becomes reliable only when durability is subordinate to ownership. Temporal should decide business progress, LangGraph should preserve agent working state, and external systems should remain authoritative for real-world effects. The handoff needs stable business, thread, command, and revision identities; durable receipts; idempotent effect execution; explicit replay semantics; fenced concurrency; and continuity across Continue-As-New. The official Temporal LangGraph plugin can collapse much of this complexity by making Temporal the durable execution substrate. When independent persistence remains on both sides, however, two durable runtimes do not become one consistent runtime automatically. A precise state-ownership protocol is what turns duplicated durability into controlled recovery.
For application users, waiting for an upload to finish entering a web portal is familiar enough. Waiting again while the application analyzes that upload, however, can leave a user wondering whether anything is happening at all. The application can reassure the user by confirming the upload was received before analysis finishes, but it still needs a way to prevent the uploaded content from being published or used downstream until it's been evaluated against content policies and approved. We can accomplish this for a user-facing upload application by simply separating the submission process from the evaluation process. That allows the application to save the upload in private staging and queue it for background analysis before returning information to the frontend. With this approach, the uploaded content remains unavailable to downstream workflows until after the application records a content approval decision. Let's consider, for example, an editorial portal where photographers can submit their images for some publication. By rule, the publication categorically rejects AI-generated content, so it keeps each upload in private staging while its AI-image detector evaluates photo content in the background. When images meet the portal's policy, they can proceed to the publication workflow; when they don't, they must remain on hold for an editor to carefully review. In this article, we’ll explore building this workflow in C# around an actual AI-generated content detection service, and we'll use the above "editorial portal" concept as a concrete implementation example. We’ll include private staging, a queue, a background worker calling our external image-analysis service, and a recorded decision. The upload endpoint will ultimately return a submission ID to the frontend, which will then use that ID behind the scenes to retrieve the current upload status and display updates beside the uploaded image. Users will then be able to see whether their submission is processing, approved, or awaiting review without ever handling the ID themselves. With this implementation, both the user and the application get exactly what they need. Separating Submission From Evaluation We’ll create a clear divide in our processing workflow between an upload endpoint and a background worker. The endpoint will save the image and queue its submission ID; the worker will retrieve that ID, open the saved image, and request an assessment. This type of separation means the upload response can be returned before detection actually finishes. If we were to await detection inside the endpoint, we would still leave the client waiting for that result. ASP.NET Core provides BackgroundService for implementing the worker, so we’ll take advantage of that in our code. As we’ve described, the frontend will retain the returned ID and use it to retrieve status automatically. The actual user who submitted the image will be able to see “Processing” next to their upload without handling the response ID manually. Just note that the .NET 10 excerpts we’ll use assume an existing authenticated application with some form of upload validation in place. We’ll specifically demonstrate the processing components and their integration points. Creating a Shared Submission Record Right off the bat, we know the endpoint and worker need a shared record that connects an image to its current status. We’ll create this relationship with an immutable record and a concurrent dictionary for our single-instance demonstration: C# using System.Collections.Concurrent; public enum SubmissionStatus { Pending, Processing, Approved, AwaitingReview, Unverified } public sealed record DetectionResult( bool? CleanResult, double? AiGeneratedRiskScore, string? AiSource); public sealed record Submission( Guid Id, string OwnerId, string FilePath, SubmissionStatus Status, DateTimeOffset UpdatedAt, DetectionResult? Detection = null); public sealed class SubmissionStore { public ConcurrentDictionary<Guid, Submission> Items { get; } = new(); } Here, the endpoint creates a Pending record, and the worker replaces it with updated copies as evaluation progresses. This lets status requests read complete snapshots. The AwaitingReview status identifies flagged content, while Unverified identifies an unusable or failed assessment. Approved means the image passed this image-origin policy (not every possible publication or security check; we’ll assume there are several others). Creating the Queue Now we’ll create the actual connection between the endpoint and the worker. A bounded Channel<Guid> holds submission IDs until the worker reads them: C# using System.Threading.Channels; public sealed class ReviewQueue { private readonly Channel<Guid> channel = Channel.CreateBounded<Guid>(new BoundedChannelOptions(100) { SingleReader = true, SingleWriter = false, FullMode = BoundedChannelFullMode.Wait }); public bool TryEnqueue(Guid id) => channel.Writer.TryWrite(id); public IAsyncEnumerable<Guid> ReadAllAsync(CancellationToken ct) => channel.Reader.ReadAllAsync(ct); } Note that in our example, we’ve allowed 100 waiting jobs. Wait mode prevents existing jobs from being discarded, while TryWrite immediately returns false when capacity is completely exhausted. That allows our endpoint to reject the submission instead of sitting around waiting for it indefinitely. Saving and Scheduling the Upload Within our existing upload handler, we’ll first enforce file-size and supported-format checks (this rejects invalid or potentially unsafe files before they're stored or processed). To prevent users from guessing filenames or accessing uploads directly, it’s important that we save the image under a server-generated filename in a private directory. After validation, the below code saves image, an IFormFile. The configured staging directory is privateDirectory, and ownerId comes from the authenticated identity: C# Directory.CreateDirectory(privateDirectory); var id = Guid.NewGuid(); var path = Path.Combine(privateDirectory, id.ToString("N")); var fileCreated = false; try { await using var output = new FileStream( path, FileMode.CreateNew, FileAccess.Write); fileCreated = true; await image.CopyToAsync(output, cancellationToken); } catch { if (fileCreated) File.Delete(path); throw; } store.Items[id] = new Submission( id, ownerId, path, SubmissionStatus.Pending, DateTimeOffset.UtcNow); if (!queue.TryEnqueue(id)) { store.Items.TryRemove(id, out _); File.Delete(path); return Results.StatusCode( StatusCodes.Status503ServiceUnavailable); } return Results.Accepted( $"/submissions/{id}", new { Id = id, Status = "Pending" }); Note that this excerpt uses injected SubmissionStore store and ReviewQueue queue instances; privateDirectory must be an absolute path outside publicly served directories. We’ve designed this so the file is closed before enqueueing, which allows the worker to reopen it. If scheduling fails for some reason, we simply remove the unscheduled record and file. The 202 Accepted response is what confirms acceptance for processing; its location points the frontend to the status endpoint we’ll implement below. Implementing the Detection Call We’ll now put the remote request in a dedicated Detector class. This gives the worker exactly one operation to invoke — DetectAsync(path, cancellationToken). We could theoretically use a hosted classifier for the same role, with our team managing its model and infrastructure. In this case, the implementation calls our AI image-detection endpoint using multipart form data. C# using System.Net.Http.Json; public sealed class Detector( IHttpClientFactory clients, IConfiguration configuration) { public async Task<DetectionResult?> DetectAsync( string path, CancellationToken ct) { var key = configuration["Cloudmersive:ApiKey"]; if (string.IsNullOrWhiteSpace(key)) throw new InvalidOperationException( "Configure the Cloudmersive API key."); using var client = clients.CreateClient("Cloudmersive"); using var file = File.OpenRead(path); using var form = new MultipartFormDataContent(); form.Add( new StreamContent(file), "imageFile", "submission"); using var request = new HttpRequestMessage( HttpMethod.Post, "image/ai-detection/file"); request.Headers.Add("Apikey", key); request.Content = form; using var response = await client.SendAsync(request, ct); response.EnsureSuccessStatusCode(); return await response.Content .ReadFromJsonAsync<DetectionResult>( cancellationToken: ct); } } Note that the API key here comes from server-side configuration. The method just opens the staged image, submits its bytes, and then deserializes the response on the other end. Any HTTP failures cause an exception for the worker to handle. If you happened to read my previous article on AI detection, you’ll know that CleanResult supplies the summarized classification for this service, and AiGeneratedRiskScore provides the risk signal we’re looking for. AiSource then adds optional source context (this is information about the exact AI model that created the image; it’s not always available, even when images are deemed high-probability AI). The key thing to note is that we’re working with probabilistic signals rather than definitive proof of authorship. Turning the Assessment into a Decision We’ll now implement the decision portion separately so the worker can apply the same rule to every response: C# public static class ReviewPolicy { public static SubmissionStatus Evaluate(DetectionResult? result) { if (result?.AiGeneratedRiskScore is not double score || !double.IsFinite(score) || score < 0 || score > 1 || result.CleanResult is null) { return SubmissionStatus.Unverified; } // Illustrative application threshold. const double reviewThreshold = 0.5; return result.CleanResult == false || score > reviewThreshold ? SubmissionStatus.AwaitingReview : SubmissionStatus.Approved; } } In this example, we approve usable results with a clean classification and an AI-generated-probability score no higher than 0.5. Processing Jobs and Saving Outcomes At this point, we can connect all the pieces. Our worker will read an ID, load its record, mark it as “processing,” and call Detector. After that, it'll replace the record with the response and policy decision: C# public sealed class ReviewWorker( ReviewQueue queue, SubmissionStore store, Detector detector, ILogger<ReviewWorker> logger) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken ct) { try { await foreach (var id in queue.ReadAllAsync(ct)) { if (!store.Items.TryGetValue(id, out var item)) continue; store.Items[id] = item with { Status = SubmissionStatus.Processing, UpdatedAt = DateTimeOffset.UtcNow }; try { var result = await detector.DetectAsync( item.FilePath, ct); store.Items[id] = item with { Status = ReviewPolicy.Evaluate(result), Detection = result, UpdatedAt = DateTimeOffset.UtcNow }; } catch (Exception ex) { store.Items[id] = item with { Status = SubmissionStatus.Unverified, UpdatedAt = DateTimeOffset.UtcNow }; if (ct.IsCancellationRequested) break; logger.LogError( ex, "Evaluation failed for {Id}", id); } } } catch (OperationCanceledException) when (ct.IsCancellationRequested) { // Application shutdown while waiting for work. } } } We’ve handled each failure inside the loop such that unsuccessful scans don’t prevent subsequent jobs from happening. Note that in this compact version, we only perform one attempt. If we were to add retries, that would require bounded attempts, along with transient-error classification and respect for Retry-After. Before builder.Build(), we register the shared components and configure the HTTP client: C# builder.Services.AddSingleton<SubmissionStore>(); builder.Services.AddSingleton<ReviewQueue>(); builder.Services.AddSingleton<Detector>(); builder.Services.AddHostedService<ReviewWorker>(); builder.Services.AddHttpClient("Cloudmersive", client => { client.BaseAddress = new Uri("https://api.cloudmersive.com/"); client.Timeout = TimeSpan.FromSeconds(60); }); Requests are connected with the worker via the singleton store and the queue. The timeout bounds each remote call (its value is an application choice). Returning Status and Enforcing Approval The frontend can now poll a status route. This handler checks the authenticated owner before returning a limited view of the record. We’ll place both routes after builder.Build() and before app.Run(), with the using directive at the top of Program.cs: C# using System.Security.Claims; app.MapGet("/submissions/{id:guid}", (Guid id, ClaimsPrincipal user, SubmissionStore store) => { var ownerId = user.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrWhiteSpace(ownerId) || !store.Items.TryGetValue(id, out var item) || item.OwnerId != ownerId) { return Results.NotFound(); } return Results.Ok(new { item.Id, Status = item.Status.ToString(), item.UpdatedAt }); }).RequireAuthorization(); The host needs to validate authentication and provide the same stable owner claim used during the upload process. The frontend will use the returned status to update its interface automatically. That same ownership check is performed by the download route. This additional condition is then applied before the file is returned: C# app.MapGet("/submissions/{id:guid}/file", (Guid id, ClaimsPrincipal user, SubmissionStore store) => { var ownerId = user.FindFirstValue(ClaimTypes.NameIdentifier); if (string.IsNullOrWhiteSpace(ownerId) || !store.Items.TryGetValue(id, out var item) || item.OwnerId != ownerId) { return Results.NotFound(); } if (item.Status != SubmissionStatus.Approved) { return Results.Conflict(new { Error = "Submission is not approved." }); } return Results.File( item.FilePath, "application/octet-stream", fileDownloadName: "submission"); }).RequireAuthorization(); Note that private storage will prevent direct public access from bypassing this check, so human reviewers need a separate authorized access path. Testing and Production Considerations To verify things like approval, review, and missing-result handling, we can substitute controlled detector responses. In the event we get simulated request failures, they should produce Unverified, while downloads should remain blocked during processing. Critically, another user’s request should NOT reveal the submission. It’s important to bear in mind that this demo code also loses its queue and records on restart. That’s obviously not production-ready, since production usually requires durable jobs and records on top of coordinated enqueuing and duplicate-job handling. Additionally, we should probably retain things like creation timestamps and attempt history in our production code. Conclusion In this article, we learned how to connect private portal uploads to a background AI-content detector through a shared record and queue. Our worker records an explicit outcome, our frontend retrieves it automatically, and our download route enforces content approval. With this workflow in place, our application can acknowledge submissions promptly while keeping unfinished and unsuccessful content evaluations out of normal publication workflows.
Software engineering is often seen as a technical field focused on designing systems, writing code, selecting architectures, defining APIs, and optimizing for performance, scalability, and maintainability. However, many of the most challenging problems are not technical, but human. As engineers advance into leadership roles such as senior engineer, Staff Engineer, architect, or engineering manager, the challenge extends beyond technical solutions. Leaders must also understand how people make decisions, how teams respond to change, and how individual behavior can influence software quality. Humans are not always rational. We depend on habits, shortcuts, intuition, experience, and assumptions. While these can improve efficiency, they may also lead to decisions that don't align with requirements, constraints, or long-term goals. A team may retain a design simply because “we have always done it this way.” Engineers might accept poor code quality if the existing codebase is already disorganized. Teams may also select a complex architecture because complexity appears more robust, even when a simpler solution would suffice. While these decisions may seem minor individually, over time they can impact more than a single implementation. They may influence team behavior, increase technical debt, reduce maintainability, and shape the system’s architecture. This article examines three common behavioral traps that affect software engineering decisions: status quo bias, the broken windows effect, and complexity bias. We will also discuss how engineers and technical leaders can identify these patterns and foster more deliberate, evidence-based decisions. What Are Cognitive Biases, and How Do They Affect Software? Cognitive biases are systematic patterns in how people interpret information, evaluate alternatives, and make decisions. Rather than random mistakes, they are mental shortcuts that help us act quickly but can also influence our judgment without our awareness. This is especially relevant in software engineering, where decisions are often made under uncertainty, time constraints, incomplete information, and the influence of prior experience. Engineers rarely evaluate every alternative from scratch. Instead, we reuse patterns, rely on intuition, trust familiar technologies, and simplify complex situations to maintain progress. Most of the time, this is useful. Software development would be impossibly slow if every decision required exhaustive analysis. Problems arise when these shortcuts replace thoughtful reasoning instead of supporting it. For example, a team may retain an implementation simply because it exists, not because it meets current requirements. Developers might accept poor practices if the codebase already appears neglected. Architects may select more complex solutions because complexity seems more robust or professional. These effects can extend far beyond the initial decision. A cognitive bias that starts as an individual preference can influence code reviews, become a team convention, shape architectural decisions, and eventually contribute to technical debt. The impact therefore goes beyond a single decision. Biases can affect: Code quality and maintainabilityArchitectural consistencyTechnical debtTechnology selectionResistance to changeTeam standards and engineering culture The most concerning aspect is that biased decisions rarely seem irrational when made. They often appear reasonable: “this is how we normally do it,” “the code is already messy,” or “we need something more robust.” Recognizing these patterns does not eliminate them, but it enables engineers and technical leaders to challenge assumptions before they become embedded in the architecture. This understanding also provides a natural transition to the following sections: status quo bias, the broken windows effect, and complexity bias. Status Quo Bias: “We’ve Always Done It This Way” Status quo bias is the tendency to favor the current situation solely because it is established. Samuelson and Zeckhauser showed that people often favor existing options, even when alternatives are available: Status Quo Bias in Decision Making. In software engineering, this bias can be more harmful than simple resistance to change, as it may keep teams from evaluating decisions against current circumstances. Software evolves continuously. Requirements shift, languages gain new features, frameworks improve, and teams learn from experience. A decision that was appropriate years ago may no longer be suitable today. Problems arise when teams assume the current solution is correct by default. “We’ve always done it this way.” How Status Quo Bias Impacts Software Engineering The greater risk is that teams may stop analyzing whether existing decisions still fit the current context, not just that they avoid change. For example, a Java team may continue using XML configuration simply because it was the original convention, even when annotations or programmatic configuration would better suit parts of the system today. The problem is not XML itself. XML may still be the right choice. The issue arises when the reasoning is: we used XML before, so we use XML now. This differs from evaluating current requirements, available language and framework capabilities, trade-offs, and then selecting the most appropriate configuration strategy. The same pattern can appear almost anywhere: “We always store this in the session.”“We always use inheritance here.”“We always create a microservice for this.”“We don’t use that newer Java feature.”“This framework has always worked for us.” Each of these statements may reflect a valid decision, but none alone provides sufficient technical justification. The broader risk is that architecture may reflect outdated constraints rather than current needs. Teams may overlook new language features, framework improvements, better APIs, production lessons, infrastructure changes, or architectural alternatives unavailable when the original decision was made. Over time, the codebase may remain internally consistent but become increasingly disconnected from its surrounding ecosystem. Research on software architecture decision-making shows these choices are not purely rational and can be influenced by bounded rationality and cognitive biases: Decision Making in Software Architecture. How to Handle Status Quo Bias in a Team The goal is not to challenge every existing decision or adopt every new technology. Consistency, migration cost, operational risk, and familiarity are all valid engineering concerns. The key is to ensure that history informs the decision, but does not determine it. A simple question can help: If we were making this decision today, with our current requirements and tools, would we make the same choice? This separates two different questions: What is the best design for the current context?Is changing the existing design worth the cost? Those are not the same problem. Another useful technique is to make evaluation criteria explicit. Compare the current approach and alternatives using the same dimensions: maintainability, complexity, operational risk, migration cost, framework support, and long-term evolution. This approach creates a fairer discussion, as the current solution no longer receives an automatic advantage simply because it exists. Naming the Bias Can Move the Discussion Forward One effective leadership technique is to name what may be happening. Instead of saying: “You are resisting change.” A staff engineer, architect, or manager can ask: “Could status quo bias be influencing this decision?” This shifts the discussion from evaluating individuals to evaluating the decision-making process. Someone may then realize: “I prefer this solution because I know it well, not because we compared the alternatives.” That distinction matters. Experience says: “We tried this before, and here are the trade-offs we learned.” Inertia says: “We do this because we have always done it.” Effective technical leadership involves helping teams recognize this distinction. The goal is not modernization for its own sake, nor is newer automatically better. The aim is to ensure architecture evolves with the context, rather than remaining unchanged simply because the current solution is familiar. Complexity Bias: “It Can’t Be That Simple” Complexity bias is the tendency to trust a complicated solution more because it appears more complete, sophisticated, or robust. Simple solutions can feel uncomfortable, especially for important problems, leading us to expect equally complex architectures. Research on causal reasoning shows that people often view complexity as a sign that an explanation fits the evidence, even though they prefer simplicity in other contexts. See Simplicity and Complexity Preferences in Causal Explanation (ScienceDirect). In software engineering, this bias often leads to overengineering. How Complexity Bias Impacts Software Engineering The main problem is not complexity itself. Some systems are legitimately complex. The real issue is unnecessary complexity, as each abstraction, service, dependency, process, and architectural layer adds risk. Additional components increase the effort required to understand, integrate, test, deploy, monitor, and modify the system. Solutions meant to improve safety can instead expand the potential for failure and raise the cost of future changes. Simple requirements can accumulate queues, events, retries, services, orchestration, and frameworks, making the architecture seem more robust even when these additions are not justified. Forbes identifies over-engineering solutions as one of the bad habits technology leaders want developers to avoid, noting that developers sometimes solve problems that don't exist or add complexity before fully understanding the problem (Forbes). Complexity bias also influences organizational practices. Teams may add approval stages, meetings, documents, and processes because increased control feels safer. Eventually, even agility suffers. Teams may become attached to processes without evaluating whether they still support effective software delivery. Just Enough Architecture George Fairbanks’ concept of Just Enough Software Architecture offers a useful counterpoint. His risk-driven approach suggests that architectural effort should match the actual risk of failure. When risk is low, extensive design can be wasteful. When risk is high, more deliberate design is warranted (George Fairbanks). This gives us a better question than: “How can we make this architecture more robust?” Ask instead: “What risks are we trying to mitigate, and how much architecture do those risks justify?” That changes the discussion significantly. An extra service may be justified by isolation requirements. Asynchronous communication may be justified by availability or throughput. Regulation may require additional governance. However, you should always justify complexity. Fairbanks summarizes the principle well: architectural effort should match the risk of failure (George Fairbanks). How to Handle Complexity Bias in a Team A useful starting question is: What is the simplest solution that satisfies the requirements and mitigates the risks we actually have? Technical leaders can then challenge complexity with concrete questions: What risk does this abstraction reduce? What requirement needs another service? What would happen if we did not introduce this complexity yet? Naming the bias can also help: “Are we adding this because the problem requires it, or because the simpler solution feels too simple?” The goal is not simplicity at any cost. Underengineering poses risks similar to overengineering. The goal is just enough architecture: introduce the complexity needed to manage real risks, but no more than the problem justifies. Broken Windows: When the Environment Lowers the Standard Unlike status quo or complexity bias, Broken Windows Theory is not a cognitive bias. Instead, it represents a risk: visible disorder can influence future behavior and gradually redefine what is considered acceptable. Although the concept originated outside software engineering, researchers have applied it to code quality for decades. Recently, researchers examined whether this effect occurs in software systems. A 2024 study in Empirical Software Engineering found that developers working in systems with higher technical debt were more likely to introduce additional debt, such as poor variable names, duplicated behavior, and other issues detected by static analysis (Springer Link). How Broken Windows Impact Software Engineering A neglected codebase sends signals. When engineers repeatedly encounter duplicated code, weak tests, inconsistent naming, abandoned abstractions, or ignored warnings, the implicit message can become: “This level of quality is acceptable here.” That can change behavior. Rather than considering how the code should ideally be structured, developers start adapting to their environment: “This module is already messy.” “Nobody tests this part anyway.” “There are already dozens of warnings.” “One more shortcut will not make a difference.” The risk is cumulative. While a single compromise may seem minor, repeated compromises can gradually lower the entire system's engineering standards. The 2024 controlled experiment is notable because it provides direct evidence of this effect. Developers working on systems with higher technical debt were more likely to introduce new debt (Springer Link). A second 2024 study examining code history also found that existing code quality can influence the quality of subsequent changes, though the effect varies by quality characteristic (arXiv). The Risk Goes Beyond Code Broken windows can also appear in engineering culture. If architectural rules are routinely ignored, pull requests are poorly reviewed, failing tests are tolerated, or technical debt is continually postponed, developers learn what the organization truly values. Written standards may say one thing, while the environment says another. That distinction matters because engineering culture is shaped not only by what leaders say, but also by what teams repeatedly observe. A codebase can therefore create its own feedback loop: Poor quality normalizes lower standards. These changes create additional visible disorder, which further reduces expectations. How to Handle It in the Team The goal isn't to create a perfectly clean system, as that can lead to overengineering. Instead, teams should prevent visible problems from becoming the accepted baseline. Small actions matter: Fix obvious problems when touching nearby codeAddress warnings and failing tests promptly, preventing them from becoming permanent background noiseSet a minimum quality standard for new changesMake technical debt visible rather than silently normalizing it Technical leaders can also name the pattern directly: “Are we accepting this because it is genuinely the right trade-off, or because the surrounding code has already lowered our expectations?” This question helps distinguish between pragmatic technical debt and simple degradation. The objective isn't perfection, but preventing the environment from silently teaching the team that quality no longer matters. A useful principle is: Every codebase teaches developers how it expects to be treated. Once poor quality becomes the norm, reversing that expectation becomes much more difficult.
A few months ago, I watched a senior engineer spend forty-five minutes reviewing a single pull request — a PR that an AI assistant had generated in under two minutes. The code looked clean. The tests passed. But she kept cross-referencing an incident postmortem from eight months earlier, muttering something about retry amplification. She caught a real production risk. The AI reviewer had flagged nothing. That moment stuck with me. We've spent years optimizing how fast we can write code. But we haven't seriously reckoned with what happens when review can't keep up. The Bottleneck Has Shifted A single engineer with AI assistance can now produce hundreds of lines of code, large refactors, infrastructure changes, and test suites — all within minutes. Review complexity, however, grows exponentially with change size and system interdependency. The core problem is no longer "Can AI write code?" It's "Can humans reliably validate what AI wrote?" Code generation speed increases. Human cognitive review capacity stays flat. That imbalance is quietly accumulating risk in engineering organizations everywhere. Why Current AI Reviewers Fall Short Most AI PR review systems today operate on static diffs, syntax-level reasoning, and shallow best-practice detection. They produce comments like: "Potential null pointer.""Consider renaming this variable.""Possible optimization opportunity." Occasionally useful. Rarely sufficient for production-critical systems. The structural problem is that these tools treat PR review as a language problem instead of a systems reasoning problem. They assume software correctness is inferable from local code semantics alone. In reality, production safety emerges from interactions between architecture, runtime behavior, operational history, and organizational context. The Shallow Review Problem in Practice Here's a concrete example. An AI assistant generates this database query optimization: Python # AI-optimized version def get_user_orders(user_id): return db.query(""" SELECT o.*, p.*, i.* FROM orders o JOIN payments p ON o.id = p.order_id JOIN items i ON o.id = i.order_id WHERE o.user_id = ? """, user_id) Typical AI reviewer comment: "Query optimized with JOIN to reduce round trips." What a senior engineer sees: "This will cause a Cartesian explosion. The orders table has 50M rows, items averages 8 per order. This returns 400M+ rows for power users. We had a nearly identical incident (INC-287) that took down the read replica. Needs pagination and selective columns." The difference isn't token count or model size. It's operational memory and causal reasoning. The Real Challenge Is Not Context Windows Many people assume the fix is larger context windows. Feed the model the whole repo, and it'll review like a senior engineer. But experienced engineers don't review code by loading entire systems into working memory. They use abstraction, selective attention, and compressed mental models. A senior engineer reviewing a Kafka retry change doesn't reread the entire messaging subsystem — they remember prior incidents, retry amplification risks, and historical outages. That's cognitive compression, not token recall. Modern LLMs are exceptional at syntax fluency, pattern completion, and probabilistic association — what you might call token intelligence. But effective PR review requires something deeper: causal reasoning, architectural abstraction, operational memory, risk forecasting. Call it cognitive intelligence — persistent contextual reasoning grounded in operational history and causality. The distinction matters because it changes what we need to build. What a Cognitive Review Architecture Looks Like Instead of: Plain Text Large Prompt + Large LLM → Review We need: Plain Text Structured Memory + Semantic Retrieval + Runtime Context + Specialized Review Agents + Reasoning Layer + LLM → Review The LLM should not be the memory. It should be the reasoning interface over structured engineering knowledge. Intent Reconstruction Before reviewing code, the system needs to understand why the change exists. Business intent, bug root cause, architectural motivation. Inputs include Jira tickets, PR descriptions, ADRs, incident reports, and commit timelines. Without intent, review quality stays shallow regardless of model size. Engineering Knowledge Graphs Human reviewers carry organizational memory: fragile services, latency-sensitive paths, scaling bottlenecks, previous outages, dangerous dependencies. AI reviewers need persistent semantic memory systems encoding the same — service relationships, API contracts, operational metadata, incident history, ownership boundaries. This creates an engineering cognition layer far richer than raw repository context. Multi-Agent Review Systems A single reviewer model is insufficient. Future systems will consist of specialized agents working together: Architecture Reviewer – dependency boundaries, coupling risk, architectural driftReliability Reviewer – retries, backpressure, idempotency, failover behaviorSecurity Reviewer – injection risks, auth issues, secret exposurePerformance Reviewer – memory growth, query amplification, scaling regressionsHistorical Regression Reviewer – correlation with past outages, postmortems, incident fingerprints This begins to approximate how experienced engineering organizations actually review software. Runtime-Aware Review Static analysis alone misses emergent runtime behavior. Future cognitive review systems will integrate observability telemetry, tracing data, production metrics, and traffic patterns. Compare these two responses to a retry configuration change: Traditional AI reviewer: "Code follows retry best practices." Cognitive AI reviewer with operational memory: "HIGH RISK: Similar retry configuration caused incident on 2023-09-15. This service processes 2M messages/hour at peak. 10 retries with exponential backoff = up to 17 minutes per message. Previous incident resulted in 8M message consumer lag and cascading downstream failures. Recommend: max 3 retries, circuit breaker, dead letter queue, idempotency check before db.save(). See ADR-089." That is a fundamentally different class of intelligence — and a fundamentally different class of safety. Engineering Memory Is the Missing Piece One of the biggest gaps in current AI systems is durable operational memory. Experienced engineers develop intuition through outages, failed deployments, debugging sessions, and production emergencies. These experiences become compressed heuristics: "This retry increase feels dangerous" — not because of syntax, but because of remembered causal relationships. Replicating this requires episodic memory systems, incident-aware reasoning, and causal knowledge graphs. Much of this mirrors practices long established in Site Reliability Engineering, where institutional learning from incidents is treated as critical infrastructure. Incident postmortems aren't just documentation — they're organizational immune system responses. Getting AI systems to genuinely learn from incidents rather than just pattern-match against them remains one of the harder open problems in this space. What Teams Can Do Today Fully cognitive review systems don't exist yet. But organizations can meaningfully improve AI-assisted review quality right now: Capture architectural knowledge in machine-readable form. Service boundaries, retry policies, timeout configurations, scaling assumptions — not just in wikis, but in structured formats AI systems can query.Link PRs explicitly to incident history. Build connections between code changes and the incidents they caused or prevented. This is organizational memory that AI systems can leverage today.Tag services with operational metadata. Criticality tier, traffic patterns, known failure modes, blast radius. Treat repositories as systems, not just files.Integrate observability into review pipelines. Connect production metrics and tracing data to code review. Runtime context dramatically improves review quality.Prioritize high-signal AI feedback. Review fatigue from noisy, low-signal comments is a real trust problem. Focus AI comments on incident-correlated patterns, architectural violations, and operational risks. The Trust Calibration Problem One concern I keep coming back to: bad AI reviewers are dangerous not because they miss things, but because they sound confident while missing things. They reduce human vigilance through automation bias. They generate fatigue through noise. They normalize shallow approval. Future cognitive review systems need to be not just more accurate, but properly calibrated — knowing when they lack sufficient context and escalating accordingly. An AI reviewer should be able to say: "I may not have enough confidence to validate this safely." That self-awareness may matter more than raw capability. The Road Ahead The next era of AI software engineering will not be defined by who generates the most code. It will be defined by trust, reasoning quality, and operational awareness. The future belongs to systems capable of understanding not just what changed — but why it changed, what it affects, and whether it's safe. That's the difference between code generation and engineering intelligence. And honestly, solving it seems harder and more interesting than anything we've built so far. Key Takeaways The bottleneck has shifted from code generation to code review and validation.Larger context windows alone won't bridge token intelligence and cognitive intelligence.Human-like review requires structured memory, causal reasoning, and operational awareness.Multi-agent architectures with specialized reviewers mirror how engineering teams actually work.Runtime-aware systems integrating production telemetry represent the next frontier.Engineering memory — learning from incidents — is critical for trust and safety.Teams can start today by capturing architectural knowledge and linking incidents to code changes. References Vaswani, A., et al. (2017). "Attention Is All You Need." NeurIPS.Kahneman, D. (2011). Thinking, Fast and Slow. Farrar, Straus and Giroux.Lewis, P., et al. (2020). "Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks." NeurIPS.Shinn, N., et al. (2023). "Reflexion: Language Agents with Verbal Reinforcement Learning." arXiv.Beyer, B., et al. (2016). Site Reliability Engineering: How Google Runs Production Systems. O'Reilly Media.Allspaw, J. (2012). "Blameless PostMortems and a Just Culture." Etsy Engineering.
A mobile request can fail without the server-side work failing. An iOS app may time out, lose the response after a POST has reached the service, or retry after connectivity changes while the original execution is still progressing. Apple explicitly distinguishes safe retry behavior by HTTP method and notes that URLSession can retry requests in some connection-loss cases, waitsForConnectivity can also cause the system to continue a request when connectivity returns. The dangerous state is therefore not “request failed,” but “completion is unknown.” If that request starts an agent that charges an account, reserves inventory, sends a message, or invokes an MCP tool, a second submission can become a second side effect. The Retry Boundary Is the Real Transaction Boundary “Exactly once” is too strong for a workflow crossing an iPhone, HTTP, an agent runtime, an MCP server, Kafka, a database, and an external API. Kafka can provide exactly-once guarantees within defined Kafka processing boundaries, but those guarantees do not atomically include arbitrary remote tool effects. The practical target is effectively-once behavior, and retries are expected, but every effect is guarded by a stable operation identity and converges on one committed outcome. Kafka’s idempotent producer suppresses duplicate records caused by producer retries, while transactional producers can atomically publish across Kafka partitions; the producer documentation also limits idempotence guarantees to a producer session and requires read_committed consumers for end-to-end transactional visibility. The operation identity must exist before the first network attempt. An iOS client can create an operationId when an action becomes durable local intent, persist it, and reuse it across transport retries. Transport material such as a server challenge may change, but the business ID must not. The server treats (subjectId, operationId) as a uniqueness boundary and stores a canonical payload hash with it. PostgreSQL unique constraints enforce row uniqueness, while INSERT ... ON CONFLICT provides an atomic conflict path under concurrency. SQL INSERT INTO agent_operation(subject_id, operation_id, payload_hash, status) VALUES (:subject, :operationId, :payloadHash, 'ACCEPTED') ON CONFLICT (subject_id, operation_id) DO NOTHING; A conflict with the same payload hash returns the existing operation; a different hash rejects key reuse. The record should exist before agent execution starts, and the accepted response should expose the durable operation identity. Let LangGraph Resume Without Repeating Effects LangGraph persistence is useful precisely because durable execution can replay code. With a checkpointer, LangGraph saves state at super-step boundaries; if execution resumes after a failure, an affected node can run again from the beginning. Official guidance consequently requires idempotent node logic, and task results can be checkpointed so completed task work can be reused during resume instead of recomputed. Replaying from an earlier checkpoint can also re-trigger later LLM calls and API requests. A stable business operation should therefore map to a stable LangGraph thread, while every effectful tool boundary receives the same operation ID. Python config = {"configurable": {"thread_id": operation_id} result = graph.invoke( {"operation_id": operation_id, "command": command}, config ) Checkpointing reduces recomputation but does not replace downstream idempotency. A reservation can succeed before its task result is durably checkpointed. LangGraph’s functional API therefore recommends idempotent tasks because an incomplete task can execute again during resume. Python @task def reserve_inventory(operation_id, sku, quantity): return mcp.call_tool("reserve_inventory", { "operationId": operation_id, "sku": sku, "quantity": quantity }) The significant property in this snippet is not the decorator. The important part is that the business identity crosses the graph boundary and reaches the tool implementation. A downstream inventory service can then use that identity to return a previously committed reservation rather than creating another one. MCP Tasks Are Durable Handles, Not Deduplication Keys The current MCP Tasks design is especially relevant to long-running agent tools. In the July 28, 2026 protocol revision, Tasks moved into the io.modelcontextprotocol/tasks extension. A server can return a durable task handle, and the client can poll with tasks/get, provide input with tasks/update, or request cancellation with tasks/cancel. The task is durably created before its handle is returned, which allows polling after a disconnect. That durability solves result retrieval after task creation, but it does not by itself deduplicate the request that creates the task. The task ID is server-generated. If the server creates task A, the response disappears, and the original tools/call is sent again, a naïve implementation can create task B. Therefore, the business operationId must be part of the tool arguments or equivalent application metadata, and task creation must first look up an existing operation. This follows directly from MCP’s server-generated task-ID model combined with retry ambiguity at the HTTP boundary. The MCP server can return an existing task handle for the same authenticated subject, operation ID, and payload hash, and later return the stored terminal result. Cancellation should also be idempotent because MCP defines it as cooperative rather than a guarantee that underlying work stops immediately. Keep Kafka Guarantees Inside Kafka Kafka is most valuable after the operation has been claimed. A database transaction can persist operation state with an outbox row carrying the same ID. Kafka producer idempotence protects against duplicates caused by producer retries, while consumers can still use the operation ID for application-level deduplication. Kafka transactions can atomically cover Kafka writes, but they do not extend over an MCP server or payment API. The event contract should preserve causality rather than inventing a new identity at each hop. JSON { "operationId": "8E7B6D9E-...", "type": "AgentToolCompleted", "tool": "reserve_inventory", "status": "SUCCEEDED" } A consumer can enforce uniqueness on (consumerName, operationId, eventType) or make the state transition conditional. Kafka delivery guarantees and application idempotency then reinforce each other instead of being treated as interchangeable. Bind Retry Identity to App Attest Without Blocking Legitimate Retries App Attest addresses a different failure mode: whether a request comes from a legitimate app instance and whether signed request material has been replayed or altered. Apple’s current guidance uses a server-provided challenge for assertions and requires the server to validate a strictly increasing assertion counter; that counter is specifically an anti-replay signal. Assertions are generated locally on the device after key attestation. The App Attest assertion must not become the business idempotency token. A legitimate retry should obtain fresh challenge material and generate a fresh assertion while retaining the original operation ID. The data hashed for the assertion can bind the server challenge, operation ID, and canonical payload hash together. Swift let payloadHash = SHA256.hash(data: body) let clientData = challenge + operationID.data + Data(payloadHash) let clientDataHash = Data(SHA256.hash(data: clientData)) let assertion = try await service.generateAssertion( keyID, clientDataHash: clientDataHash ) Apple recommends server-controlled challenges, server-side validation, and assertion-counter tracking as assertions are generated on demand without a round trip to Apple’s servers. The server verifies App Attest, checks that the challenge binds the operation ID and payload, then performs the idempotency lookup. A fresh assertion can retry the same operation; a replayed assertion fails anti-replay validation; an altered payload fails the hash check. Effectively-Once Behavior Is a Composition Property Reliable agent execution does not come from asking iOS to retry less often or from labeling a Kafka pipeline “exactly once.” It comes from carrying one durable business identity across every retry and every boundary, claiming that identity atomically before execution, making LangGraph effects idempotent under resume, using MCP Tasks as durable result handles rather than creation-time deduplication keys, restricting Kafka’s exactly-once guarantees to Kafka’s transactional domain, and using App Attest to prove request integrity without confusing anti-replay state with business deduplication. When those boundaries align, a lost mobile response can cause another HTTP attempt, another graph invocation, or another poll, but it does not cause another business effect. That is the operational meaning of effectively once.
Most enterprise AI post-mortems do not blame the model. They blame the storage tier that starved the accelerators, the identity policy that over-granted access, the cost model that ignored egress, the forecast that leaked future data, or the region that failed and took a business process with it. The hard part of production AI was never intelligence. It was the engineering discipline around it. This article distills the architectural patterns that decide whether a cloud AI system is trustworthy at scale, spanning infrastructure, identity, cost, operations, the applied domains, low-code assembly, platform selection, and multi-cloud resilience. It is written for engineers who have to keep these systems running, not for a keynote. Infrastructure: The Interconnect Is the Bottleneck Distributed training is a systems problem before it is a machine learning problem. When a job spans many graphics processing units (GPUs), the fabric connecting them (e.g., NVLink within a node, InfiniBand, or a vendor fabric across nodes) frequently caps throughput more than raw compute does. Accelerators wired through an ordinary network idle while they wait to synchronize gradients. Storage is the symmetric constraint. If the file system cannot deliver data at the rate the accelerators consume it, utilization collapses. The pattern is a tiered design: Hot tier: parallel or block storage feeding active training at high input/output operations per second (IOPS).Warm tier: recent data staged for quick promotion.Durable lake: object storage providing petabyte-scale durability, partitioned and lifecycle-managed underneath. Two cost drivers hide from the pricing page: data egress (moving data across regions or out of a provider) and idle warm capacity. Optimizing only the advertised compute line item guarantees a surprise on the invoice. Identity Is the Perimeter In a service-to-service AI architecture, the network perimeter is gone; identity is the boundary. A zero-trust posture, where every request authenticates and receives least privilege, contains the blast radius when a component is compromised. Across providers, identity federation is the load-bearing pattern: a principal authenticates once and is recognized everywhere, so access is granted and revoked centrally instead of reconciled across three identity systems. Policy must travel with the workload; a rule enforced on one cloud and forgotten on another is not a policy. Model authorization is the emerging frontier. As models call tools and take actions, the question moves from who can query this model to what may this model do on a user's behalf. Least privilege applied to an autonomous agent is the boundary between useful and unbounded. Cost and Operations Are a Control Loop Cost management is not a spreadsheet; it is automation. Consistent resource tagging across every cloud is the prerequisite for attribution. On top sit budgets, alerts, and automated remediation that throttles runaway spend before it escalates. Site reliability engineering (SRE) supplies measurable targets. For AI workloads, the golden signals extend beyond latency and errors to accelerator utilization, queue depth, and prediction quality. A model can be fully available and quietly wrong, so define a service level objective (SLO) for output quality, not just uptime. Three techniques earn their complexity: Spot or preemptible capacity plus checkpointing cuts training cost sharply when jobs resume cleanly after reclamation.Predictive scaling anticipates load instead of reacting to it.LLM inference optimization becomes architectural: batch requests, cache frequent responses, route easy queries to smaller models, reserve the expensive model for queries that need it. The Applied Domains Share a Spine, Differ in Physics Vision is byte-heavy. High-resolution images and video streams make the data and network layers dominant. For real-time video, decouple frame capture from analysis and sample frames rather than processing every one. Critically, a business-rule layer, never the model alone, owns consequential decisions. Every extraction should carry a confidence score used as a routing gate: Python def route_extraction(field, threshold=0.90): if field["confidence"] >= threshold: return "auto_process" return "human_review" Language is byte-light but semantically treacherous, and because it replies directly to users, errors are visible. The defining risk of generative systems is hallucination. The strongest architectural defense is retrieval grounding, forcing answers from verified sources with citations: Python def answer(question, knowledge_base): passages = knowledge_base.search(question, top_k=3) context = "\n".join(p.text for p in passages) prompt = f"Answer using ONLY this context.\n{context}\n\nQ: {question}" return model.generate(prompt), [p.source for p in passages] Forecasting is defined by time order. You cannot shuffle a time series into random splits, and the most common failure is data leakage, using information unavailable at prediction time. Test on a fair, time-ordered holdout, and always emit a prediction interval; a point forecast that hides its uncertainty invites overconfident decisions. No-Code and Low-Code: Governed or Ungoverned No-code and low-code platforms collapse build cost from a scoped project to an afternoon, which is why adoption is exploding. The symmetric risk is sprawl: hundreds of ungoverned flows handling sensitive data, owned by no one. Govern with guardrails, not gates. Restrict which connectors and data sources are permitted, assign an owner and an SLO to every production flow, then let builders move freely inside the boundary. The goal is to make the safe path the easy path. Platform Selection Without Self-Deception Vendors all claim to be fastest, cheapest, and most reliable. Benchmark to replace claims with evidence: Latency: report percentiles (p95, p99), never averages that hide the slow tail.Quality: measure on your own representative data, not a public leaderboard.Cost: model total cost of ownership, including transfer, storage, idle capacity, operations, and migration, not the headline compute rate.Reliability: verify the platform meets your recovery time objective (RTO) and recovery point objective (RPO). Combine dimensions in a weighted scorecard whose weights are fixed before scores are seen. Adjusting weights afterward to crown a favorite converts analysis into rationalization. Multi-Cloud Resilience: Design for the Day a Cloud Fails For systems a business cannot lose, a single provider is a gamble. Multi-cloud resilience deliberately places critical workloads so no single provider failure takes the business down, applied only where the cost of failure exceeds the cost of prevention. Predict rather than react. Combine leading signals into a health score and fail over proactively: Python def health_score(latency_ms, error_rate, saturation): latency_factor = max(0, 1 - (latency_ms / 1000)) error_factor = max(0, 1 - (error_rate / 0.05)) saturation_factor = max(0, 1 - saturation) return round(0.4*latency_factor + 0.4*error_factor + 0.2*saturation_factor, 3) Kubernetes makes workloads portable; data replication (with the consistency-versus-availability trade-off decided per workload) keeps data ready on the other side; and a portable foundation of federated identity, uniform policy, and centralized monitoring makes failover routine rather than heroic. The discipline that separates real resilience from a slide deck is rehearsing failure on purpose. An untested failover path is a promise, not a capability. The Judgment Layer Across every layer, value came not from the most powerful component but from the judgment applied to it: matching effort to problem difficulty, keeping humans on consequential decisions, measuring before deciding, building governance in early, and designing for change. Tools will churn; foundation models will make today's designs look quaint. That is precisely why principles outlast product knowledge. The scarce resource in enterprise AI was never intelligence. It was judgment, and judgment does not ship from the cloud.
TL;DR: A Déjà-Vu? AI adoption seems to be scaling: 37% of respondents in McKinsey’s 2026 survey report an EBIT effect from AI, and Gartner finds that 22% of organizations have scaled it across business units. Now, Agile practitioners have seen this combination before, as AI transformations and Agile transformations rhyme. Five classic failure patterns from Agile transformation adventures are back under new names: mandates from above, licenses mistaken for training, greenfield showcases, parachuted consultants, and promised payroll savings dressed up as strategy. They share one condition: organizations make AI decisions at organizational scale without leaving inspectable evidence at the workflow level in the trenches. And for good measure, let us throw in ignoring culture and excluding most of the organization’s people in the process. History Does Not Repeat Itself, but AI Transformations and Agile Transformations Do Rhyme AI transformations in large organizations are scaling, individual productivity is up, leaders still plan to increase spending, and yet enterprise financial impact remains limited: McKinsey's 2026 State of AI survey (1,719 respondents, fieldwork May 4 to June 8, 2026) puts numbers on three of the four: 44 percent of respondents say AI is scaling across their enterprise, up from 38 percent a year earlier; 80 percent of those who use AI report improved individual productivity; and 37 percent attribute any EBIT impact to AI at all, with the "AI high performer" group flat at about 6 percent.Gartner's September 2026 survey of 1,303 respondents from organizations with at least $50 million in annual revenue supplies the spending picture: 85 percent of functional leaders plan to increase AI spending in 2026, 22 percent of organizations have scaled AI across multiple business units or adopted an AI-first approach, and 11 percent do not know what their function spent on AI in 2025. Something is happening, and something is also not translating. Agile practitioners have seen that combination before. "History does not repeat itself, but it rhymes," a line widely attributed to Mark Twain despite no evidence that he said it; the attribution to Twain dates back to 1970. The attribution is shaky; nevertheless, the observation holds. I wrote the Scrum Anti-Patterns Guide about what organizations do to Agile when they adopt it from the top down. The same organizations are now doing the same things to AI, with a new generation of leaders who consider the Agile years ancient history, and five rhymes stand out. The Five Rhymes of AI Transformations Rhyme 1: The Mandate From Above IBM's June 2026 study of 2,000 C-level technology executives found that 80% reported CEO-driven AI transformation mandates, and 77% said adoption is already outpacing their governance capabilities. The most public example is Shopify. In a late-March 2025 memo that he later posted on X, CEO Tobi Lütke told the company that "reflexive AI usage is now a baseline expectation at Shopify" and that, before asking for more headcount, teams "must demonstrate why they cannot get what they want done using AI," as Tom's Hardware and TechCrunch reported. Whether that works at Shopify, I cannot judge from the outside. What I can judge is the predictable risk when that kind of memo lands in an organization where governance is already falling behind: visible compliance and invisible workarounds. Agile practitioners remember the memo announcing "we are now an agile organization" and the Sprint Reviews that followed, which were ignored by everyone who could change a decision. Rhyme 2: The Belief That This Time Training Is Optional The Agile version bought a two-day certification class and called it a transformation. Often, AI transformations skip even that: buy Copilot or ChatGPT Enterprise licenses, send an email, done. The tool is "intuitive," so the reasoning goes; it is sold as the classic example of learning by applying. Lütke's own memo contradicts this, noting that "using AI well is a skill that needs to be carefully learned." The McKinsey gap between 80% reporting individual productivity gains and 37% reporting any EBIT impact shows why individual productivity is a poor proxy for organizational change. Individuals may get more productive, whatever that means in this context, which does not imply that the organization has changed at the same time. The same survey shows where the difference lies: nearly three-quarters of high performers report fundamentally redesigning workflows because of AI, against one-quarter of everyone else. Deloitte's June 2026 pulse check of nearly 3,700 professionals found that 48% were adding AI without redesigning workflows or roles, and only 12% were redesigning workflows or roles at scale. The divide runs between organizations that change the nature of work and those that bolt AI onto whatever structure they have. Rhyme 3: The Greenfield Showcase Every transformation needs a success story for the board, so a team with no dependencies on the legacy systems, no regulatory exposure, and no operational duty builds something impressive. The Agile version was the "pilot team" in the innovation lab with the fancy toys. The AI version is the internal chatbot that answers HR policy questions and was presented at the town hall as evidence of AI's great potential. Exploration detached from production constraints is useful. The anti-pattern is mistaking evidence that something can be built for evidence that the organization has created value. BCG's 2025 survey of 1,250 senior executives found that 70% of AI's potential value sits in core business functions such as sales and marketing, manufacturing, supply chain, and pricing, which is where the showcase usually never goes, due to the "unsexiness" of the use cases. Rhyme 4: The Consultancy That Sets It Up for You In come the slide decks, the "AI transformation office," and the currently fashionable forward-deployed engineers. The role name dates back to Palantir in the early 2010s; the practice is far older. Thomas Otter, who spent years at SAP, notes that "early chunks of SAP R/1 were built at ICI and John Deere," decades before anyone called the practice forward deployment. I do not consider the practice an anti-pattern. Engineers who join the organization, learn its culture, and stay long enough to hand over applications built on understanding are legitimate. The anti-pattern is the parachute version: the engineers arrive, do the tactical technical work, and leave, and the organization is now running systems it cannot explain. Agile had the consultancy-staffed transformation office that left when the budget line ended. Rhyme 5: The Cost Story Ask most leadership teams why the organization adopts AI, and you get a story about new business, better products, and faster learning. Ask what the business case they signed off actually contains, and you find payroll. Consultancies, in my observation, sell AI as they sold offshoring: a way to remove people who do repetitive work. Cost reduction, as such, is not the anti-pattern; however, turning it into the transformation objective is. About 80% of McKinsey's high performers, and everyone else, pursue efficiency, but most high performers also pursue growth or innovation, thereby distinguishing the two approaches. Klarna ran the other experiment in public. After claiming its AI assistant did the work of 700 customer service agents, CEO Sebastian Siemiatkowski told Bloomberg in May 2025, as CX Dive reported, that "cost unfortunately seems to have been a too predominant evaluation factor when organizing this; what you end up having is lower quality," and started hiring humans again. McKinsey's respondents have noticed which story their leadership actually believes: 39% now expect AI-related job cuts, up from 32% a year earlier. Agile had the same split. The board heard "faster and cheaper"; the teams heard "better products"; and when the two stories collided, the teams lost. What the Five Rhymes of AI Transformations Share Each AI transformation rhyme has a visibility problem. Leadership can see the headcount numbers perfectly well and still optimize them; a consultancy dependency is a capability-transfer problem, while a mandate is an authority and incentive problem. What the five have in common sits one level down. In each case, the organization makes its AI decisions at organizational scale (a mandate, a license contract, a showcase budget, a vendor engagement, a business case) without leaving inspectable evidence at the workflow scale. Too often, nobody can show, for a specific workflow, who decided that AI would do this work, on what terms, under what cost constraints, who checked it, and with what result. Visibility is the symptom, and missing evidence is the condition. Scrum already had low-tech answers to similar problems: an ordered Product Backlog, an explicit Definition of Done, and a recurring Retrospective. None of them needed a platform, and none of them made leadership act. What they did was let a team generate evidence about the system it worked inside. The A3 Delegation System borrows that design principle: make consequential decisions visible before buying another layer of tooling to manage them. Six stages (Decide, Route, Hand Over, Define Done, Inspect, Roll Up), seven artifacts, and no software beyond the AI the team already uses. It is an operating discipline for AI delegation, one workflow at a time, and the evidence is a byproduct of doing the work. Where Each Rhyme Meets a Countermeasure Let us come back to the five "rhymes" and how the A3 Delegation System can mitigate these AI transformation issues: The AI Workflow Inventory makes the license fallacy and the showcase inspectable: Before anything else, the team lists the workflows it already hands to AI, each with an owner. It takes an hour, and the assumption that "people will figure it out" collapses once the list shows what they figured out. You may discover personal AI habits that were never treated as organizational workflows at all, including some touching sensitive data. The inventory also refuses the greenfield showcase by construction. Only existing workflows with a named owner enter it. The A3 Framework decision and the Routing Policy put a countermeasure against the mandate: For each inventory entry, the team decides Assist (AI drafts, you decide), Automate (delegate execution, not responsibility), or Avoid (the cost of failure is trust). Then it routes the work to a model tier by stakes and cost. Leadership can set the boundaries: approved tools, prohibited data, risk limits, or spending constraints. It cannot make the workflow-specific delegation decision from a company-wide memo; the people who know the work can do so in minutes per entry, and the decision is then on paper for leadership to read. Routing is also where the token bill becomes a decision, and precision matters here: while the price per token keeps falling, the cost of operating AI keeps rising, because cheaper tokens invite longer, more autonomous workflows that consume far more of them. Gartner predicted in August 2026 that inference costs per agentic workflow will rise more than fivefold through 2028; its analyst, Will Sommer, said, "Product leaders cannot rely on more efficient token economics to rationalize AI costs." That is the economic problem the Routing Policy addresses at the workflow level: expensive intelligence is a deliberate choice, never a default. The A3 Handoff Canvas and the AI Definition of Done make the parachute inspectable: Six fields (task split, inputs, outputs, validation, failure response, records) and a one-page quality standard per task class. Here is the ownership test for anything a consultancy or a forward-deployed engineer built: can the team fill in these two documents for the system without calling the vendor? If yes, the team owns the delegation, whoever set it up. If no, the organization is renting understanding, and the rent comes due when the engineers leave. The Delegation Audit asks one question, and it is not the cost question: Monthly or every other Sprint, 45 to 60 minutes, four checks: output and source drift, model fit, reversibility, and category creep (Assist work that quietly became unreviewed Automate). Each finding gets an owner and a decision: change the A3 category, change the tier, update the AI Definition of Done, fix the stop rule, or retire the delegation. The Audit asks whether this delegation is still sound. It does not ask what the freed capacity produced. Roll Up, the last stage, compiles what the audits show for those who ask. Whether what they show is worth paying for is a leadership decision, and it sits outside the A3 Delegation System. The system produces evidence for the value conversation, but it does not own the value decision. The AI Working Agreement wraps the other six: It records the team's rules on data, disclosure, responsibility, and review, and it is the document the team hands upward when leadership asks what "AI adoption" looks like here. It is a page that beats a slide on every occasion. Where the A3 Delegation System Stops A skeptical reader, and my readers have watched frameworks overclaim for twenty years, will ask the obvious question: am I criticizing consultancies for selling transformation frameworks and then selling my own? A3 is not an AI-transformation methodology. It is an evidence-generating delegation discipline. It cannot make leadership respond to the evidence. It can make ignoring the evidence harder. It does not determine why your organization adopts AI, nor does it replace a strategy, a portfolio decision, or the conversation about what happens to the people whose repetitive work disappears. What it does is make the absence of those decisions visible within weeks, team by team, in writing, for the price of a few hours. There is a second limit: A3 can tell you whether AI should do a piece of work, which model, what it needs, what acceptable output means, whether the delegation has drifted, who owns it, and what it is allowed to cost. It does not tell you whether the workflow should exist. Suppose the system reduces a weekly reporting workflow from 4 hours to 40 minutes, with excellent output and impeccable governance. The question that remains is why the organization produces that report at all. The A3 Delegation system can prevent undisciplined delegation. It cannot prevent an organization from competently automating work that should have disappeared. The likely next development step of the A3 system is a single field on the AI Workflow Inventory, not another canvas: what changes if this workflow works? I have not added it yet. The team should expect the visibility A3 produces to be unwelcome. A team that runs the inventory, the decisions, and the Audit inside a mandate-driven transformation produces evidence the organization may refuse to absorb. I have watched organizations refuse the evidence their Retrospectives produced for years, and the refusal told the teams more about the transformation than any all-hands did. If your leadership will not read a one-page working agreement and a monthly audit log, you have learned what the AI transformation is for. Conclusion AI transformations may repeat many of the mistakes of Agile transformations. The A3 Delegation System does not prevent organizations from making them. However, it gives teams a simple way to make some of them visible before they become expensive: Count how many of the five rhymes are playing in your organization right now. Respect yourself and be honest while aggregating those. Then put a document against one of them.