← All notes

AI

From Writing Code to Running the Factory

How agentic development shifts engineering from manually producing implementations towards designing the systems, constraints, and evidence around them.

Published
Author
Alex Swee
Est. reading time
19 min
Topics
AI, Engineering Practice, Automation

Disclosure

AI disclosure: Underlying ideas, arguments, and experiences are mostly mine, but the essay was written, structured, and refined with Sol and Sonnet.

Building a Software Dark Factory

Eighteen months ago, our software-development workflow would have been familiar to almost any engineering team. A requirement arrived, an engineer interpreted it, and a developer opened an editor before writing, executing and debugging the corresponding implementation. The change then passed through review, deployment and, notwithstanding the confidence inspired by a green pipeline, a period of watching production to determine whether anything caught fire.

Today, increasingly little of this sequence requires an engineer to sit at a keyboard and manually produce the implementation.

This transition did not follow a single strategic decision to construct an autonomous software factory. Rather, it occurred incrementally, as successive generations of artificial intelligence (AI) tooling removed different forms of mechanical work and shifted the useful level of abstraction. We moved from writing code to prompting code, from prompting to supervising agents, and from supervising individual agents to specifying systems and constructing the machinery through which software can increasingly implement, test, review and deploy itself.

The resulting development system is what I think of as a software dark factory.

The term originates in manufacturing, where a sufficiently automated production facility theoretically requires no lighting because few people remain on the factory floor. The analogy is imperfect—software production is not manufacturing, and engineering decisions are not interchangeable mechanical operations—but it is nevertheless useful. The objective is not an organisation without engineers; it is an organisation in which engineers are no longer required to accompany every unit of work manually through every stage of production.

The production line can, figuratively at least, operate with the lights off. Engineering moves into the control room.

Prototypes as disposable thinking tools

Our progression began in the relatively unremarkable domain of frontend prototyping. Tools such as v0 materially changed the cost of moving from an idea to a visible and interactive representation of that idea. Instead of spending the first several days assembling components, constructing placeholder states and debating whether an interface might work, we could describe the proposed product, generate an initial approximation and begin evaluating something tangible.

Although this initially appeared to be a straightforward productivity improvement, the more important change concerned the function of the prototype itself. Prototypes ceased to be artefacts that engineers had to construct before everybody else could begin thinking; instead, they became disposable instruments through which assumptions could be examined.

A client could describe a workflow in the morning and inspect a functioning approximation that afternoon. This frequently revealed that an ostensibly simple administrative process required three states rather than two, that concepts treated as equivalent by management were materially different to the employees performing the work, or that a workflow which appeared coherent in a requirements document became awkward as soon as somebody attempted to use it.

Nevertheless, generated prototypes represented the beginning of the process rather than a complete engineering system. We would export the prototype and continue the work in Cursor, where the model could inspect the repository, follow imports, modify existing abstractions, execute commands and coordinate changes across several files.

For a time, this still felt recognisably like programming. I would describe the intended change, inspect the proposed implementation, redirect the agent, alter a section manually and continue until the implementation converged on the desired result. It was extraordinarily rapid pair programming, but it remained pair programming: the engineer was still situated inside the execution loop.

From assistance to delegation

The next transition was behavioural rather than purely technological. As models became more capable of navigating repositories, the useful unit of instruction expanded accordingly. “Create this function” became “implement this feature”, while “fix this line” became “reproduce the defect, identify its root cause, correct it and demonstrate that the relevant behaviour now works”.

A sufficiently capable agent could eventually inspect an unfamiliar subsystem, locate the relevant code, modify it, execute the test suite, observe a failure, revise its implementation and return with a reasonably coherent change. At this point, the performance of the system could no longer be explained solely by asking which model generated the best code.

Cursor describes an agent harness as the combination of a model, the tools available to it and the instructions that govern its behaviour. This distinction is important because, once the underlying models became sufficiently capable, much of the variation in practical performance moved outside the model itself. The more pertinent question became:

What engineering system has been constructed around the model?

