Skip to content

Capstone Project: Adding a Feature and Committing from Scratch for a TODO Utility

📚 Series Navigation: The previous article "33 Key Windows Considerations" straightened out common Windows pitfalls—paths, line endings, terminals, and sandbox differences. In this article, we won't discuss new features, but instead do something more exciting: assemble all the parts we learned in the first thirty-odd articles into a running machine. The next article "35 Commands and Configurations Cheatsheet" will summarize all the commands and configuration keys in one table, ready for you to reference anytime.

Alright, no lectures today—let's build a small utility together from scratch.

I have a very simple command-line utility—a Python script todo.py that can add and list todos, and that's it. It has a glaring limitation: once items are added and finished, they can't be deleted, so you just watch the list grow longer and longer. In this article, we will take the "delete todo" feature all the way from explaining context, assigning the task, and setting permissions, to self-verification and a git commit, walking through the entire process.

Simply put, each previous article discussed "how to use a specific part"—how to write AGENTS.md, how to craft prompts, how to set permissions, and how to delegate git commits. Individually, they make sense, but when it comes to an actual task, how these parts connect and in what sequence is often confusing. This article is that "assembly blueprint."

I won't assume you already have this project on your computer. In Section 01, I'll walk you through creating todo.py in two minutes, and then you can follow along step-by-step and see the results with your own eyes. This is a "hands-on" guide, not a "conceptual read"—just reading without typing is as good as not reading.

After reading this article, you will get:

  • A complete development flow from scratch to commit, with each step corresponding to a previous article, building muscle memory
  • A copy-pasteable AGENTS.md, a fully structured task prompt, actual commands, and expected outputs
  • Hands-on techniques to "let Codex run tests to verify itself, and not sign off until it passes"
  • How a clean git commit is prepared by Codex, with you acting as the final gatekeeper
  • A comparison table mapping "which article's skills are used in this step," which you can apply to any project in the future

⚠️ All references below to specific commands, parameters, and default behaviors are based on the official Codex documentation. Things like model names and UI text will vary with versions; please refer to your local codex --help and actual displays.


01 First, Set the Target: Build a TODO Utility in Two Minutes

Before we start, we need something to work on. Let's create this project manually first—all subsequent actions will revolve around it.

Analogy: A concrete shell before renovation. Without a house, even the best design blueprint cannot be realized. This todo.py is our shell: simple enough to read in one glance, but with "all the walls in place"—it has data, features, and an entry point, just enough to add a new wall (the delete feature).

Find an empty directory and create todo.py with the following content (same for Mac / Linux / Windows, requires Python 3):

python
# todo.py
import sys

TODOS = []

def add(item):
    TODOS.append(item)
    print(f"已添加:{item}")

def list_todos():
    if not TODOS:
        print("(暂无待办)")
        return
    for i, item in enumerate(TODOS, 1):
        print(f"{i}. {item}")

def main():
    if len(sys.argv) < 2:
        print("用法:python todo.py [add <内容> | list]")
        return
    cmd = sys.argv[1]
    if cmd == "add":
        add(" ".join(sys.argv[2:]))
    elif cmd == "list":
        list_todos()
    else:
        print(f"未知命令:{cmd}")

if __name__ == "__main__":
    main()

Once created, run it to verify it works:

bash
python todo.py add 买咖啡豆
python todo.py list

Expected output:

text
已添加:买咖啡豆
(暂无待办)

Note there is a "feature" here—because each run starts a new process, the in-memory TODOS list is cleared after execution, so the list shown during list is empty. This is not a bug but a setting of our minimal target. Keep it in mind, as we will use it for verification in Section 05.

Finally, place it under Git management (we will commit it later):

bash
git init
git add todo.py
git commit -m "init: TODO 小工具初版"

You should see Git report that a commit has been created. The target is set; let's officially begin.

💡 Summary: Take two minutes to manually create the minimally runnable todo.py target and add it to Git, so subsequent steps have a clear aim.


02 Step 1: Write AGENTS.md to Explain the Project Context Once and for All

Codex starts as a blank slate when entering a project—it does not know what the project is, how to run it, or what rules exist. The first thing to do before starting is to give it a "handover checklist." This is precisely what "11 Project Manual AGENTS.md" is about.

Analogy: Posting a "Site Notice" for a temp worker. No matter how skilled a worker is, if they don't know "where the fuse box is, which wall cannot be demolished, or where to dump trash," they will make a mess. AGENTS.md is that notice on the door that Codex reads before every run.

Create AGENTS.md in the project root, copying this version (short, precise, only containing what it actually needs):

markdown
# TODO Utility

A command-line todo utility using pure Python 3 standard libraries with no third-party dependencies.

