← All articles

Rubble / Blog

What Is Software Engineering Still Solving in the AI Era?

Today, an application can go from a description of the requirements to testing, packaging, and deployment in just a few minutes.

Developers no longer need to search through documentation page by page or write every script by hand. AI can recognize the project structure, install dependencies, generate pipelines, fix errors based on execution results, and finally deploy the application into an environment where it can actually serve users.

From the perspective of the person completing the task, much of the familiar software engineering work really does seem to have disappeared. Something that once took days may now be reduced to one sentence describing the goal and a few rounds of confirmation.

This change is real. A great deal of manual implementation and operational work will decline, and roles whose main value lies in those activities may shrink with it. Platforms and AI may genuinely reduce the total cost of producing software, rather than merely shifting the same work to another group of people.

But the question here is not how many software engineers will still be needed in the future, nor is it to argue that the cost of software engineering must increase. The real question is:

When an implementation has to run for a long time, preserve state, be depended on by other systems, and begin to produce real-world consequences, which problems disappear as implementation and deployment get easier, and which still need to be handled by platforms, organizations, or automation mechanisms?

The previous article used Build Engineering as the entry point because it makes one transition particularly easy to see: how code that can be deleted and rewritten at any time starts becoming software that has to be tested, released, maintained, and held accountable for its consequences.

Let's follow that transition further: how does an implementation that can always be regenerated begin to touch state, dependencies, permissions, and responsibility, and become increasingly difficult to replace directly?

One Success Only Shows That One Path Worked

Suppose AI generates deployment scripts for an application, fetches its runtime dependencies, completes testing and packaging, and finally deploys it successfully.

That success shows at least one thing: under the code, dependency versions, network conditions, permission configuration, and platform state at that moment, there was a path that could complete the task.

For a short-lived, very low-risk internal tool, that may already be enough. Software engineering does not require every temporary program to have complete software supply chain evidence, cross-environment verification, and a long-term maintenance system. The appropriate level of engineering has always depended on the software's purpose, lifetime, and consequences of failure.

But one success does not automatically answer what comes next.

After the code changes, can it still produce the required result? Will a dependency upgrade change the application's behavior? How will it degrade when an external service is unavailable? Did this success depend on an undeclared package in the environment? What permissions did the deployment script actually receive? Can the data be migrated and recovered? Can a new version still read the existing state?

Developers do not necessarily need to handle all of these questions themselves, nor do they all need to be solved before the first deployment. But once the software begins to be used continuously, they do not disappear just because the first deployment went smoothly.

Two things are easy to confuse here.

The first is that the cost of implementing and executing a known path is falling quickly.

The second is whether the cost of keeping software understandable, controllable, and recoverable through continuous change, failure, and long-term operation is falling at the same rate.

The first change is already obvious. The second may also happen through platforms, standardization, and further automation, but one successful deployment is not enough to establish that.

Platforms Really Do Make Some Engineering Work Disappear

AI being able to deploy an application from a single sentence is not just the result of smarter models.

It also depends on an environment that has already been heavily engineered.

Cloud platforms turn compute resources, networking, certificates, domain names, container runtimes, logging, scaling, and access control into stable interfaces. Package managers define how dependencies are described, resolved, and downloaded. Application frameworks establish conventions for project structure, startup, and configuration. CI systems provide repeatable execution environments. Platform APIs then combine these capabilities into operations that software can control.

AI is not facing a completely open and unstructured real world. It is working with a set of engineering interfaces that have been designed over many years to accept clear inputs and return clear feedback.

Platforms also do more than move the application team's work to another layer.

Once a managed platform handles certificate issuance and renewal in a standardized way, every application no longer needs its own copy of the same manual process. If a platform allows only a small number of validated deployment paths, many custom scripts, environment combinations, and the failures associated with them disappear entirely.

Platforms are often most effective not when they automate a hundred deployment paths, but when most of those paths are no longer options.

The fact that application teams no longer manage physical servers does not mean a platform team has taken over the same number of manual operations one server at a time. Standardization, shared implementations, and multi-tenant platforms can reduce both labor cost and failure rates.

But relationships the platform has not standardized still remain.