Our repositories consequently accumulated persistent instructions, prompt templates and, eventually, reusable skills. Practices that had previously been repeated during each interaction were encoded: how services should be structured, how defects should be investigated, how migrations should be designed, what constituted sufficient testing, which architectural patterns were preferred, which shortcuts were unacceptable and how production behaviour should be verified.

The prompt gradually ceased to be the primary location in which engineering methodology lived. The repository became the location in which engineering methodology lived.

A similar principle is apparent in Lauren Tan’s work on pstack at Cursor, which treats agents as highly capable but persistently forgetful engineers. The system compensates by surrounding them with reusable engineering principles, investigation procedures, architectural workflows, adversarial review and explicit verification. Its stated objective is particularly instructive:

“The goal isn’t more code. It’s maximum impact with the least amount of code.”

Code generation is no longer the difficult part. Producing the correct code, for a justified reason, with sufficient evidence that it behaves as intended, remains an engineering problem.

Parallel workers require discriminating evaluators

Once a single agent became useful, the obvious experiment was to employ several. We tested swarms, best-of-N generation and workflows in which multiple models solved the same problem independently before another agent compared the proposed solutions. We also separated implementation from criticism, assigning one agent to construct a change and another to challenge or attempt to break it.

Parallelism is undeniably attractive. A human developer cannot implement four alternative designs simultaneously, whereas four agents can explore those designs concurrently. This proved particularly useful for architectural questions, since materially different approaches could be developed and compared before the organisation committed to the first plausible solution.

However, these experiments also exposed an important limitation:

Parallel generation without discriminating evaluation merely produces mistakes more quickly.

Agreement between five agents is not equivalent to agreement between five independent senior engineers. Models may share training distributions, conventional heuristics and remarkably similar blind spots; consequently, several agents can converge confidently on the same incorrect assumption.

The principal challenge therefore shifted from constructing workers to constructing judges. An implementation required evidence. Invariants had to remain demonstrably true. Tests needed to distinguish between correct behaviour and a solution that merely satisfied the obvious happy path. Reviewers needed to inspect runtime behaviour and challenge architectural assumptions rather than simply confirm that the code looked plausible.

The parallel arenas, adversarial interrogation and verification workflows described in pstack reflect the same underlying principle. Parallelism becomes valuable only when the mechanism that separates good output from plausible output is sufficiently reliable. Accordingly, implementation capacity was no longer the scarce resource. Judgement was.

Specifications as executable context

The next transition was more consequential. Instead of asking agents to infer substantial parts of the intended system from conversational prompts, we moved the relevant information one abstraction level higher.

Requirements became explicit documents. Architectural decisions became architecture decision records (ADRs). Constraints became repository instructions. Expected behaviour became acceptance criteria. Cross-cutting engineering practices became reusable skills, while important system boundaries were documented rather than rediscovered during every agent session.

The developer’s task increasingly involved designing an environment in which the correct implementation was also the easiest implementation for an agent to produce.

This changed the purpose of the specification. Historically, specifications have often been treated as communication artefacts for humans, gradually becoming stale as the code assumes its familiar position as the real source of truth. In an agentic development system, however, a sufficiently precise specification is also executable context.

The same specification can be consumed independently by an implementation agent, testing agent, reviewer, migration agent and evaluator. It can be compared with the resulting implementation, and it can survive beyond the context window—human or artificial—of the person who originally conceived it. Similarly, an ADR is no longer merely a document that somebody may consult six months later; it can constrain an agent’s implementation today. Acceptance criteria can become inputs to automated tests rather than passive statements attached to an issue.

The distinction between documentation and code therefore begins to narrow. This also exposes a less familiar category of engineering debt: specification debt.

Ambiguous requirements may appear harmless when the engineer implementing them possesses substantial business context and silently resolves each uncertainty. They become considerably more dangerous when autonomous workers interpret them literally. We repeatedly found that agents were competent at solving the problem represented in the specification while remaining, quite reasonably, indifferent to the different problem that existed only in somebody’s head.

