
Fredy Acuna / October 30, 2026 / 10 min read
Most of my code today is written with an AI agent in the loop. The hard part was never getting the agent to write code. It was getting it to write the right code, to stop when a real decision was mine, and to leave a trail I could pick up the next morning.
The workflow I use for that is Organic Driven Development (ODD). I did not invent it. It ships with Gentle AI, the agent harness by Gentleman Programming that configures Claude Code (and other agents) with an orchestrator, persistent memory through Engram, skills and a review layer. ODD is the default workflow that orchestrator runs on every request.
This post is how ODD works from the point of view of someone who uses it every day, with real artifacts from my own repos: HitZero (a SaaS for sports clubs), bici.taxi (a ride request app) and NOVALYX (an AI music video project).
ODD runs the same protocol, in order, on every request. No one has to ask for "planning mode".
The first step is the one I underestimated. Before ODD, asking an agent "what do you think about this function?" could end with the function rewritten. Now a question gets an answer, and a change needs an explicit request.
This is my favorite rule. A task is small when three things hold:
git diff alone.A task is large only when that third test fails: it spans several sessions, waits on something external, or the requirements are too long to survive a context compaction.
What the rule explicitly does not use is file count, line count, or how many tests are involved. A rename across 30 files is small. A three-line change to a billing cron that every free club depends on can be large. Size is about whether the work survives an interruption, not about how it looks in the file tree.
Small work stays inline: read, edit, see the test fail, fix, run the focused test and the suite once. No documents, no ceremony.
Large work gets a markdown file at odd/tasks/<feature-name>.md, plus a copy in Engram so it survives a lost session. It has a fixed order:
## Specs: numbered requirements S1..Sn, quoting the user's exact words, error messages and examples. Never summarized.## Tasks: one line per task with its linked specs, route (inline or delegated), status and evidence.## Log: L1 is the original request verbatim; later corrections, decisions and verification results are appended.Here is a trimmed excerpt from a real one in HitZero, for importing athletes from a spreadsheet:
# Athlete spreadsheet import (Excel/CSV + AI header mapping)
Objective: let an admin bulk-create athletes from a spreadsheet, with Cerebras suggesting the column mapping.
Branch: feat/athlete-spreadsheet-import. Delivery: ask-on-risk. Test runner: vitest (unit)
## Specs
- S4. Normalization is deterministic code, not AI: phone to +57 10 digits, dates to `YYYY-MM-DD` (not future), ...
- S8. Plan limit: ... reject the whole batch with `PLAN_LIMIT_REACHED` (409) if `active + batch > limit`, before inserting.
## Tasks
- [x] T1 (S2, S4-S6, S9): shared row validator and normalizer. Route: inline. RED: module missing; GREEN: 9/9, types suite 253/253, tsc + biome clean.
- [x] T2 (S7-S9, S13): `POST /organizations/{orgId}/athletes/import`. Route: inline. ... Risk: item 4 (advisory lock + batch limit).
## Log
- L4 (agent finding): ... OPEN DECISION: is phase 1 athletes-only with guardian contact stored as emergency contact (S10), or must guardian invites ship in the same release?
- L6 (user answer to L4): "Emergency contact only (Recommended)". Phase 1 is athletes only; T5 stays out of phase 1.
- L8 (agent decision): S3 narrowed to headers only. No sample rows (minors' names and birth dates) are sent to Cerebras.
- L11 (independent verifier): S1-S3 and S6-S13 PASS, S4 FAIL, S5 PARTIAL. Blockers: B1 a number in the birth-date column threw `RangeError` ...
- L12 (parent): one correction batch. RED types 3/13, dialog 1/7; GREEN ...
A few things in there are worth pointing out:
The document is also the handoff. When work is delegated, the subagent does not get a paraphrase. It gets a reference like Spec: odd/tasks/<feature>.md, T2, S3-S4, reads the document up to ## Log, and reports which S# it covered. That kills the "telephone game" where requirements degrade every time they are summarized.
Even this blog post started as one: the task that produced it lives in odd/tasks/blog-posts-from-recent-work.md in this portfolio repo, with each post as a numbered spec.
ODD applies one test-first policy: when a runnable, deterministic test and a clear expected outcome exist, observe RED before implementing, then GREEN, then refactor with tests green.
It also says when not to. Passive documentation, a missing runner, or no meaningful RED gets an explicit exception in the log instead of a fake test. In bici.taxi there was no runner at all when dispatch automation started, so the first task added vitest, and the document says so:
## TDD
- Mode: on (source: global user config "Strict TDD Mode: enabled").
- Runner: none existed; adding `vitest` (`pnpm test` → `vitest run`).
## Progress / evidence
- T1 c3a58bb: RED observed (3 files failed, modules missing) -> GREEN 23 tests.
- T3 b1b5bd2: ... buildTripView privacy tests written after the code (characterization)
- Unverified: real WAHA sends, real DB accept race, browser run of /viaje.
I like that last line a lot. The agent is not allowed to claim what it did not see.
Gentle AI runs a parent orchestrator that can spawn subagents. The rule is not "use agents for big things". Delegation needs a named reason:
path:line evidence.Size alone, file count, or "this feels complex" never fire a delegation. Without a reason, the parent writes inline, following its document.
The why is context. The parent session is the one talking to me and holding the plan. If it reads every file and dumps every test log into its own context, it gets slow and forgetful exactly when the work gets long. Subagents compress: an explorer reads 40 files and returns 2k tokens; a verifier runs the suite and returns a verdict per spec. The parent stays thin enough to keep orchestrating.
Every code change ends with one line:
Risk: none
Risk: item 4 (advisory lock + batch limit)
The items are the high-risk list: (1) stored data and irreversible effects, (2) security, (3) contracts others consume, (4) concurrency, (5) delivery and environment, (6) no test would catch a regression.
There is one check before writing Risk: none that has saved me more than once: does the diff change a shared helper, option or parser that other behavior also uses? If yes, that is item 3, even if the request only mentioned one screen.
The Risk line is what decides whether a change gets an independent verifier, so it is not decoration. It also tells me, at a glance, which commits deserve a careful read.
What I like:
What it costs:
For me the trade is worth it. The feature documents across HitZero, bici.taxi and NOVALYX are now the most useful history I have of why the code looks the way it does.