Data state, business permissions, external dependencies, compatibility requirements, and special runtime conditions are not automatically handled just because the deployment interface becomes simpler. The platform itself may also introduce new vendor dependencies, control-plane complexity, and internal state that application teams cannot see.

So the more useful question is not simply, "Where did the complexity go?" It is:

Which problems were eliminated through standardization? Which were shared and amortized? Which merely moved somewhere else? And what new relationships did the platform introduce?

When the same AI enters environments full of legacy systems, proprietary networks, specialized hardware, historical data, and internal security rules, its effectiveness often drops noticeably.

That does not necessarily mean the AI suddenly became less capable. A more common reason is that these environments still contain many relationships that have not been standardized. Real dependencies are not completely recorded, sources of truth are unreliable, and systems differ enough that unifying them behind one interface may not be worthwhile or even possible.

Platforms can eliminate choices, paths, and repeated work once they have been standardized. What remains comes mainly from the relationship between a specific change and the existing state, dependencies, permissions, and responsibilities around it.

From a systems engineering perspective, work around a system's technical identity, composition, and change can be understood as part of Configuration Management (CM). A source repository can provide a source of truth for source state. But to know what an artifact was actually produced from, and what a change will affect, dependencies, toolchains, configurations, environments, and derivation relationships between artifacts also need to be represented in a traceable and revisable engineering model.

Once an implementation begins to touch these relationships, it is no longer just a solution that can be deleted and recreated at any time.

How an Implementation Becomes a Change to an Existing System

The previous article summarized this asymmetry as: "Implementations can be rewritten; system relationships cannot be reset as easily."

Code that AI has just generated is usually easy to replace. If the direction turns out to be wrong, it can be deleted and regenerated. Trying another implementation may not cost much either.

We can call an implementation that has not yet been accepted by the system and can still be freely replaced a candidate implementation.

The difficult part begins when that candidate implementation is about to enter a system that is already running. The system is not blank. It already contains two kinds of relationships.

The first consists of facts already established in the system.

Data has already been written in a particular format. Some behavior has already been deployed and used. Components already depend on one another. Interfaces are already being called by other systems. These facts directly affect future compatibility, migration, recovery, and verification.

The second consists of commitments the system has made externally.

Public interfaces, customer contracts, delivery schedules, user expectations, and regulatory, certification, or compliance obligations can all constrain how the system may change later. Some of these commitments may exist before the first line of code is written.

System facts and external commitments do not appear at the same time.

A piece of code may already be included in a production artifact but still be isolated behind a feature flag. It has not written any data and has not been used by external users. Deployment has happened, but the new system facts and external commitments are still limited.

Conversely, a feature may not yet have been implemented, while its public interface, delivery contract, and compliance requirements have already been fixed. The implementation is still free to take different forms, but the number of conditions the future system has to satisfy has already increased.

A release is therefore not a single boundary where every constraint takes effect at once.

When an implementation has to be assessed together with existing data, dependencies, permission boundaries, and external commitments, it moves from being a candidate implementation to a candidate change against an existing system.

A candidate change first has to deal with facts and commitments that already exist. If the system accepts it, the change will go on to write new data, establish new dependencies, create new behavioral expectations, and leave behind new long-term responsibilities.

We can call the impact that a change has to deal with now its change load.

Change load is not the number of lines changed. It is which existing relationships the modification touches. Whether those relationships contain important state, whether they can be undone quickly, whether the impact crosses multiple systems, and how much verification and coordination they require all change the actual load.

We can call the long-term obligations created after a change is accepted its commitment cost.

That cost may come from new data formats, public APIs, message protocols, permission models, user expectations, or contractual obligations. Maintaining, changing, or eventually leaving those relationships will require compatibility work, migration, verification, coordination, and responsibility.

Change load and commitment cost are usually related, but they are not the same thing.

An internal refactoring may require extensive changes to low-level code and therefore carry a high current change load. But if it does not alter data, interfaces, or external behavior, it may add very little long-term commitment.

A public API may be easy to implement the first time. Once external users depend on it, however, changing or retiring it later may become very expensive.

A change accepted today becomes part of the system that tomorrow's changes have to deal with. The data it writes, the dependencies it establishes, and the commitments it creates become existing conditions for the next change.