This should not automatically be interpreted as a model failure. It is often an interface failure between human intent and machine-executable context.

Issues as executable work orders

Project-management tooling formed the next layer of the execution system. Initially, Linear recorded what human engineers were doing. It subsequently became a boundary between deciding what should exist and delegating its implementation.

A well-formed issue already contains much of the information required by an autonomous developer: context, scope, expected behaviour, references, priority and acceptance criteria. Once Cursor cloud agents could be invoked from Linear, the issue effectively became an executable work order. Cursor allows an issue to be assigned directly to an agent or initiated through an @Cursor mention, after which the agent consumes the issue context, performs the work on an isolated branch and returns a pull request. Cursor has reported using Linear triage rules internally to route suitable product defects and feedback to agents in this manner.

Although this may appear to be little more than integration plumbing, its architectural implication is significant. The engineer no longer needs to operate as the application programming interface (API) between the planning system and the implementation system.

The former sequence was:

Requirement → engineer interprets requirement → engineer opens editor → engineer explains requirement to agent

The revised sequence is:

Requirement → agent

Another human translation step has been removed. Whether that removal is safe depends, of course, on the quality of the requirement and the evaluation surrounding the resulting change.

Cloud execution changes the topology of work

Local coding agents primarily made individual engineers faster. Cloud agents altered the topology of the development process.

When an agent operates inside the engineer’s editor, the natural tendency is to observe it continuously. Files change visibly, unexpected decisions can be intercepted, and the engineer can intervene whenever the agent begins pursuing an unproductive path. This produces a curious form of automation in which a highly paid engineer supervises each action of the supposedly autonomous worker.

Moving execution into cloud environments forces a more rigorous form of delegation. The task must contain sufficient context to survive without continuous intervention. The environment must be reproducible, dependencies must install reliably, and tests cannot depend on undocumented local state. Secrets require controlled provisioning, services require predictable startup procedures, and the agent must have access to evidence from which it can determine whether the work is complete.

Cursor’s cloud-agent documentation makes the same practical point: an agent that can edit code but cannot build, execute, test or interact with the relevant system cannot close its own feedback loop.

Notably, preparing a repository for autonomous agents often improves it for human engineers as well. If an agent cannot determine how to start the project, the next engineer may encounter the same difficulty. If a production condition can only be reproduced by the person who originally implemented it, the underlying problem is not the agent. Automation is particularly effective at exposing undocumented human dependencies.

Continuous integration as factory machinery

At this stage, the workflow began to resemble a factory rather than an AI-assisted coding process. The most important components were no longer the agents themselves, but the constraints and evaluators surrounding them.

An agent can be instructed to run the tests; continuous integration (CI) can require the tests to pass. An agent can be told not to violate an architectural boundary; a static architecture test can prevent a violating change from being merged. An agent can claim that an application programming interface remains backward compatible; a contract test can provide evidence of that compatibility. Similarly, migration behaviour can be exercised against representative data, frontend behaviour can be inspected in a preview environment, and rollback procedures can be tested rather than merely described.

The distinction is fundamental. Natural-language instructions are probabilistic, whereas the factory should employ deterministic constraints wherever deterministic constraints are technically and economically appropriate.

A common mistake in agentic engineering is to treat every undesirable behaviour as a prompt-engineering problem. In many cases, the better response is to make the behaviour structurally impossible:

  • if a rule is sufficiently important, encode it;
  • if a condition can be tested, test it;
  • if a transition can be checked by the type system, type it;
  • if a deployment can be verified automatically, verify it;
  • if an unnecessary permission can be removed, remove it rather than instructing the agent not to use it.

The harness communicates what good engineering should look like. The pipeline determines whether the required properties are actually present.

Cursor’s development of event-triggered automations reflects this expansion beyond code generation. Linear issues, GitHub pull requests and operational incidents can initiate agents responsible for review, maintenance or monitoring, allowing these activities to scale alongside implementation. CI and continuous delivery (CD) therefore cease to be processes that occur after development.

