Building a Software Factory: The Night My Agents Waited for Me

Late one evening, I started two agent runs and went to bed expecting to wake up to merged pull requests. They were working on Simlock, an open-source tool I maintain at Callstack that hands out iOS simulators and Android emulators to agents. Both features had approved specs, and I had spent days building a workflow that could take them through implementation, review and testing. When an agent asked whether the device tests could run, I said yes and added: "I won't be here to answer your questions." I thought I had given it what it needed to carry on.

By morning, both runs had spent hours waiting for me, for different reasons. One had reached a gap in its tests and used up the review rounds I had allowed. The other had merged a pull request, started the next task, and found a product decision missing from the spec. It left me a question and stopped. At my desk, I would have read those messages, answered them, and watched the work continue. Overnight, each one had become the end of the run.

I thought the hard part was teaching agents to write, review and test code. The part I had missed was teaching the workflow what to do when they needed me. I started changing how the workflow handled permissions, review limits, and unanswered product questions. In this article, I'll walk through those changes and how they helped me build a workflow that can run without constant human intervention.

Where the workflow still needed me

The workflow around Simlock is what I call a software factory: agents, scripts and rules that turn approved work into merged code. A feature splits into tasks, each tracked in a GitHub issue and delivered as a pull request. An agent called the orchestrator coordinates the run, handing implementation, review and testing to separate agents and deciding what comes next from their reports. That arrangement lets different agents concentrate on different jobs. It also means the orchestrator needs an answer whenever a report leaves the next step open.

During the day, agents would ask me to approve the next step. I’d check what they had done and tell them to go ahead. Each exchange felt reasonable on its own, especially when the agent was about to use a shared device or touch the main branch. Across a whole feature, though, I kept returning to the conversation to keep the work moving. I hadn’t realised my presence had become part of the workflow.

An earlier daytime run made the pattern visible: almost all its questions were about permission or process; only one needed a product decision. I’d started turning recurring answers into instructions, but a rule about running tests couldn’t answer what a feature should do. I needed to separate the decisions I could settle once from the choices that would emerge as the agents built something.

Give routine decisions an answer

Device tests boot simulators and emulators on a shared machine, so two runs trying to use them at once can interfere with each other. The agent kept asking whether it could run them, but I was usually making the same decision: proceed when the machine is available. I added a script that holds a lock while the device tests run and updated the instructions to let unattended agents run those tests when the lock is free. When I'm present, it asks once, and my answer covers the whole delivery run, from implementation through review, testing, and merge

Review needed a more explicit ending too. The run that hit the test gap had found that its tests didn't prove startup happened in the required order. It had reached the review limit, so I had to authorize another fix and review. The missing test was added, and the feature finished the next day after further fixes and checks. That work needed verification before it could merge, but every extra round also needed me to let it continue.

I revised the review rules so that now they tell the orchestrator when to attempt another fix and when to hand the work back. Later checks focus on the fixes; corrections to comments and documents get recorded without another review cycle, while an unresolved defect in production code can still stop delivery.

Merging follows the same pattern. Agents go through a script that checks whether the pull request is ready, the automated checks have passed and the branch can merge. Direct merges are denied in the agent's settings, along with commands such as force pushes and releases. I can give the agent permission to finish approved work because that permission comes with conditions it must satisfy. If the merge script refuses, the run has a specific issue to fix or hand back to me. It doesn't need to ask whether I feel ready to merge.

Those changes moved repeated decisions into the workflow. The product question from the night was harder, because I hadn't made the decision at all.

The product decision the spec left open

Simlock was getting a warm pool: devices booted ahead of time so an agent could get one sooner. Requests for devices wait in a queue, and the spec hadn't settled how far ahead the pool should prepare. It could boot a device only for the first request, or prepare devices for the requests waiting behind it as well. That choice reached into an existing part of Simlock that shuts down idle devices. A device prepared for someone further back in the queue could sit unused, get shut down, and then get booted again by the pool.

The agent left a handoff explaining the loop and the alternatives. Preparing only for the first request limits how far ahead the pool works. Preparing for everyone waiting means the shutdown rule has to recognize those devices and keep them alive until they are needed. In the morning, I chose to prepare devices only for the first request. Keeping the existing shutdown rule made the change smaller. I wanted to ship a working pool sooner and improve how it prepared devices later. The agent folded that choice back into the spec and added it to the test list, then resumed implementation and review. The handoff had made the question quick to answer, but the run had still needed me there to answer it.

At the time, I required agents to stop for unresolved product decisions. I had already allowed them to make conservative assumptions about smaller details, provided they recorded those assumptions in the pull request description.The wording of an error message, for example, can usually follow the existing code. An Assumption: line records the question, the choice and why it fits. I can read it afterward and ask for a correction without keeping the run waiting over every detail.

For a while, I drew a firm line: agents could settle implementation details, but product decisions came back to me. I later changed that boundary. The orchestrator could make a provisional product choice, as long as it recorded the reasoning for me to review.

Decide, then tell

