As portion of getting to grips pinch the caller world of agentic engineering, I built an exertion to support my work. It’s a blase app: high-quality web UI pinch move refresh and look-up, modals and auto-save, integrations to outer systems, instrumentality learning and text analysis, inheritance jobs, and a due situation setup pinch fully automated deployment. It’s astir 150,000 lines of code, primarily successful Rust (~120 kLoC) pinch the remainder successful TypeScript and Terraform.
This was wholly written by agents. Mostly Claude Code, and immoderate use of Cursor. I didn’t publication aliases reappraisal immoderate of the code, except occasionally, retired of interest.
While building the application, I could spot immoderate things going awry. After watching an edit to statement 4,000 of a record scroll by successful the terminal, I had a person look. The information entree furniture had grown to over 6,000 lines. As much features landed, this continued to grow. Every query, publication aliases write, repeated the aforesaid HTTP request setup, the aforesaid JSON encoding and decoding. Eventually, it reached 17,155 lines. In a azygous Rust file.
An research successful refactoring
The 17,155 statement record was the full information entree layer. A single, self-contained module. Reviewing the code, location was no de-duplication, nary soul language, constricted extraction of functions, and very small extraction of classes. It did person a clear boundary with an interface to preserve. It was a awesome target for refactoring.
The extremity of refactoring an agentic codification guidelines is to walk tokens now in refactoring to make token depletion for early activity lower. An experiment should beryllium capable to show that arsenic this record was refactored the token costs of making abstracted characteristic implementations successful this code base would decrease.
Precisely because agents ne'er study this was now imaginable to tally as an experiment. I could punctual a caller supplier to make precisely the same change aft each refactoring stage. Unlike a quality engineer, the experiment would not beryllium tainted by learning from erstwhile steps.
- Create an wide refactoring plan, pursuing strict refactoring discipline.
- Craft a typical change, described successful a azygous prompt.
- Establish a baseline costs of change: successful a sub-agent, execute that prompt, including asking the sub-agent to study token consumption.
- Throw distant the change.
- In a loop:
- Apply a azygous measurement of the wide refactoring.
- In a sub-agent, execute exactly the aforesaid alteration receiving the token costs of the change.
- Throw distant the change.
- Record each token costs, clip to execute the change, and lines of code aft each measurement of the refactoring, including the baseline.
The punctual utilized for the typical alteration and the refactoring steps applied are shown successful the appendices, below.
One caveat: Claude doesn’t supply reliable methods for counting tokens unrecorded contempt showing token counts, reporting tokens consumed per session, and billing for tokens. I’m assuming this is a temporary rumor that will amended complete time. Instead, the sub-agent reported the number of characters received and sent and used tiktoken to approximate tokens, by dividing characteristic count by four.
Results
| Baseline | 17,155 | 17,155 | 50,359 | 159,564 | 1,705 | 342 |
| Step 1 (FirestoreClient) | 16,706 | 16,706 | 49,910 | 155,205 | 1,723 | 530 |
| Step 2 (extract_doc_id, new_link) | 16,562 | 16,562 | 49,766 | 159,227 | 2,105 | 574 |
| Step 3 (link-query helpers) | 16,567 | 16,567 | 49,771 | 154,054 | 2,105 | 524 |
| Step 4 (FakeStore predicates) | 16,577 | 16,577 | 49,781 | 154,146 | 2,060 | 654 |
| Step 5 (value ctors) | 16,469 | 16,469 | 49,673 | 171,251 | 2,036 | 1,353 |
| Step 6 (FieldsBuilder) | 16,469 | 16,469 | 49,673 | 171,251 | 2,036 | 1,353 |
| Step 7 (queries.rs) | 16,474 | 15,670 | 49,678 | 151,850 | 1,800 | 587 |
| Step 8 (traits.rs) | 16,508 | 13,845 | 49,712 | 132,558 | 1,723 | 446 |
| Step 9 (traits/ split) | 16,508 | 13,845 | 49,712 | 132,558 | 1,723 | 446 |
| Step 10 (codec.rs) | 16,521 | 12,846 | 49,725 | 131,871 | 1,750 | 540 |
| Step 11 (fake_store.rs) | 16,535 | 11,122 | 49,739 | 133,016 | 2,460 | 600 |
| Step 12 (store/ split) | 16,550 | 9,269 | 49,754 | 104,080 | 2,050 | 490 |
| Step 13 (co-locate tests) | 16,550 | 9,269 | 49,754 | 104,080 | 2,050 | 490 |
| Step 14 (complete fake_store.rs) | 16,553 | 7,225 | 49,757 | 107,205 | 2,453 | 523 |
| Step 15 (store/ split) | 16,608 | 3,695 | 49,812 | 27,360 | 2,113 | 454 |
The absorbing metrics present are the full lines of codification successful the data access layer, the full lines of codification successful the largest azygous file in the information entree furniture and the input tokens consumed while producing the change.