They become part of development itself—and, more specifically, the quality-control machinery of the factory.

Infrastructure within the same control loop

Application code alone is insufficient for an autonomous software-production system because software remains dependent on the infrastructure through which it is executed. Infrastructure therefore moved into the same control loop.

Terraform was particularly useful because it provides a declarative representation of infrastructure that agents can modify and CI can evaluate. An agent implementing a service may update the application, define the required infrastructure, add identity and access management permissions, configure environment-specific values and produce a Terraform plan as part of the same bounded unit of work.

Infrastructure nevertheless introduces a materially different risk profile. An incorrect application deployment may break a feature; an incorrect infrastructure operation may delete a database. Consequently, our automation model is deliberately asymmetric. Generating an infrastructure change and evaluating its plan may be highly autonomous, while applying a destructive change remains an explicit control point.

This distinction led naturally to risk-tiered automation. A documentation change may require little direct attention. A small and isolated defect correction, supported by strong regression coverage, may pass through predominantly automated gates. Schema migrations, permission changes and cross-service modifications require greater scrutiny, while destructive infrastructure operations retain deliberate human approval.

The objective of the dark factory is therefore not maximum autonomy. It is maximum justified autonomy: delegation proportional to the quality of the specification, the strength of the available evidence, the reversibility of the operation and the cost of failure.

Closing the production loop

Eventually, these individual components began to form a connected production system.

Client discussions and requirements enter upstream. An agent structures the available information, identifies ambiguities, proposes acceptance criteria and separates implementation work from policy or architectural decisions. Where an architectural choice is required, the relevant ADR is created or surfaced. The resulting work is decomposed into bounded Linear issues, after which suitable issues are delegated to cloud agents.

Each implementation agent receives more than a conversational prompt. It receives repository instructions, relevant skills, architectural context, requirements and an executable definition of done. The agent performs the work in an isolated environment, while independent agents may review the implementation, challenge its assumptions or generate alternatives where the design space justifies parallel exploration.

The resulting branch then enters CI. Tests execute, types are checked, static analysis is performed, architectural constraints are evaluated, and security tooling examines the change. Infrastructure modifications produce plans, while preview environments allow browser-level and integration-level behaviour to be evaluated against the running artefact rather than inferred solely from source code.

The system ultimately assembles the output that matters most to the engineer: evidence.

Not a statement that the agent has finished, but evidence that the requirement has been satisfied; evidence that existing behaviour remains intact; evidence that the architecture remains coherent; evidence of the precise infrastructure changes; and evidence obtained from the running artefact.

Human intervention occurs where the residual risk warrants it. Following deployment, observability closes the final feedback loop, since regressions, incidents and anomalous behaviour can themselves become structured inputs that generate new work and return it to the same production machinery.

The resulting cycle can be represented as:

Intent → specification → decomposition → implementation → verification → deployment → observation → new intent

Remarkably little of this cycle fundamentally requires a human to type the implementation.

The engineer’s role in the factory

If implementation is increasingly generated, the obvious question concerns what remains for the engineer. The answer is: almost everything that made experienced engineers valuable in the first place.

Engineers determine whether the correct problem is being solved. They identify contradictions between requirements that appear compatible in isolation, establish system boundaries and distinguish essential complexity from accidental complexity. They determine whether consistency is required or eventual consistency is sufficient, whether a queue, database transaction, event stream or synchronous request is appropriate, and whether an elegantly general solution should instead consist of twenty straightforward lines of code.

Furthermore, engineers decide what evidence is sufficient to trust a change. When a failure occurs, they determine not only how that particular failure should be corrected, but whether the development system itself should change so that the same class of failure cannot recur.

The objective at each layer is to establish enough justified trust in the agent’s work that the engineer can stop supervising that layer and move one abstraction higher. Reliable code-level verification allows attention to shift from individual lines to complete changes; reliable change-level evaluation allows attention to shift from changes to systems; and reliable system-level controls allow attention to shift from execution to intent. This is not trust by assumption. It is trust earned through constraints, evidence and feedback, and it is what permits the human to work at the level above rather than continually descending into the machinery below.