I updated the delivery instructions to let the orchestrator decide how a feature should behave when the spec leaves that behavior open. I told it to choose the option closest to the code’s existing behavior and easiest to reverse, then record the choice for my review and continue.

Under the current instructions, the orchestrator could handle the same warm-pool question during an unattended run. It could provisionally choose the queue-head option, record that choice in the spec as pending my review, and add a test for the behavior. The next agent could build against that decision. A Decision taken comment would capture the reasoning and what changing the choice would require, so the run could continue while I was away.

When I came back, I could review both the implementation and the decision behind it. If I wanted the other option, I’d review the consequences described in the decision comment and ask for the corresponding change. While the pull request was open, that answer would start a fix run. After a merge, it would become a follow-up task. Letting the agent continue means accepting that I may ask it to revise some of that work afterward.

I still approve the feature's requirements and architecture before delivery. Those decisions give the agent a starting point for choosing conservatively when a gap appears. A choice that contradicts an accepted architecture decision or a project rule requires the run to stop, as does a required device check that cannot run or a blocking defect left after the final review. The delegated choice still goes through the rest of the checks; the agent has permission to choose within those boundaries.

When a run does stop, it leaves a handoff clear enough for me to pick up the work without reconstructing the conversation. Other tasks that don’t depend on it can continue. A decision made on my behalf needs the same clarity: enough context to understand the choice and change it if needed.

Find the gaps before delivery starts

The warm-pool question belonged in an earlier conversation. I had given the agent permission to build the feature before we had finished deciding how it should interact with code already running around it. A provisional choice could keep delivery moving, but I wanted to discover more of those gaps while we were still planning. I needed the spec session and its second reader to uncover those interactions before I told the delivery agents to begin.

You can start with the points in your own sessions where work waited for you. Look at what your answer contributed: permission to run an operation, a decision about the next review step, or a choice about the product. Put recurring answers into the workflow, and decide which remaining choices you are willing to delegate and review later.

For each question that stops a run, decide whether the workflow should answer it, the agent can make a reversible choice, or delivery needs to wait for you. To apply this beyond individual agent sessions, explore how we build agentic delivery workflows, with verification and human review built into the process.

Next: how an agent interview and a second reader turn a feature request into a spec you can leave an agent to build from.

Insights

Learn more about AI

Building a Software Factory: Write Specs an Agent Can Build From — cover

Nov 6Article

Building a Software Factory: Write Specs an Agent Can Build From

Learn how to research requirements, resolve open decisions, and write testable specs that AI coding agents can implement without the planning conversation.

 Large-Scale Migrations With Coding Agents: Planning, Verification, and Rollback — cover

Nov 4Article

Large-Scale Migrations With Coding Agents: Planning, Verification, and Rollback

Plan codebase migrations with coding agents using reviewable requirements, verification checks, staged rollouts, and defined rollback criteria.

Conversational Banking: From Customer Questions to Authorized Actions — cover

Nov 2Article

Conversational Banking: From Customer Questions to Authorized Actions

Explore conversational banking architecture through a card-dispute example, covering tool access, authorization, execution, observability, and human handoff.

Your AI Coding Agent Still Needs an Architecture — cover

Oct 22Article

Your AI Coding Agent Still Needs an Architecture

Use explicit contracts, behavior tests, and dependency checks to guide AI coding agents and review their changes through a TypeScript favorites example.

cover

Oct 15Live Stream

Who Left the Agents Running? React Native Software Factories

Join Kewin Wereszczyński and Szymon Chmal to explore what can go wrong in a React Native software factory overnight and the controls that keep agents on track.

cover

Oct 14Podcast

When Coding Gets Faster, What Breaks Next?

Matt Hargett explains how AI moves bottlenecks across testing, deployment, infrastructure, support, and the rest of software delivery.

cover

Oct 12Podcast

AI Coding Agents and Hermes With Tzvetan Mikov

Tzvetan Mikov discusses AI coding agents, context switching, low-level engine optimization, reviewing specs and tests, and his plans for Hermes X.

cover

Oct 12Podcast

State Machines for AI Agents With David Khourshid

David Khourshid explains state machines for AI agents, human approval steps, tracing, and handling invalid model outputs in agent workflows.

cover

Oct 9Article

How to Maximize AI SDLC? Measure the Outer Business Loop, Not the Inner Developer Loop

Connect AI-assisted development to measurable business outcomes. Matt Hargett shares a checklist for assessing user exposure, results, and delivery costs.

cover

Oct 8Article

Building Reliable AI Figma-to-Code Loops

Build an AI Figma-to-code workflow that uses your design system and checks its output. Learn how component mappings, import checks, and visual feedback work.

cover

Oct 7Talk

Automating Mobile QA with Cloud Agents

Michał Pierzchała shows how cloud-based QA agents can test real iOS and Android user flows and bring screenshots, recordings, and logs into pull requests.

cover

Oct 1Live Stream

Can Jev Make AI Agent Decisions Faster?

Explore Jev with Kewin Wereszczyński and Szymon Chmal: how it works, its approach to faster agent decisions, and possible local alternatives.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.