## Running
- Add: `python todo.py add <content>`
- List: `python todo.py list`

## Conventions
- Use only the standard library; do not introduce third-party packages.
- New features must match the existing command-line style (`python todo.py <command> <args>`).
- Changes must run successfully with the commands above.

## Testing
- If no test file exists, create `test_todo.py` using the standard library `unittest`.
- Run after changes: `python -m unittest`

Remember the painful lesson in Article 11—do not bury the only useful rule inside a hundred lines of fluff. I learned this the hard way last year: my AGENTS.md was 140 lines long, and the critical rule "Do not use npm" was buried at line 140. Codex's attention had already been diluted by the preceding lines, and it went right ahead to run npm install. Thus, I intentionally compressed this notice to under twenty lines: every single line is something it actually needs for this task, with no company intro or product vision.

💡 Summary: The first step is to write a short and precise AGENTS.md specifying "how to run, what rules to follow, and how to test," keeping it strictly focused on what it actually needs.


03 Step 2: Assign Task—Specify Goal, Scope, Constraints, and Done When in One Message

With the notice posted, it's time to assign the task. The quality of the delegation directly determines how precise the output will be. This is the core of "13 Prompting Guidelines": a zero-information prompt like "add a delete feature" leaves too much to imagination; only by stating the "four elements" can you prevent it from going astray.

Analogy: A work order for a contractor. You can't just say "modify this room." You must specify: what to change it into (Goal), which walls to touch or avoid (Scope), what materials to use (Constraints), and how to verify it's done (Done when). If you miss any of these four, the worker has to guess, and you rework.

Go into the project and start a Codex session:

bash
codex

Then hand this fully structured requirement to it (you can copy it directly):

text
Add a "delete todo" feature to todo.py.

Goal: Support `python todo.py done <index>` to delete the corresponding todo by the index shown in list.
Scope: Only modify todo.py, and add/modify test files; do not change the overall CLI style, and do not introduce third-party packages.
Constraints: The index starts from 1; when the index is out of bounds or not a number, print a friendly message instead of throwing an error or crashing.
Done when: Use unittest to cover three cases—"normal deletion, out of bounds, non-numeric index"—and ensure `python -m unittest` is all green.
❌ Vague Assignment✅ Four-Part Assignment
"Add a delete feature"Clear command format done <index>
Unspecified scope and sizeBoxed scope: only touch todo.py + tests
Unspecified boundary handlingConstraints: friendly message for out-of-bounds / non-numeric
Unspecified done criteriaDone when: 3 test cases + unittest all green

My own experience: writing the done criteria into the prompt is the single most cost-effective line. Last month, I had Codex add fault tolerance to a parsing script. I got lazy and didn't specify verification, and although it "finished modifying," the script crashed immediately on an empty file. I added "must not crash on empty files, huge files, or garbled files" and re-assigned it, and it passed on the first try. The ceiling of Codex is often locked by your own questioning style.

💡 Summary: Use the "Goal + Scope + Constraints + Done when" recipe to state everything clearly, otherwise Codex will fill in the blanks; specifying the done criteria is like telling it "don't stop until it passes."


04 Step 3: Set Permissions—Allow File Edits and Testing without Letting It Go Wild

Before assigning the task (or when starting the session), think clearly: how much leeway should Codex have? This is the domain of "15 Permissions, Sandbox, and Approval"—the sandbox controls "what it can touch," while approvals dictate "whether it asks you before acting."

Analogy: A keycard for a worker. If keycard privileges are too low, they have to keep asking you to open doors, which is annoying. If too broad, they can wander into your bedroom and look through your drawers. For our task, what we need is "ability to modify files and run tests inside the project directory, but not leave the premises."

For this task, we recommend "workspace write + on-request approval"—it can freely modify files (including creating and deleting) in the project directory, and run python -m unittest, but it will pause to ask you for out-of-bounds actions like modifying files outside the workspace or connecting to the internet. Specify it in the command line:

bash
codex --sandbox workspace-write --ask-for-approval on-request

Or write it to ~/.codex/config.toml as default (TOML configuration file):

toml
# ~/.codex/config.toml
sandbox_mode = "workspace-write"
approval_policy = "on-request"
You Might ThinkActual Default (under workspace-write)
It can connect to the internet to install packagesNetwork is off by default
It can modify the .git directory at will.git is read-only protected by default
It can modify files anywhereWritable only inside the workspace directory

Don't be like me back in the day, setting sandbox_mode to danger-full-access as the global default just for convenience. Last winter, in a temporary directory without Git initialized, I asked it to "clean up unused files." With full access enabled and no "workspace boundary" in place, it started browsing my home directory. When I pressed Esc to interrupt it, my spine tingled. For this simple TODO project, workspace-write is more than enough; don't loosen the reins to places you shouldn't.