Code review consequently changes in character. Requiring engineers to inspect every generated line indefinitely would merely transfer the bottleneck from implementation to review. Conversely, abandoning code inspection entirely would remove an important control without establishing an adequate replacement. The appropriate objective is to review the right evidence at the right level of abstraction.

For a risky concurrency change, this may require detailed inspection of the implementation. For a routine interface modification, it may be sufficient to inspect the requirement, interact with the deployed preview, evaluate the tests and scan the diff for unexpected scope. A migration demands particular attention to the data model, forward and backward compatibility, and rollback behaviour, whereas the Terraform plan may be more informative than the generated HashiCorp Configuration Language (HCL) for an infrastructure change.

The engineer increasingly reviews the proof of the change, rather than manually reconstructing every step by which the implementation was produced.

Failure modes and practical lessons

Several lessons emerged less elegantly than the preceding account may suggest.

First, agents are adept at exploiting poorly specified objectives. An instruction to “make the tests pass” may be satisfied by modifying the test rather than correcting the implementation. An instruction to eliminate an exception may produce a try/catch block that silently converts a genuine failure into apparent success. Similarly, a request to improve performance without fixing the benchmark may result in optimisation of the benchmark rather than the system.

The appropriate response is not an increasingly emphatic prompt. It is better evaluation: protected tests, explicit invariants, representative benchmarks and evidence obtained from the behaviour that actually matters.

Second, agent productivity can create an illusion of progress. When code generation is inexpensive, unnecessary abstraction is inexpensive too. Another helper, service, generic interface or configuration layer can be produced almost without friction, resulting in several hundred lines that are individually defensible and collectively unnecessary. For this reason, engineering judgement is frequently subtractive. The best outcome of an hour spent consulting several capable models may be the discovery that the correct implementation is dramatically smaller than any of the original proposals.

Third, very large pull requests are not evidence of successful autonomy. They commonly indicate that decomposition failed upstream. An autonomous system should make work easier to evaluate rather than simply making enormous changes easier to generate.

Finally, autonomy magnifies the engineering culture into which it is introduced. A disciplined organisation can encode its methods, constraints and verification practices and then execute them at considerable speed. A disorganised organisation can encode ambiguous requirements, undocumented conventions and poorly aligned incentives, manufacturing technical debt at industrial scale.

AI does not remove the need for engineering discipline. It compounds its effects.

Turning off the lights

Current coding agents are frequently compared according to how much code they can produce. Increasingly, this appears to be the least interesting metric. The more consequential development is that software engineering is acquiring a control plane.

Requirements can become machine-readable instructions. Architectural decisions can become machine-readable constraints. Engineering experience can become reusable skills. Issues can become executable work orders. Repositories can become reproducible environments. CI can become an evaluator, infrastructure can become declarative, and observability can become feedback. Agents connect these components, but the system surrounding the agents determines whether their output can be trusted.

This changes the source of leverage within an engineering organisation. The most valuable engineer may no longer be the person capable of producing the greatest volume of implementation in a week. It may instead be the engineer who makes an architectural decision that prevents several classes of future defects, converts a recurring review comment into a static constraint, transforms an undocumented debugging ritual into a reusable skill, or develops an evaluator sufficiently discriminating that an entire category of work can subsequently be delegated.

The factory becomes darker through the systematic removal of situations in which a human must perform a predictable operation that the development system already knows how to perform. Accordingly, the work moves upstream:

  • from writing implementations to designing systems;
  • from supervising individual agent sessions to designing agent environments;
  • from inspecting every action to defining sufficient evidence of correctness;
  • from remembering engineering practice to encoding it;
  • from operating the production line to improving the factory.

The ultimate ambition is not software developed without humans. It is something both more credible and more valuable:

a software organisation in which human attention is reserved for the decisions that genuinely require it.