This floor plan shows 4 things. The first constituent is the baseline, measurement 0, and past the aforesaid metrics are repeated after each refactoring step has been applied.
- The full lines of codification successful the information entree furniture arsenic a whole. Initially, this is conscionable the azygous record I started with. This becomes galore files arsenic refactorings are applied. By the extremity there are 19 Rust files.
- The lines of codification successful the azygous largest record successful the information access layer. This started arsenic the entirety of the information furniture successful the single initial file. By the end, the azygous largest record is simply a test library. Further refactoring passes could use the aforesaid approach to this.
- The full input tokens consumed by the sub-agent while applying the representative change.
- The full output tokens produced by the sub-agent while applying the typical change.
Refactoring reduces token consumption
The results are clear. Input tokens enactment reasonably level until the largest file starts to fall, and past they driblet before, successful the words of Claude, falling disconnected a cliff.
Between the guidelines statement and the last refactoring, input tokens for the same task reduced from 159,564 to 27,360. A redeeming of 132,204 tokens, aliases 83%. And that redeeming is not a one-off. Every single change that touches the information entree furniture from this constituent guardant now costs importantly less.
How overmuch of a saving? Assuming Sonnet 5 pricing astatine the clip of writing of $3/MTok, 39.7 cents. Not a lot. Does it multiply? How will this play retired crossed debugging? More analyzable features? This is refactoring only 1 information of the codification base, tin the full codification base be aggressively refactored to find savings everywhere? How overmuch would those refactorings cost?
This redeeming is because the supplier has to publication little code. But it is not because location is little codification to read. The wide codification successful the data access furniture arsenic a full has stayed reasonably constant. Therefore to be able to slope this saving, the supplier must beryllium capable to successfully identify the smallest subset of files basal to read. The results make it look this was happening. Reading the Claude Code thinking output and record publication summaries arsenic the alteration was being applied also indicates the sub-agent was successfully reference smaller and smaller sections of codification each time.
In different words, randomly cutting the record into smaller files is unlikely to thief arsenic much: moreover if each record were smaller, the agent would beryllium forced to publication done galore files looking for the relevant code. While the measurement pinch the biggest effect happens astatine the end, the previous steps were refactorings to group up this saving. This was not planned. It was simply a consequence of really refactoring typically proceeds: local record changes to extract duplication, earlier breaking down into smaller files erstwhile a repeating halfway emerges.
The refactoring did not make the typical alteration smaller. The number of tokens produced erstwhile penning codification was mostly unaffected: the output tokens do not move very much. Those tokens are 5 times the value of the input tokens. But, location are a batch little of them. Are there refactorings that could beryllium applied to trim output token production? I request a much analyzable sample alteration to research these questions. The sound of the non-deterministic codification procreation process is hiding immoderate variance caused by changes successful the factoring of the code.
Notes connected the process
Claude was not bully astatine refactoring. If you publication the punctual and the refactoring steps below, it’s clear that the refactorings produced were straight successful consequence to the prompt. Claude is incapable to look at code, look astatine refactorings successful wide and activity retired which are suitable to apply: a quality needs to actively guideline it. This marries pinch wider experience successful this app. The improvement harness includes an explicit refactoring step. That refactoring measurement did not punctual Claude into improving this file. More anecdotally, Claude.ai was amended than Claude Code. I utilized some interfaces to create the refactoring plan. Claude Code spotted extract usability arsenic the first step. Claude.ai went further and saw an full customer people to be extracted.
It was besides bad astatine applying them. The mechanical enactment of refactoring was performed by penning Python scripts utilizing grep and sed. These scripts often sewage confused by indentation. Oh, the irony. In addition, the azygous astir valuable refactoring was missed successful the first pass, and had to beryllium re-applied arsenic a follow-up step. This is why the number of steps successful the fig don’t lucifer the refactoring steps successful the appendix.
It took astir 8 hours to complete the full experiment. This was mostly unattended. The only involution was aft six hours 40 minutes erstwhile it appeared to person finished, but had skipped that step and needed to beryllium redirected. This research was moving connected slow hotel WiFi. I wondered if that contributed to clip taken. But on deeper study of the codification base, the cargo impermanent build cache had become very large. Test execution was suffering, significantly.
Further activity and broader implications
Unfortunately, it didn’t hap to maine to execute a count of the tokens required to create and execute the refactoring scheme until it was already complete. I’ve looked astatine my aggregate depletion crossed the time model wherever I was doing this work, including designing and running the experiment. I can’t opportunity really galore tokens were required to perform the refactoring. The precocious bound is 5 million, however. This includes creating the refactoring scheme twice, the work to creation the research including the typical change, and various different tasks. Future activity should see a much meticulous count of tokens consumed to refactor.
This is conscionable 1 experiment, connected a important exertion that is still greenfield and built and maintained by a azygous developer. But, I judge this is simply a perchance absorbing first step. This effort shows the value, successful clip and money of refactoring. As good as measuring really costly refactoring is. It would beryllium absorbing to look astatine much analyzable changes, astatine wider refactoring, refactoring continuously, and moreover the comparative worth of different refactoring approaches.
This is conscionable the beginning.
Appendices
Note: These appendices see the prompts that I used, and the output that was returned. The only editing applied has been to remove the circumstantial codification changes to beryllium made. These are included without editing to show really the agents were directed. There are nary hidden tricks. As such, location is immoderate connection successful present that mightiness be confusing. The correction is successful the original.
The typical changeThis is the recorded punctual that was fed to each sub-agent, location was no further discourse supplied different than the codification guidelines and accompanying architecture documentation. Every sub-agent was starting pinch exactly the aforesaid information.
You are moving successful the Rust task astatine ~/dev/your-project-name.
Add a caller ItemWatchStore nationalist async trait to the Firestore layer, pursuing existing patterns exactly. The trait must person 3 methods:
- async fn watch_item(&self, item_id: &str, user_id: &str) -> Result<()>
- async fn unwatch_item(&self, item_id: &str, user_id: &str) -> Result<()>
- async fn watched_items_for_user(&self, user_id: &str) -> Result<Vec<String>>
Watches are stored successful a item_watches Firestore collection. Each archive has fields: itemId (string), userId (string), createdAt (timestamp). There is nary Rust struct for a watch grounds — the methods return Vec<String> (item ids).
Implement the trait for some FakeStore (using an in-memory Vec<(String, String)> section added to FakeStoreInner) and FirestoreStore (using the aforesaid HTTP patterns utilized for different shop impls successful this file).
At the very extremity of your response, output precisely this JSON artifact (fill successful existent values):
{ "files_read": [ {"path": "src/firestore.rs", "chars": 123456}, ... ], "response_chars": 7890 }Do NOT perpetrate the change. Stop aft penning the code.
Refactoring stepsThis is the punctual that was utilized to create the refactoring plan.
Following the strict meaning that a refactoring is simply a provably correctness preserving bid of codification edits, and utilizing Martin Fowler’s 2nd version of Refactoring arsenic the source, examine @src/firestore.rs. This is simply a 17K LoC Rust file. No record should be that long. It is almost surely not utilizing an soul connection to build and negociate queries. Produce and describe, but don’t execute, a sequence of refactorings that would massively trim the statement count of that file, without changing the interface astatine all.
Following is the explanation of the refactorings applied, extracted from the scheme built and followed by Claude. The existent scheme includes predicted codification changes. For each refactoring, the individual steps to follow were listed. Each of those steps was individually testable, and was individually tested. This is simply a stricter refactoring than most human engineers would follow.
The steps listed present don’t statement up straight pinch the measured changes above arsenic Claude skipped the astir valuable azygous refactoring (splitting retired the shop into sub-files) connected the first walk and had to complete that afterwards arsenic 2 further steps.
Fowler ref: Extract Class (7.5); Extract Function (6.1) for each primitive
FirestoreStore presently conflates 2 responsibilities:
- Domain query orchestration — which query to run, which documents to write, really to parse results into domain types
- Firestore HTTP transport — auth headers, URL construction, JSON encoding/decoding of Firestore ligament types, retry-on-PRECONDITION_FAILED
Fowler §7.5 calls for extracting a caller people erstwhile you tin place a coherent subset of a class’s information and behaviour. The transport responsibility owns: client: reqwest::Client, project_id: String, MetadataAuth, and documents_url() / auth_header(). Extract these into a caller FirestoreClient struct.
Estimated savings: ~1,200 lines successful FirestoreStore impls; FirestoreClient adds ~120 lines net.
Step 2 — Extract Function: extract_doc_id and new_link (Fowler §6.1)
Fowler ref: Extract Function (6.1)
-
extract_doc_id — The expression doc.name.rsplit('/').next()?.to_string() appears verbatim astatine the start of each 20 parse_*_document functions. Extract it.
-
new_link — Building a Link struct pinch metadata: HashMap::new() and provenance: None and a caller UUID appears 62 times. Extract a mill function.
Estimated savings: ~500 lines (62 × ~10-line structs → 62 × ~2-line calls; 20 parse functions each suffer 1 statement of boilerplate).
Fowler ref: Extract Function (6.1)
Two sub-patterns recur wrong the FirestoreStore trait impls aft moving a nexus query:
-
Pattern A — cod each nexus documents from query rows (~15 sites).
-
Pattern B — query links and return precisely 1 target ID, correction if missing (~8 sites):
Estimated savings: ~200 lines.
Fowler ref: Extract Function (6.1)
Inside the FakeStore impls, ~15 methods repetition variations of inner.links.iter()....
Extract 2 methods connected FakeStoreInner. The 15 callsites past become single-line. Methods that additionally select by a 2nd predicate (e.g. besides checking to_kind) concatenation .into_iter().filter(…) connected the result of the helper.
Estimated savings: ~120 lines.
Step 5 — Replace Inline Code pinch Function Call × 4: Firestore worth constructors
Fowler ref: Replace Inline Code pinch Function Call (8.5)
Add 4 backstage free functions (file-level, not methods) earlier the codec block. Replace each 128+ json!({"stringValue": …}) / json!({"timestampValue": …}) etc. inline expressions pinch calls to these functions. Each multi-word json macro telephone becomes a single short call.
Estimated savings: ~80 lines (mostly from multi-line json macros collapsing to one-liners).
Fowler ref: Extract Class (7.3)
The ~20 encoder functions each travel this shape:
fto mut fields = serde_json::Map::new(); fields.insert("foo".to_string(), str_val(&x.foo)); fields.insert("bar".to_string(), ts_val(x.bar)); json!({"name": path, "fields": fields})Extract a mini builder. Rewrite each encoder usability to usage the builder. A ~40-line encoder shrinks to ~12 lines.
Estimated savings: ~500–600 lines crossed the 20 encoder functions.
Fowler ref: Move Function (8.1)
Convert src/firestore.rs to a module directory: rename to src/firestore/mod.rs. Then create src/firestore/queries.rs and move each 32 LinkQuery constants and the LinkQuery/EqFilter/EqValue/Ordering/Direction type definitions into it. Add pub(super) usage queries::*; successful mod.rs.
No behaviour changes; each callsites already reference names that were in scope via the level file.
Reduces mod.rs by ~800 lines.
Fowler ref: Move Function (8.1)
Move each 17 pub trait definitions (and their associated correction types) to src/firestore/traits.rs. Re-export them from mod.rs pinch pub use traits::*;.
Reduces mod.rs by ~1,900 lines. Produces a ~1,900-line traits.rs that needs further decomposition.
Step 9 — Move Function: divided traits.rs into a traits/ module directory
Fowler ref: Move Function (8.1)
Convert src/firestore/traits.rs to a module directory by grouping the 17 traits into 4 domain-aligned files:
| traits/planning.rs | ConcentrationStore, GoalStore, ItemStore, NoteStore, PursuitStore, FocusPassStore | ~650 |
| traits/content.rs | CaptureStore, TagStore, UrlReferenceStore, DocumentStore, PaperStore | ~550 |
| traits/people.rs | ThoughtworkerStore, ExternalContactStore, CompanyStore | ~300 |
| traits/system.rs | SessionState, LinkStore, SuggestionStore, SuggestionVetoStore, OAuthTokenStore, MigrationLedger, EmbeddingStore, RuntimeConfigStore, SalesforceSyncStateStore | ~400 |
traits/mod.rs becomes a axenic re-export record (~20 lines). Associated error types (FocusPassError, SuggestionDecisionError, etc.) move with the trait that produces them.
No trait meaning changes, nary callsite changes — only relocation. Each resulting record is 300–650 lines.
Fowler ref: Move Function (8.1)
Move each archive encoder/decoder functions (*_document, parse_*_document, kind_str, parse_kind, parse_capture_source, parse_outcome, etc.) positive FieldsBuilder and the worth constructors from Steps 5 and 6 into src/firestore/codec.rs. Make them pub(super).
After Step 6 this module will beryllium ~400–500 lines alternatively than ~1,200.
Reduces mod.rs by ~500 lines (post-Step-6).
Fowler ref: Move Function (8.1)
Move FakeStore, FakeStoreInner, and each 18 trait impl blocks for FakeStore into src/firestore/fake_store.rs. Re-export FakeStore from mod.rs pinch pub usage fake_store::FakeStore;.
FakeStoreInner and helper methods enactment backstage to the module.
Reduces mod.rs by ~4,700 lines.
Step 12 — Move Function: divided FirestoreStore impls into per-trait files nether src/firestore/store/
Fowler ref: Move Function (8.1)
Create src/firestore/store/mod.rs pinch FirestoreStore struct definition, impl FirestoreStore (constructor + FirestoreClient from Step 1), and MetadataAuth.
Then create 1 record per logical domain grouping.
Each record contains only usage super::*; (or definitive imports) and the trait impl block(s). No type definitions, nary helpers. Helpers utilized by multiple impl blocks enactment successful store/mod.rs.
Reduces what would beryllium a ~10,000-line record into 10 files of 120–650 lines each. mod.rs becomes a ~100-line re-export manifest.
Step 13 — Move Function: co-locate tests pinch their modules
Fowler ref: Move Function (8.1)
The existing #[cfg(test)] modules trial circumstantial domain areas and belong pinch the modules created successful Step 12 alternatively than successful a single tests.rs.Each trial module moves wrong a #[cfg(test)] mod tests { … } artifact astatine the bottommost of the target file, pinch usage super::*; to access the module’s internals. No trial is changed, only relocated.
Any shared trial fixtures (FakeStore::new, helper builders) that are already successful fake_store.rs are accessible via the existing use super::fake_store::FakeStore import chain.
Reduces mod.rs by ~2,000 lines; each target record gains 200–700 lines of tests that are straight adjacent to the codification they exercise.
English (US) ·
Indonesian (ID) ·