💡 Summary: Draw boundaries using sandbox and approval settings before starting—workspace-write + on-request lets it modify and test but prevents it from leaving the project; using danger-full-access globally is just digging a hole for yourself.


05 Step 4: Let It Self-Verify—Run Tests, Don't Finish Until It Passes

When Codex says "finished modifying," it doesn't mean it got it right. The most hassle-free way is to let it run tests itself, confirm they are green, and then hand it to you—you act purely as the final inspector. This step truly implements the "done criteria" from Article 13.

Analogy: Letting the chef taste the dish before serving. You already specified in the task assignment (Section 03) that "having python -m unittest pass" counts as acceptance, which is like handing it a ruler beforehand. Now, Codex will self-test against this ruler after edits: run tests -> check if they all pass -> keep editing if not. You don't need to watch its every move; wait for it to serve the finished product that has "already been tasted and confirmed good."

Because we wrote the test requirements in AGENTS.md (the ## Testing section in Section 02) and the prompt during delegation, Codex will typically create a new test_todo.py and run it after editing. When it runs the tests, you will see something like this in the terminal:

text
...
----------------------------------------------------------------------
Ran 3 tests in 0.003s

OK

Seeing OK and Ran 3 tests indicates that all three cases (normal deletion, out of bounds, non-numeric index) passed. In case it doesn't run them automatically, add a line to prompt it:

text
Run python -m unittest and paste the output; if anything fails, fix it until it is all green.

Here's a detail from Section 01 to keep in mind: since TODOS is an in-memory list cleared after the process terminates, you cannot verify deletion logic via CLI manual testing (like running add, done, then list)—the list command will always show empty list. Therefore, the correct verification for this feature can only be achieved via unit tests, asserting after add and done within the same process. This precisely highlights: when a task requires automated testing, do not cut corners with manual visual checks.

I have an iron rule: whenever I have Codex modify logic, I always append "run tests and paste results" at the end. Once, I skipped it to save trouble, glanced at the diff and merged it because it looked fine, only to have a boundary case break production. Since then, I never skip "self-verification."

💡 Summary: Include the test command in Done when when delegating, and let Codex run unittest until it's all green before delivering; memory-based or stateful logic must be verified by tests, don't rely on visual checks.


06 Step 5 (Optional): When to Call Subagents or MCP to Help Out

This simple TODO task is easily handled by a single main agent, without needing subagents or MCP. However, the capstone's purpose is to teach you "when to upgrade your tooling," so this section defines the boundaries—knowing when NOT to use them is just as important as knowing when to use them.

Analogy: Hiring external help during renovation. If you're painting a single wall, you can do it yourself. But if you're painting ten rooms simultaneously and need to check external color swatches, it's time to call in helpers and read references. Subagents are the helpers; MCP is the reference channel.

Remember two simple guidelines:

  • Tasks that are "voluminous and parallelizable" → Call subagents. For instance, instead of deleting a feature, you want to batch-migrate deprecated syntax across twenty files. Having the main agent split the task and run multiple subagents in parallel is much faster than sequential editing. For how to split and assign, see "21 Subagents"—remember its behavior: it only splits when you ask, and never acts on its own.
  • Tasks that "need to reach the external world" → Connect MCP. For example, before deleting, you need to check an internal company database to verify "whether this todo has been archived"—external capabilities Codex cannot access on its own are plugged in via MCP, as covered in "20 Connecting External Tools with MCP".
TaskUpgrade Needed?Why
Adding a delete feature to todo.py❌ NoSingle file minor change, main agent is sufficient
Batch-migrating deprecated syntax across 20 files✅ SubagentsVoluminous and parallelizable; splitting speeds it up
Querying external system archive status before deleting✅ MCPCodex cannot reach outside, needs interface

To be honest, the most common mistake for beginners is not "failing to use them when needed" but "forcing them when not needed"—splitting a three-line modification into five subagents is just creating trouble. Finish the task with the simplest single agent first, and only upgrade when you actually hit a wall of "voluminous/parallel" or "external reach."

💡 Summary: A single main agent is enough for small tasks, don't force heavy gear; call subagents (Article 21) only when "voluminous and parallelizable," and use MCP (Article 20) only when "needing to reach the external world."


07 Final Step: git Commit—It Drafts, You Confirm

Once the feature works and tests are green, the final step is committing the changes cleanly. This relies on "26 Git and GitHub Integration" and wraps up the workflow: it drafts the review and commit message, while you decide when to commit.

Analogy: A double signature before signing a contract. One party drafts the terms (Codex reviews diffs and writes the commit message), while the other party checks them line-by-line and signs (you inspect and approve the commit). A commit is a permanent piece of history; someone must guard the gate, and that person is you.

Specify directly in the session:

text
Commit this change: first check git status and git diff, then write a commit message in English prefixed with feat:, and list the changes and message for my confirmation before committing.

Codex processes git commits by typically following this sequence:

text
1. git status   —— Check current changes
2. git diff      —— Review specific line edits
3. git add       —— Stage files to be committed
4. git commit    —— Write the message and commit

⚠️ Experimental, subject to change: letting Codex run git commit itself relies on an experimental flag called codex_git_commit, which is off by default. So a safer posture is: let Codex help you check the diff and draft the commit message, but type the final git commit yourself—this lets you leverage its draft while keeping the "commit execution" firmly in your own hands.

Why is there naturally a gatekeeper stage for git commit? Under workspace-write sandboxing, the .git directory is read-only protected (as covered in Article 15), so actions like committing typically trigger approvals. This gives you a perfect window to review: are the files correct? Does the commit message make sense? Did it accidentally include files it shouldn't have (like temporary files)? Once verified, let it proceed. You can expect a commit message like:

text
feat: add delete todo by index feature and supplement unittest cases

After committing, a manual check is safest:

bash
git log --oneline -1

Expected output (the hash on your local machine will differ, which is normal):

text
a1b2c3d feat: add delete todo by index feature and supplement unittest cases

My personal habit is: I never enable "full auto" for git commit to let it run unattended. The guiding principle running through this book—"it reviews, you approve"—is most evident during commits. It's not that I'm afraid it will write a bad message; rather, I want to prevent it from carrying half-finished work into git history. Cleaning it up later is far more expensive than inspecting it on the spot.

💡 Summary: Let Codex draft the commit (checking diffs, preparing the message), but authorize the actual git commit yourself—this is where the "it reviews, you approve" principle lands at the end of the workflow.


08 Reviewing the Workflow: Which Skills Map to Which Article

Reviewing the workflow, you'll notice that the first thirty-odd articles are not isolated knowledge points, but workstations on an assembly line. You can apply this table to any project in the future:

StepActionRelevant Article
① Set targetCreate minimally runnable todo.py and add to GitThis article
② Write noticeWrite AGENTS.md to specify context, run steps, and conventions11
③ Assign taskGoal + Scope + Constraints + Done when recipe13
④ Set permissionsDraw boundaries with workspace-write + on-request15
⑤ Upgrade (optional)Call subagents for parallel tasks; connect MCP for external data21, 20
⑥ Self-verifyHave it run unittest until all green before delivery13
⑦ CommitCodex drafts diff and message; you confirm and commit26

The sequence of this workflow is the natural rhythm of real development: first let it understand the project (notice), then explain what is needed (delegation), boundary its write permissions (permissions), let it verify its work (testing), and finally check and save (commit).

As for model selection and inference intensity, I stuck with the defaults in this workflow—flagship gpt-5.5 + default inference effort (typically medium), which is perfectly sufficient for this simple TODO task without needing to turn it up to high. If you face a tough challenge like "large-scale cross-module refactoring," refer back to "30 How to Choose Models" to adjust levels by scenario. I deliberately went with default settings throughout this article to show you that the workflow itself is the star; parameter tuning is just icing on the cake.

💡 Summary: Notice -> assign -> permissions -> (upgrade optional) -> self-verify -> commit is the natural rhythm of real development; each previous article represents a station on this assembly line, and weaving them together is what "using Codex" really means.


Summary

This article has walked through putting the pieces together into a complete assembly:

  • Set target: Manually build a minimally runnable todo.py and add it to Git, giving subsequent steps a target.
  • Write notice: A short and precise AGENTS.md containing only what is needed, without fluff diluting key rules.
  • Assign task: Fully specify the "Goal + Scope + Constraints + Done when" recipe; nailing down Done when is like saying "don't stop until it passes."
  • Set permissions: Boundary access with workspace-write + on-request; don't set danger-full-access as the global default.
  • Self-verify: Let it run unittest until green; stateful or memory-based logic must be verified by tests, avoid visual checks.
  • Commit: Codex drafts, you confirm—the "it reviews, you approve" guideline lands at the end of the workflow.

You should now be able to: take any small requirement, stop improvising, and follow the "notice -> assign -> permissions -> self-verify -> commit" chain, assembling what you learned into a clean, reproducible, and reliable development workflow. This is the true boundary between "knowing how to use Codex" and "having heard of Codex."


The next article "35 Commands and Configurations Cheatsheet" organizes all commands, configuration keys, and common slash commands in one place for quick reference. Having just followed along, think about this: which commands in this workflow can you type blindly next time, and which ones do you still need to look up? That table is prepared precisely for the latter.