Long-lived software builds up history through repeated acceptance and further change. Existing relationships can be migrated, retired, or ended, but they usually cannot be cleared away at no cost.

This is not limited to systems with fixed hardware.

Hardware introduces constraints around resources, timing, product variants, and certification. Pure software systems do not have the same physical constraints, but they gradually accumulate data formats, public interfaces, message protocols, permission models, contracts, and institutional requirements.

The sources of these constraints differ, but they all reduce the space in which the system can make future choices freely.

Engineering therefore does more than show that an implementation can work. It also identifies existing commitments, avoids fixing unnecessary relationships too early, breaks high-risk changes into steps that can be observed and reversed, and preserves compatibility windows, migration paths, and exit paths when necessary.

Those transition structures have costs of their own. Compatibility layers, dual-running systems, and staged migrations introduce temporary state, require additional observation, and eventually require old paths to be cleaned up. That complexity is worth carrying only when the options it preserves are valuable enough.

None of these mechanisms can establish that the requirement itself is correct. Reliably putting the wrong goal into the system is still doing the wrong thing.

"What should we build?" and "How do we bring it reliably into a long-lived system?" are different questions. This article is mainly about the second.

Why Change Handling Can Become the Dominant Cost as Implementation Gets Cheaper

The most direct effect of AI is to lower the cost of producing candidate implementations.

It can also help with analysis, testing, debugging, and operations, but these activities are more constrained by existing state, external dependencies, and evidence requirements. AI can quickly generate a database migration script, but that does not make the existing data disappear. It can generate a compatibility strategy, but it still needs to know which systems actually depend on the old behavior.

The previous article called the engineering system's ability to deal with these problems change handling.

It means whether the engineering system can see what a change will touch, establish enough evidence for the important judgments, and make a clear disposition when the change should proceed, be adjusted, or stop.

The changes being handled here are not a set of identical units that can be counted by the number of commits.

A hundred small changes that are isolated from one another, automatically verifiable, and easy to roll back may be easier to handle than one migration involving historical data and a public interface.

Conversely, a system that releases very rarely may still face enormous verification, coordination, and migration costs from a single protocol or database format change.

Multiple small changes can also interact. Each may look safe in isolation, yet together they may widen dependency version ranges, introduce configuration conflicts, or affect the same downstream systems at the same time.

Change load can also usually be estimated only roughly before work begins. Actual build, verification, and runtime results may expose dependencies and failure modes that were not known before, forcing the engineering system to reassess the impact scope.

Even before the engineering system is overloaded, impact analysis, verification, and risk handling may become the dominant cost of a change if implementation costs fall faster than those other costs.

Time once spent mainly on writing code may instead go into a few different questions: What does this modification actually affect? What evidence is needed? Can the system recover if it fails? Should the change continue entering the system at all?

Only when the continuing change load approaches or exceeds some specific handling capacity does the problem further show up as queues, weaker admission criteria, and accumulating long-term complexity.

Engineering capacity is not one uniform pool.

Database migration, security assessment, hardware verification, and cross-team coordination depend on different capabilities. The development organization may appear to have spare capacity overall while still being unable to handle a particular type of change in time.

Fully described, change handling includes three things:

Understanding which existing relationships a candidate change will touch; establishing evidence proportionate to the risk; and determining what new facts and commitments will be created if the change is accepted, then deciding whether to accept, split, defer, narrow, reject, or redesign it.

The engineering system does not decide every product, contractual, or organizational commitment on its own. But it does need to make clear how a change will fulfill, expand, or violate those commitments, and it needs to prevent unauthorized consequences from quietly entering the system.

Change handling therefore cannot be measured by how many tasks a pipeline processes per hour, or by a simple acceptance or rejection rate.

If an engineering process only helps every change find some way to pass, but has no real backpressure or authority to block a change, release volume may increase while the system continues accumulating long-term complexity.

Conversely, if the standard path simply rejects every necessary difference that exists in reality, the complexity does not disappear. Teams will turn to private scripts, manual operations, and exception paths that the platform cannot see, eventually making it harder for the organization to understand how the system actually works.

A good engineering system allows acceptable changes to move forward in time, forces unsuitable changes to change shape or stop early, and does so without diluting existing permission boundaries or evidence requirements.

