Coding Agents Don't Need Longer History — They Need Intent Continuity

Coding Agents Don't Need Longer History — They Need Intent Continuity

TL;DRI built a complete, working implementation in pure Python and shared actual benchmark numbers from real runs (no simulated data).The core lesson: just pulling up past history isn't the same as knowing what's actually still accurate.A basic search setup only grabbed 57% of the requirements a coding agent needed. Adding a verification layer pushed that to 100%.Out of 8 tasks, the baseline got zero right, basic search got 4, and intent-aware search nailed all 8.I did all of this with zero embeddings, zero vector databases, and absolutely no LLM calls in the pipeline.I also own up to a bug in my original experiment design that almost made my results look way better than they actually were.Why More History Isn't EnoughI set up a coding agent workflow that worked perfectly at first. But once a project got long enough, it started causing problems.When a project passed a few dozen steps, core rules began vanishing. No one deleted them. The context window was not full. Those rules were still technically sitting in the chat logs. They just dropped off the radar because new requests did not trigger the agent to check if an older decision still mattered.For instance, you might tell the agent on day one to never expose internal database IDs in API responses. Sixty messages later, you ask it to build a new authentication flow. That new request says nothing about IDs. Since the agent lacks a clear reason to look back, it skips that step and ships an endpoint leaking the exact data you tried to protect.This is not a made up scenario. It is the actual test case I used for this article. Below, I will show you how three different methods handle this exact problem.Every result shown here comes from actual test runs using Python 3.12 with no outside dependencies. You can clone the repo and run run_experiment.py to reproduce the numbers yourself, unless I specifically call out an isolated test.Complete Code: https://github.com/Emmimal/intent-continuity/What Intent Continuity Actually MeansTerms get mixed up here pretty fast, so let us clear up the definitions.Standard RAG, introduced by Lewis et al. (2020) [1], connects a language model to a retrieval system that finds relevant information from an external knowledge source. The basic question is simple: what information is relevant to this query?Bigger context windows let models hold more text at once. But that size does not make the model check an old rule buried sixty turns back. Liu et al. (2023) [2] pointed out that models miss details stuck in the middle of long prompts, even within their stated limits.Yet that misses the real point. Even with total recall, a model still has to connect an old rule about database IDs to a new login task. Memory failure is not the problem. Deciding what matters is.Intent continuity is different. It means carrying an old requirement into a new task without the user repeating it, while dropping that rule if something newer overrides it.Here is the exact split this article focuses on:Right now, most talk about agent memory focuses purely on that first question.Who This Is ForBuild this if you run coding agents on long-running projects where rules get stated once and forgotten. Think of multi-week refactors, codebases packed with old design choices, or teams where whoever set a constraint three weeks ago is not the person prompting the agent today.Skip it for quick, single-session tasks that carry no history. Skip it if your project is small enough that you can just paste your full requirements doc into every prompt. Skip it if you are already manually repeating every rule to the agent on every turn. If a human is constantly reminding the agent what to do, the system never needs to look up past decisions.If your agent sessions stay short and your rules never shift, standard search or zero memory works great. Long projects just do not work that way.Full Pipeline ArchitectureThe intent-continuity pipeline, horizontal flow. Nine steps carry a coding agent's historical requirements from raw interaction history, through verification and supersession checks, to a graded implementation. No embeddings, no vector database, no LLM call in the pipeline.This diagram maps how an AI coding agent recovers and verifies project requirements from earlier conversations instead of relying on a longer context window or plain vector search. Interaction history moves left to right through rule-based intent extraction, then drops down and continues right to left through candidate retrieval and verification, where superseded or out-of-scope decisions get dropped before anything reaches the agent. The pipeline ends with a deterministic agent and a requirement checker, so every recovered requirement is graded the same way it was verified. The whole system runs in pure Python, with no embedding model or vector database anywhere in the chain.I set one strict rule before writing a single line of code: 100% pure Python.No API keys, no external LLM, no embedding models, and no vector databases.Part of the reason was convenience. I wanted anyone to clone the repo and run it in under a second with zero setup friction. But the bigger reason was control. If I relied on an embedding model, the benchmark results would just get tangled up in how good or bad that specific model happened to be.The verification logic is what actually does the heavy lifting here, and I wanted to prove it can stand entirely on its own two feet.The pipeline starts by turning raw chat logs into structured requirement records.The extractor is just simple. It scans each message for sentences that look like requirements, identifies the part of the system the requirement appears to target, and extracts specific values when they are present.Trigger-Phrase Flagging vs. Ground Truth — 70 InteractionsMetricValueTrue positives12False positives3False negatives0Precision0.80Recall1.00The key takeaway here is 100% recall. The extractor caught every single planted requirement in the test set.Precision landed at 0.80 on purpose. Three everyday sentences tripped the filter because they used trigger words like "must"—for instance, telling someone you must run to a dentist appointment.The next step cleans those up. The component classifier checks if a candidate maps to a real system part. If it does not find a match, it drops the item.I kept those false positives in the benchmark deliberately. A keyword filter claiming flawless precision on a tuned test set usually just hides its weaknesses. I wanted predictable, measurable behavior instead.Put simply, the extractor is written to catch too much rather than miss a rule in silence.Is this code fancy? Not at all. It is just a lightweight extractor that feeds structured data to the next steps. A real production app would need something much heavier.Component 2: Candidate Retrieval and the Domain SchemaCandidate retrieval figures out which past records to check before running any verification. It relies on two simple signals. First, does the record share the same system component as the current task? Second, does it belong to a linked component listed in a domain schema?I wrote this schema once based on general backend design rules. For instance, auth work impacts security and API behavior, while API work requires testing and security reviews.The system applies this exact schema to every single task without modification.That fixed approach matters. An earlier version let me define custom relationships for each individual task, which felt like cheating. I will explain why that skewed things shortly.Component 3: VerificationThis is the part of the pipeline that actually handles intent continuity. Once the system pulls up candidate rules, verification runs two quick checks on each one: has a newer rule replaced it, and does it apply to the current task context?The system figures out if a rule is superseded using a simple rule instead of manual labels. If two records share the same component, scope, and target key, but have different values, the newer one replaces the older one.Same Rule, Two Different OutcomesPairComponentScopeEffectOutcomeR2 (turn 9) → R7 (turn 39)authSameauth_methodR7 supersedes R2R4 (production) vs. R5 (prototype)databaseDifferentdatabase_engineNeither supersedes the otherTake R2 and R7. They talk about the same key in the same scope, so the later one wins and drops the old one.Then look at R4 and R5. They disagree on the database engine too, but they target different scopes: production versus prototype. The verification step keeps both active because they apply to separate environments.Handling that distinction automatically matters a lot. If the system treated them as conflicting, it would break a core use case. Writing code to compute that difference instead of hardcoding labels turned out to be the most critical design choice in the whole project.Component 4: The Compiler and the Deterministic AgentWhatever records survive the verification stage get flattened into a clean key-value context. From there, they go straight into a template that mimics a coding agent.The simulated agent itself is intentionally simple. It starts with a fixed set of defaults, gets updated by whatever fields it actually receives, and has zero ability to guess a rule it was never handed.That is the whole point of the setup. Every result you see below comes down entirely to what each search strategy managed to recover. It has nothing to do with a language model having a good or bad day, because there is no model in the pipeline at all.Component 5: The CheckerA single function grades every run using the exact same ground-truth field list every time.No approach gets special treatment or a different rubric. The required fields for each task are locked in before any search strategy runs, and every method is measured against that exact same fixed standard.What Happens on Task T1Here is how the pipeline performs against one of the benchmark tasks:"Implement the new authentication flow."Ground truth: This task relies on three unstated constraints: the OAuth2 migration from turn 39, a backward-compatibility rule from turn 4, and an internal-ID hiding rule from turn 15. The prompt does not mention any of them.Three Conditions, One TaskConditionCandidates FoundSurvived to ContextFields Handed to AgentViolationsBaselineNoneNone{}3Naive lexical retrievalR2, R7R2, R7{'auth_method': 'oauth2'}2Intent-awareR1, R2, R3, R7, R10R1, R3, R7, R104 fields including OAuth2, ID hiding, and compatibility0Why "found something related" isn't the same as "found what's current." Both mechanisms retrieve the same two historical records; only one determines which is still valid before handing it to the agent.This diagram walks through a real supersession case from the intent-continuity experiment: an early decision to use JWT-based authentication, later replaced by a decision to migrate to OAuth2. Naive lexical retrieval, the kind of behavior you'd get from plain keyword or vector similarity search, finds both records and lets the newer one win purely because it was mentioned more recently, a coincidence of ordering rather than an actual validity check. Intent-aware retrieval finds the same two records but runs an explicit verification step that determines the older record is superseded before either one reaches the coding agent. Both approaches land on the correct auth method in this particular case, which is exactly the point: one got there by luck, and the other by design, and that difference is invisible until you test a case where the ordering doesn't happen to save you.Standard keyword search gets the auth method right, but only because R7 happens to overwrite R2 during context assembly. The compiler just keeps the last record it sees, rather than figuring out that the older JWT decision was actually invalid. It completely misses backward compatibility and ID exposure because those sentences do not share a single word with "implement the new authentication flow."Intent-aware retrieval grabs those hidden requirements through the domain schema instead of relying on keyword matches. The verification step explicitly determines that R2 is superseded before anything reaches the agent, which is a deliberate validation step rather than a random ordering quirk.It also picks up R10, a rate-limiting constraint, right alongside the three graded requirements. R10 is a valid historical constraint that sits outside the checklist for this specific task, proving the system captures context without over-specializing.Stated as plainly as possible across the three conditions:Baseline: "I do not know the past."Standard keyword search: "I found something with matching keywords."Intent-aware: "I found several related items, checked which ones were still valid, and reconstructed what actually matters."The Experiment: 70 Interactions, 12 Requirements, 8 TasksI built a synthetic project history instead of using real chat logs for the same reason the pipeline has zero external dependencies. I wanted a ground truth I could fully verify, rather than a dataset where figuring out what the agent should have known becomes a subjective judgment call.The setup contains seventy chronologically ordered interactions: twelve genuine planted requirements, three trap sentences designed to trip up a keyword extractor without being real rules, and fifty-five lines of ordinary noise like standup reminders, pull request comments, and casual chat.The 12 Planted RequirementsIDTurnComponentTypeScopeEffectSupersedesR14apiconstraintanypreserves_old_fields=True—R29authdecisionanyauth_method='jwt'—R315securityconstraintanyhides_internal_ids=True—R421databaseconstraintproductiondatabase_engine='postgresql'—R526databasedecisionprototypedatabase_engine='sqlite'—R631uiconstraintanydashboard_sections=(...)—R739authdecisionanyauth_method='oauth2'R2R843performancepreferenceanyuses_small_model=True—R947testingconstraintanyhas_integration_tests=True—R1051apiconstraintanyrate_limited=True—R1155uipreferenceanydefault_theme='dark'—R1259deploymentconstraintproductionrequires_staging_validation=True—The 8 Later Tasks (None Restate Their Dependencies)IDComponentScopeExpected RequirementsTask TextT1authanyR7, R1, R3Implement the new authentication flowT2uianyR6, R11Add the new monitoring metrics to the dashboardT3databaseproductionR4Set up the production database configurationT4apianyR1, R10, R9, R3Add a new public search endpointT5performanceanyR8Optimize the inference pipelineT6deploymentproductionR12Prepare the deployment pipelineT7testinganyR9Add tests for the new payment endpointT8databaseprototypeR5Set up the prototype branch databaseMeasuring What It Actually RecoversAll numbers below come from real runs of run_experiment.py. Nothing here is a projection or an estimate.Aggregate Results Across All 8 TasksConditionAvg. RecallIrrelevant RetrievedStale Decisions AppliedViolationsTokens SuppliedTasks PassedBaseline0.00001400/8Standard keyword search0.5717171554/8Intent-aware1.0010001998/8Tasks passed out of 8, by condition. Going from no history, to naive lexical retrieval, to intent-aware retrieval doubles task correctness and then doubles it again.This bar chart shows the final task-completion results from the intent-continuity experiment across the same 8 coding tasks. A coding agent with no access to history passed 0 of 8 tasks, breaking a requirement it was never told still applied. Naive lexical retrieval, the kind of result you'd expect from basic keyword or vector-similarity search with no validity checking, passed 4 of 8. Intent-aware retrieval, which adds an explicit verification step to drop superseded or out-of-scope requirements before they reach the agent, passed all 8. The gap between naive retrieval and intent-aware retrieval is the actual finding of this experiment: retrieving related history isn't the same as retrieving requirements the agent can currently trust.Intent continuity, as implemented here, is not a compression technique. I want that stated explicitly rather than left for a reader to infer from the table.Intent-aware retrieval uses more tokens than standard keyword retrieval—199 versus 155, or roughly 28 percent more—because it correctly recovers requirements that standard retrieval misses outright, and that trade-off costs something. The core claim here is about correctness, not compression. The system used more tokens and produced better task accuracy, rather than a smaller footprint."Irrelevant Retrieved" Is Not a Single NumberConditionNoise ChatterDropped in VerificationExtra Beyond ChecklistStandard keyword search1106Intent-aware055Eleven of standard retrieval's seventeen irrelevant hits are pure chatter matched by lexical accident, which is genuine junk with zero relation to the task.Intent-aware retrieval's ten extras split evenly: five are true noise correctly filtered out during verification, and five are records that reach the agent without being on that specific task's graded checklist. Those five extra records are real, currently valid context, not errors.That distinction matters. Standard retrieval's extras are not guaranteed to be right; they are just retrieved. For Task T3, standard retrieval pulls in both the correct production database decision and the stale prototype decision, and the stale one wins the field because there is no verification layer to stop it.Per-Task Pass/FailTaskBaselineStandard Keyword SearchIntent-awareT1FAIL (3)FAIL (2)PASST2FAIL (2)PASSPASST3FAIL (1)FAIL (1)PASST4FAIL (4)FAIL (3)PASST5FAIL (1)PASSPASST6FAIL (1)FAIL (1)PASST7FAIL (1)PASSPASST8FAIL (1)PASSPASSThe Time I Almost Shipped a Rigged ExperimentThe domain schema described in Component 2 went through an earlier version that was much narrower and much worse. I had declared those component relationships per task instead of globally. Task T1 was individually told in advance, "you also depend on api and security." Task T4 was individually told, "you also depend on security and testing." Those two hand-picked declarations happened to be exactly the components those two tasks' correct answers needed, and nothing more.That is not a discovery mechanism. That is an answer key dressed up as a retrieval rule, and it directly undercut the entire premise of this project, which is supposed to work without being told where to look.I caught it the only way that actually works: I deleted the per-task hint and reran the experiment with nothing put in its place.Version With vs. Without the Per-Task Hint (Ablation)VersionTasks PassedFailing TasksPer-task hint (original)8/8NoneHint removed, nothing replacing it6/8T1, T4The drop was not subtle. It failed exactly the two tasks that had been individually hand-fed their answers—proof that the hint had been doing real, load-bearing work the entire time. I had nearly missed it simply because the final aggregate score looked good.The fix, shown in full in Component 2, was replacing the per-task hint with one general schema, authored a single time, and applied uniformly to every task, including the six that never needed the extra help.Applying it uniformly rather than selecting it cost something real. Irrelevant records retrieved rose from 4 to 10, and tokens supplied rose from 161 to 199, because the schema now also fires harmlessly for tasks that never needed it. The result stayed at 8 out of 8, but this time, it earned it.A synthetic benchmark you built yourself is the easiest thing in the world to unconsciously rig, because you already know the answers before you write the test. Delete the part you suspect is doing too much heavy lifting and see what breaks. It is the only sanity check that actually worked here.Honest Design DecisionsThe component and keyword dictionaries are hand-authored for this domain rather than learned. This is a controlled demonstration of the verification mechanism, not a general-purpose extraction system you could point at an arbitrary codebase tomorrow.Retrieval in this experiment uses plain lexical word overlap instead of an embedding model or vector database. This choice keeps retrieval quality from becoming a confounding variable. If I had used a specific embedding model, a skeptical reader could reasonably argue the whole comparison depended on which model I happened to pick. Swapping in a real embedding model would likely change the recall numbers for standard retrieval, but it would not change the underlying argument. The verification step is what does the interesting work, and it remains agnostic to how candidates were found.Eight tasks and twelve requirements make up a demonstration rather than a statistically powered study. The scope is sized to be fully inspectable and reproducible, not to generalize with confidence to arbitrary production codebases.The simulated agent is a deterministic template on purpose, not a real coding language model. This ensures every result is attributable strictly to what each retrieval strategy recovered, rather than to model behavior on a given day.The checker only tests fields explicitly declared in each task's ground truth. That is a narrow rubric by design, not a general measure of code quality.Trade-offs and What's MissingReal extraction: The rule-based extractor works here because I control the dataset. A production version would need a genuinely robust extraction front end, likely a small classifier rather than a simple trigger-phrase list.Embedding-based candidate retrieval: The retrieval step is a clean swap point. Drop in an embedding model for candidate generation and the verification layer downstream does not need to change at all.A real coding LLM: The deterministic agent template exists specifically to isolate what each retrieval strategy recovers. Replacing it with an actual model would test a different hypothesis: whether the model correctly uses the recovered context, rather than just whether the context was successfully found.Cross-session persistence: Everything here runs completely in-process. A lightweight persistent store sharing the same record interface would allow intent continuity to survive across restarts.ClosingRetrieval gets you what's related. Verification gets you what's valid. Intent continuity gets you what still matters, right now, for the task in front of you.Most systems optimize the first and skip the other two. That's why they keep shipping code that quietly breaks a decision someone made weeks ago.Complete code: https://github.com/Emmimal/intent-continuity/References[1] Lewis, P., Perez, E., Piktus, A., et al. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS 33, 9459–9474. https://arxiv.org/abs/2005.11401[2] Liu, N. F., Lin, K., Hewitt, J., et al. (2023). Lost in the Middle: How Language Models Use Long Contexts. arXiv:2307.03172. https://arxiv.org/abs/2307.03172DisclosureAll code in this article was written by me. It is original work, developed and tested on Python 3.12. All benchmark numbers come from actual runs of the system and are reproducible by cloning the repository and running run_experiment.py. None of the results were calculated or simulated after the fact.The system uses zero external dependencies and runs entirely on the Python standard library. It does not use an embedding model, vector database, or LLM API. I have no financial relationship with any tool, library, or company mentioned in this article.All diagrams in this article, including the featured image, were created by the author. The featured image was generated with ChatGPT (DALL·E).

Original Source

Read the full article at Towardsdatascience →

KhanList aggregates and links to publicly available news content. We do not host full articles from third-party sources. Always verify important information with original sources.