Machine Coding: Building Software with AI
“First, solve the problem. Then, write the code.”
Summary
Machine coding is no longer just asking an AI to write files for you.
That was the early version. You described an app, the model produced a pile of code, you copied it into a folder, ran it, pasted the errors back, and hoped the loop converged.
That workflow taught us something important: conversation can turn into software. But it also hid the real lesson.
Modern machine coding is a controlled loop between intent, code, tools, tests, evidence, and human judgment.
The model is not the whole system. It is one powerful participant inside a working environment. The repository is state. The terminal is feedback. Tests are memory. Git is recovery. Error messages are observations. A passing check is stronger than a confident explanation.
Freestyle cognition still matters here, but the style has matured:
express intent
-> constrain the work
-> generate a small change
-> run the environment
-> observe evidence
-> repair or commit
-> decide the next move
That is machine coding now.
The Example: Too Much YouTube
Imagine spending hours searching YouTube for five useful minutes. Researchers, creators, and lifelong learners run into this constantly. Videos contain valuable ideas, but the useful parts are buried inside transcripts, descriptions, comments, and long recordings.
The original version of this chapter used “Too Much YouTube” as the example: an AI-assisted tool for collecting YouTube transcripts and surfacing relevant material for active research.
That is still a good machine-coding project because it has the right shape:
- a real user problem;
- messy external data;
- a small useful prototype;
- room for retrieval, summarization, and ranking later;
- clear ways to test whether each part works.
The goal is not to build the perfect research platform in one sitting. The goal is to build one useful slice and prove it against reality.
For this prototype:
- fetch or import transcripts;
- store transcript metadata and text;
- search transcripts by topic;
- return useful segments with source links;
- keep the implementation small enough to understand.
That final point matters. A machine-coded project that you cannot understand is not done. It is a liability with a nice demo.
The Modern Process
The old process looked like this:
prompt -> generated project -> zip -> run -> paste errors -> repeat
The modern process looks like this:
flowchart TD
A[Intent] --> B[Constraints]
B --> C[Repository State]
C --> D[Small Generated Change]
D --> E[Run Checks]
E --> F{Evidence}
F -- Pass --> G[Commit or Mark Baseline]
F -- Fail --> H[Small Repair]
H --> E
G --> I[Choose Next Slice]
I --> B
The difference is not cosmetic. In the old process, the model’s explanation carried too much weight. In the modern process, the environment pushes back.
You still talk freely. You still explore. You still use AI for momentum. But every meaningful step has an outside check.
Start With Intent, Not a Prompt
Do not begin by asking:
Build me an app that summarizes YouTube.
Begin by naming the intended outcome:
I want a small local tool that can ingest one YouTube transcript,
store it with metadata, search it by keyword, and print matching
segments with timestamps. No web UI yet. No accounts. No scheduler.
That is not merely a better prompt. It is a contract for the session.
A useful machine-coding intent has five parts:
| Part | Question |
|---|---|
| Outcome | What should exist when this slice is done? |
| Boundary | What are we explicitly not building yet? |
| Evidence | How will we know it works? |
| State | What files, tests, and data already exist? |
| Risk | What could cause hidden damage or wasted time? |
For Too Much YouTube, a good first intent might be:
Create a CLI command that loads a saved transcript JSON file,
normalizes it into segments, stores it in SQLite, and lets me search
for a phrase. Prove it with unit tests for parsing and one integration
test for the CLI.
That is small enough to build, run, and trust.
Constrain the Work
Constraint is still the first machine-coding superpower, but the reason has changed.
We are not constraining the project because the AI “thinks in components.” We are constraining it because uncontrolled generation creates too many possible failure paths.
Use constraints like these:
- Stack: Python, SQLite, pytest, simple CLI.
- No hidden services: no background workers, no web app, no vector database in the first slice.
- No speculative dependencies: every new package must have a clear job.
- Small files: each module should have a reason to exist.
- Deterministic core: parsing, storage, and search should be testable without a model call.
The first version should be boring. Boring code gives you a stable floor. Once the floor holds, you can add retrieval, embeddings, ranking, summaries, and a UI.
Build the Map Before the Code
Before generating implementation, ask for the project map:
Propose the smallest project structure for this slice.
For each file, tell me:
- why it exists
- what it imports
- what it exports
- how we will test it
Do not write the code yet.
A reasonable answer might be:
tmy/
transcript.py -> parse transcript files into segments
store.py -> create SQLite tables and store/retrieve segments
search.py -> keyword search over stored text
cli.py -> command-line entry point
tests/
test_transcript.py
test_store.py
test_cli.py
This map is not proof. It is a checklist. Later, when the agent edits the repository, compare reality against the map:
- Did every promised file appear?
- Do imports resolve?
- Are there tests for the risky parts?
- Did the implementation stay inside the slice?
- Did it add dependencies without asking?
Machine coding works better when the artifact stays inspectable.
Generate Small Changes
Modern coding agents can edit a repository directly. That is powerful, but it changes the discipline.
Do not ask for a whole application unless the application is tiny. Ask for the next narrow change:
Implement transcript.py and tests/test_transcript.py only.
The parser should accept a list of transcript entries with text,
start time, and duration. Return normalized Segment objects.
Run the tests after editing.
Then let the environment answer.
If tests pass, continue. If they fail, the failure is now part of the conversation:
Here is the failing test output. Diagnose the smallest fix.
Do not change public behavior unless the test is wrong.
This is the key shift:
In machine coding, the most important messages often come from the runtime, not the model.
Verification Is Not Optional
The model can propose. The toolchain must verify.
For a small project, verification can be simple:
- run unit tests;
- run the CLI against a sample file;
- inspect the output;
- check imports;
- check formatting;
- review the diff;
- commit the working state.
For a larger project, verification gets stronger:
- deterministic unit tests for pure logic;
- integration tests for databases, files, and APIs;
- model-backed tests labeled separately;
- security checks for secrets and untrusted input;
- regression tests for previously fixed bugs;
- release notes that say what changed and what was proven.
Do not collapse these into one vague question: “Does it work?”
Ask narrower questions:
| Question | Better evidence |
|---|---|
| Can it parse transcripts? | Parser unit tests |
| Can it store and retrieve data? | SQLite integration test |
| Can a user run it? | CLI smoke test |
| Can it survive bad input? | Edge-case tests |
| Did the change stay scoped? | Diff review |
| Is the model-made code understandable? | Human read-through |
A confident answer is useful. A passing check is better.
Human Judgment Still Owns Direction
There is an old trap in AI-assisted coding: once the model starts producing code, the human becomes a passenger.
That is backwards.
The human should own:
- the goal;
- the scope;
- the product taste;
- the risk tolerance;
- the final acceptance decision;
- the decision to stop, split, revert, or continue.
The agent can own:
- first drafts;
- local refactors;
- test generation;
- bug diagnosis;
- documentation passes;
- tedious mechanical edits;
- alternative implementation candidates.
The tools own:
- whether commands ran;
- whether tests passed;
- what files changed;
- what the current repository state is;
- what external systems actually returned.
When those roles are clear, machine coding becomes calmer. You are not trying to “think like the AI.” You are keeping the work grounded in current state and observable evidence.
When the Agent Drifts
Drift does not mean the model is bad. It means the session has lost contact with the real task.
Warning signs:
- the agent keeps rewriting files that were already working;
- fixes undo earlier fixes;
- new dependencies appear to solve small problems;
- explanations get longer while tests stay red;
- the project grows sideways instead of finishing the slice;
- the agent claims success without running checks;
- the same error returns after multiple repairs.
The repair is not more enthusiasm. The repair is state.
Use a resync prompt:
Stop and resync.
Here is the current goal:
Here is what is already working:
Here is the failing command:
Here is the exact error:
Here are the files changed in this attempt:
Propose the smallest next action. If the current path is too tangled,
say what baseline we should return to.
If the system can read the repository directly, ask it to inspect the tree, tests, and diff. If it cannot, provide the smallest useful slice: file list, failing file, traceback, and current goal.
Step Count Is Not the Compass
The older version of this chapter used step count as a sanity check. That was useful when the workflow lived inside a long chat.
It is less useful now.
A modern coding run can take many internal steps and still be healthy if each step is grounded. A short run can be dangerous if it makes a large unverified change.
Use evidence instead:
| Signal | Healthy |
|---|---|
| Scope | The current slice is still small |
| State | The agent knows the real repository state |
| Tests | Checks are being added or run |
| Diff | Changes are reviewable |
| Recovery | There is a baseline to return to |
| Dependencies | New tools are justified |
| Outcome | Pass, fail, and unknown remain distinct |
The worst state is not failure. The worst state is false progress: a big patch, a confident summary, and no independent evidence.
Git Is Part of the Thinking
Use version control as a cognitive tool.
Before a risky change:
Mark the current passing state.
Do not change more than one subsystem in this next step.
After a useful slice works:
Commit this as the baseline for transcript ingestion.
Summarize what is proven and what is still unproven.
If the next branch fails, you have choices:
- repair forward;
- revert the branch;
- keep the failed attempt as evidence;
- split the task smaller;
- try a different implementation path.
Freestyle cognition does not mean refusing structure. It means using structure so exploration can move without destroying itself.
A Modern Machine-Coding Checklist
Use this before asking an agent to build:
- What is the smallest useful outcome?
- What is explicitly out of scope?
- What files or systems may be changed?
- What commands prove the change works?
- What tests should exist before we trust it?
- What data can be mocked?
- What requires a real external service?
- What is the rollback point?
- What should be committed if it works?
Use this after the agent edits:
- What changed?
- Did the change match the requested scope?
- Were tests added or updated?
- Were checks actually run?
- Did any dependency, schema, or config change?
- What remains unproven?
- Is the next step a repair, a commit, or a new slice?
This is the discipline that turns AI coding from a rush of output into an engineering process.
When Everything Breaks
Sometimes the branch collapses. The tests are red. The model has patched around symptoms. The code is larger and less clear than when you started.
Do not romanticize the loop.
Stop and classify the failure:
- The goal was too vague. Rewrite the intent as a smaller outcome.
- The slice was too large. Keep the useful tests and split the implementation.
- The state was stale. Resync against the real repository.
- The verifier was missing. Add a test before another repair.
- The stack was too ambitious. Return to boring tools.
- The task exceeded the current competence envelope. Bring in a human expert, documentation, or a narrower prototype.
Starting over can be right, but do it deliberately. Preserve the lesson:
- what failed;
- what evidence showed the failure;
- what assumption was wrong;
- what smaller version should come next.
Failure is useful when it becomes an observation. It is waste when it becomes another ungrounded rewrite.
Machine Coding as Amplified Creation
Machine coding still belongs inside freestyle cognition.
You begin with a rough idea. You talk it through. The model helps you shape it. Code appears faster than it used to. That speed can feel astonishing.
But the mature version is less mystical and more powerful:
human intent
+ model variation
+ tool feedback
+ tests
+ version control
+ review
= amplified creation
The point is not to get as close to the AI as possible.
The point is to build a working rhythm where your intent becomes an artifact, the artifact is tested against reality, and each loop teaches you what to do next.
That is the modern version of machine coding: not code from nowhere, and not automation without judgment, but a faster path from thought to verified change.