Platforms and architecture affect change load, change handling, and future commitment cost at the same time.

Standardized interfaces, fewer optional states, and stable module boundaries can reduce the number of relationships that a modification has to touch. Automated tests, execution isolation, impact analysis, and recovery mechanisms can improve the engineering system's ability to establish evidence and handle failure.

Better design also changes the shape of the change itself, making it more local, easier to observe, and easier to stop or reverse. That lowers the current load while also avoiding unnecessary new relationships in the system.

Three Boundaries the Engineering System Needs to Manage

To handle a change, the engineering system needs to answer at least three questions:

What can affect the result?

What does the existing evidence actually support?

Who has the authority to let this continue?

These correspond to causal scope, evidence scope, and decision authority.

What Can Affect the Result: Causal Scope

Causal scope is about which inputs, states, and external conditions are allowed to affect an artifact or runtime result.

Source code, dependencies, toolchains, configuration, network resources, models, data, and callable tools can all become part of the actual causal chain.

Writing those inputs into a declaration is only the first step. What matters is whether actual execution still respects that scope.

If a build script can read undeclared files from the host, invoke tools that merely happen to exist in the environment, or access the network outside a controlled entry point, then the inputs in the declaration are not the same as the inputs that actually affect the result.

The same applies to agent systems. A system may declare that an agent can use only a few tools, but if the agent can bypass them and access other resources directly, the actual causal scope has already grown beyond what was declared.

Causal scope must be enforceable, observable, and verifiable. It cannot exist only in a configuration file or design document.

What Does the Existing Evidence Actually Support: Evidence Scope

A test, evaluation, or runtime record does not broadly establish that "there is nothing wrong with this software."

It supports a more specific claim: which object, under which environment and at what time, satisfied the conditions that were actually checked.

First, evidence needs to be bound to an object with a clear identity.

The "stable artifact" emphasized in the previous article serves exactly this purpose. Test results, security checks, and release records need to identify which artifact they apply to and where that artifact came from. Having the same release artifact pass through testing and release is one common way to reduce the cost of identifying the object, but it does not mean an artifact can physically be built only once.

Second, evidence cannot be transferred freely between different objects.

A reproducible build asks whether another party, given the same source code, the same build instructions, and the environmental conditions the project declares relevant, can independently produce a bit-for-bit identical artifact.

If two stages produce different artifacts, the engineering system needs to establish the properties under which they may be considered equivalent.

Two artifacts having the same functional behavior does not mean their performance, security properties, and provenance are also the same. Passing the same test suite separately only shows that both satisfied the conditions those tests actually checked.

Evidence can transfer from one object to another only as far as the original evidence covers, and only for the properties covered by the artifact equivalence that has been established.

Finally, evidence also needs the necessary degree of independence.

If the same agent uses the same interpretation of a requirement to generate the implementation, the tests, and the explanation one after another, the process may be fully traceable without producing independent evidence.

The question is not simply whether a different model was used. What matters is whether the key evidence can fail together because of the same mistaken assumption.

For higher-risk changes, all important evidence should not depend on the same interpretation of the requirement, the same environmental assumption, the same data source, or the same way of deciding whether a test passes.

For problems with limited impact that are easy to detect and recover from, self-checks performed by the same agent can still be useful. Evidence requirements should be proportionate to the consequences, rather than mechanically pursuing independence for its own sake.

Who Has the Authority to Let It Continue: Decision Authority

Decision authority is about who may accept which residual risks and who may authorize which external side effects.

Build systems, policy engines, and other automation mechanisms can enforce rules that have already been established. A failed test can stop a release. Insufficient permission can reject an operation. An amount above a threshold can require additional authorization.

But an automated system cannot decide that a risk is worth accepting on behalf of the person or organization that bears the consequences simply because it found a path that can pass the rules.

Authority over the project schedule does not imply authority to waive contractual, safety, or regulatory obligations. Having permission to operate in production does not imply authority to change every piece of business data.

If it is unclear who ultimately bears a risk or who has the authority to decide, that uncertainty should itself be exposed. A pipeline or agent should not quietly make the decision for the organization.

None of these three boundaries becomes correct merely because it has been written into a document.

In legacy systems, some of the most dangerous conditions are dependencies nobody knows about, product variants that cannot be completely enumerated, and historical assumptions preserved only in the experience of a few people.

The first step in such a system is not to pretend that the boundaries are already complete. For changes with significant consequences, the engineering system needs to use isolation, observation, shadow running, failure feedback, and progressive modeling to discover relationships that were never expressed before.

Making boundaries explicit does not mean the real system is fully understood. At minimum, the engineering system should record known unknowns, trust assumptions, and verification gaps instead of assuming they do not exist.

Causal scope determines which effects the current system can express, observe, and constrain.

Evidence scope determines how far those effects have been understood and verified.

Decision authority determines who is allowed to let the change continue when uncertainty remains.

Together, they form the basis for accepting, adjusting, deferring, or rejecting a candidate change. If new runtime results expose previously unknown relationships, the original judgment also needs to be reassessed.

How AI Systems Change the Expression and Scope of These Boundaries

AI did not create a completely different set of software engineering problems. But it changes how these boundaries are expressed, and it expands both the causal range that needs to be managed and the real-world consequences involved.

Interaction Will Change; Precise Expression Will Not Disappear

As model capabilities improve, the proportion of implementation that people describe line by line through programming languages will probably continue to decline.

In highly standardized domains, natural language, graphical descriptions, examples, and existing system state may become the main interface between people and software production systems. More code will be generated by AI and then executed and checked by tools.

But causal scope, evidence scope, and decision authority cannot exist only inside a temporary conversation.

"Deploy this application to the internal network" may already express the main goal clearly enough for a person. An execution system still has to determine which environment to deploy to, which identity to use, which networks and data may be accessed, and what to do if the operation fails.

AI can infer these conditions, but an inference is itself a real choice. Different inferences can produce completely different permission scopes and real-world consequences.

This does not mean every system behavior has to be fixed in advance.

Recommendation systems, schedulers, and interactive agents may intentionally allow dynamic or even probabilistic results. What needs stable expression are the conditions that affect system risk, external side effects, compatibility commitments, and responsibility.

The parts that are allowed to vary also need an explicit range of variation and limits that cannot be crossed.

Programming languages may gradually move behind the interaction layer, but precise, stable, and checkable forms of expression cannot disappear. Otherwise, every execution becomes another fresh interpretation of intent.

Agent Runtimes Expand the Causal and Evidence Scope

Much of a traditional program's behavior may already be contained in its compiled artifact.

In a system where agents act as execution nodes, final behavior may also depend on the model, system prompt, retrieved data, callable tools, external service state, current time, session state, and runtime permissions.

Even if the code and artifact do not change at all, a change in any of these runtime conditions may produce a different result.

Both build processes and runtime processes need provenance, authorization, and traceability, but they cannot use exactly the same kinds of guarantees.

A hermetic build can constrain inputs, isolate the environment, and aim for an artifact with a stable identity or for a reproducible build.

An open, probabilistic runtime keeps receiving external inputs, interacting with changing data and services, and may produce external side effects that cannot be undone. Even if the model, prompts, and retrieved content are recorded, reproducing exactly the same execution trace may still be impossible.

Such systems therefore do not need complete determinism.

Some conditions have to become hard constraints that cannot be crossed, such as permission scopes, monetary limits, and data that must never be accessed.

Some properties can only be evaluated statistically, such as error rates, the frequency of dangerous actions, and the distribution of outcomes.

The engineering system also needs to define in advance when to stop high-risk actions, when to fall back to a previously verified path, when to isolate a capability, and which changes should trigger reassessment.

That does not mean every anomaly should stop the entire system. Different consequences require different stopping and degradation conditions.

Runtime evidence also has a specific object, environment, and time scope. After a model upgrade, an external API change, a shift in data distribution, an expansion of permissions, or the introduction of a new tool, earlier tests and evaluations may no longer be enough to support the original conclusion.

Evidence collection itself is constrained by privacy, sensitive data, and retention cost. The goal should be to preserve enough evidence for specific engineering judgments, not to record everything that might possibly affect a result forever.

Agents Connect Decision Authority Directly to Real-World Actions

When an agent can search for information, modify configuration, call services, and execute multi-step operations, model output is no longer just text. It may become the starting point of a real-world action.

A model may propose an action. The authority to execute it must come from outside the model.

The system needs to define in advance which sources and channels have authority to issue instructions. Even if untrusted content influences the action proposed by the model, that must not give the content any new authority to execute the action.

Specific tool calls and their parameters should still pass through permission and policy decisions outside the model. The same agent must not be able to modify or bypass those decisions arbitrarily.

A policy enforcement mechanism does not first need to prove that a piece of natural language is "not an instruction" in some semantic sense. It needs to guarantee that, regardless of how the model interprets the content, that interpretation cannot break through established boundaries on capabilities, parameters, and side effects.

External policy is not automatically correct either. It also needs versioning, testing, observation, and auditing.

Its value is that it separates action generation from action authorization, so that one model error does not control both the interpretation of the goal and the final authorization.

Correct permissions do not make the action correct.

An agent can misunderstand the goal while operating entirely within its legitimate permissions. It may also retry and repeat an operation that is not safely idempotent. The risk depends on its level of autonomy, permission scope, action frequency, blast radius, and reversibility, not just on how intelligent the model appears to be.

External mechanisms cannot establish that the business intent itself is correct. But they can limit the scope of incorrect actions, introduce independent decision points for high-consequence operations, and prevent one model misunderstanding from becoming an uncontrolled real-world consequence.

AI lowers the cost of proposing and executing actions. It also extends the causal scope, evidence scope, and decision authority that engineering systems need to manage from pre-release change control into continuous runtime operation.

What Work Will Disappear, and When More Engineering Is Not Needed

There is no need to deny that a great deal of work will be automated when discussing what software engineering still has to solve.

Combining existing components according to documentation, writing common deployment scripts, fixing ordinary dependency problems, and repeatedly adjusting environments based on logs are all becoming increasingly suitable for AI. Once mature platforms provide sufficiently clear interfaces, people may not even need to understand most of the underlying implementation details.

If the main value of a role remains centered on these activities for a long time, that role may indeed shrink or disappear.

The fact that software still needs reliability, permission control, and recovery capabilities does not imply that every application team needs to preserve the same number of people and the same division of responsibilities as before.

These capabilities can be concentrated in the platform layer. Shared mechanisms and unified controls can also reduce the marginal cost for each application. Much of the work that individual teams once handled separately can be solved once by the platform.

Likewise, not every piece of software deserves a complete set of control mechanisms.

For a short-lived tool with no important state, limited impact, no need to maintain compatibility with older versions, and a failure mode that allows it simply to be deleted and recreated, very lightweight engineering controls may be entirely reasonable.

Engineering is not about making every system as complex as possible. It is about deciding which relationships are worth managing explicitly based on lifetime, state, dependencies, permissions, and consequences of failure.

The real danger with temporary tools is usually not that they started with too little engineering. It is that they continue to be treated as temporary after people begin depending on them for the long term, after they start storing important state, or after they gain more powerful permissions.

As the relationships and real-world consequences carried by software increase, conditions that were once reasonable to ignore gradually need to be expressed, verified, and controlled.

Conclusion

AI and platforms can genuinely reduce, and even eliminate, large amounts of implementation, testing, and deployment cost. Software engineering does not need to expand a one-sentence goal back into more manual steps simply to prove that it still exists.

As implementation and deployment stop being scarce, software engineering increasingly has to deal with how an implementation that can be rewritten at any time becomes a change against an existing system; which established facts and external commitments it touches; and which new long-term relationships it will leave behind if accepted.

The engineering system needs to understand the current change load, determine what new facts and commitments the change will create, and use causal scope, evidence scope, and decision authority to decide whether it should be accepted, adjusted, deferred, or rejected.

Implementations may become easier and easier to redo. But the relationships created by an accepted change become part of the history that the next change has to face.

Software engineering has to manage how that history is created, how it changes, and who ultimately bears the consequences.

About the author

Chai Feng is a Senior Technical Consultant and Team Coach at Odd-e, with over 20 years of experience in software and hardware R&D and engineering management. His work focuses on modern software development, software architecture, large-scale software engineering, build and CI/CD systems, and AI-assisted development.

Odd-e · Website · GitHub