<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Likhan Siddiquee</title>
    <description>The latest articles on DEV Community by Likhan Siddiquee (@likhan).</description>
    <link>https://dev.to/likhan</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2074020%2F69feec62-49a8-455a-beda-596a8618a826.jpg</url>
      <title>DEV Community: Likhan Siddiquee</title>
      <link>https://dev.to/likhan</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/likhan"/>
    <language>en</language>
    <item>
      <title>Architectural Debt is the New Technical Debt</title>
      <dc:creator>Likhan Siddiquee</dc:creator>
      <pubDate>Thu, 01 Oct 2026 11:03:29 +0000</pubDate>
      <link>https://dev.to/likhan/architectural-debt-is-the-new-technical-debt-4dlf</link>
      <guid>https://dev.to/likhan/architectural-debt-is-the-new-technical-debt-4dlf</guid>
      <description>&lt;p&gt;Imagine a request to show payment status on an order page. An agent finds the order endpoint, adds a &lt;code&gt;paymentStatus&lt;/code&gt; column to the orders table, copies a field from the billing response, and updates the UI. The tests pass. The ticket closes.&lt;/p&gt;

&lt;p&gt;Which service owns the truth when a refund happens tomorrow?&lt;/p&gt;

&lt;p&gt;If billing owns payments but orders stores a second status, someone now has to define how those values converge. A retry, an out-of-order event, or a partial failure can show a paid order as unpaid. The added code may be tidy. The decision about ownership is not.&lt;/p&gt;

&lt;p&gt;That is architectural debt: the cost of a missing or compromised boundary, paid on every later change that crosses it. AI makes it easier to incur while making local code cleanup cheaper. The scarce skill is deciding what a system means and which part is allowed to say so.&lt;/p&gt;

&lt;h2&gt;
  
  
  The marginal cost of code fell, not the cost of change
&lt;/h2&gt;

&lt;p&gt;Generating a handler, test, and migration takes less typing than it used to. An agent can also rewrite an awkward function, remove duplication, and fill in boilerplate quickly. Those are useful gains. They do not make the output free: someone still has to review behavior, migrate data, deploy the change, and support it when production disagrees with the test fixture.&lt;/p&gt;

&lt;p&gt;Lint errors and duplicate code still matter, especially when they obscure a bug. But a clean lint report says nothing about whether &lt;code&gt;paymentStatus&lt;/code&gt; belongs on an order. The local quality of the patch and the global shape of the system are different questions. Faster patching shifts attention toward the question that cannot be answered by rewriting a file.&lt;/p&gt;

&lt;p&gt;I wrote about the &lt;a href="https://www.likhansiddiquee.com/blog/velocity-microphone/" rel="noopener noreferrer"&gt;velocity microphone&lt;/a&gt;: AI amplifies unclear intent and returns the consequences faster. Here the consequence is not a conspicuously broken function. It is a plausible new source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  The easy path crosses the wrong boundary
&lt;/h2&gt;

&lt;p&gt;An agent asked to make the order page show payment status will search for the code that renders the order page. The shortest path may be to extend the order model, because that is where the current request is being handled. No malicious reasoning is required. The task, the nearby code, and the tests all point at a local completion condition.&lt;/p&gt;

&lt;p&gt;Then another feature reads &lt;code&gt;orders.paymentStatus&lt;/code&gt; to decide whether to ship. A third updates it after a chargeback. Each change is defensible within its own ticket. Together they turn a display shortcut into a domain rule, and now neither orders nor billing can change its state model without coordinating with the other.&lt;/p&gt;

&lt;p&gt;The same failure takes other forms: a shared database table becomes an unofficial integration API; a frontend duplicates an authorization rule; a "temporary" mapping layer becomes the place where two services disagree about customer identity. Generated code does not create these traps. It makes the easy path fast enough to take repeatedly before the coordination cost becomes visible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Guard the contract before generating the patch
&lt;/h2&gt;

&lt;p&gt;For the order-page request, I would first ask: is payment status a billing fact or an order fact? Suppose billing owns it. The order page can request a billing-owned view or consume a deliberately defined projection. If it caches a projection, the contract must name its source, update mechanism, failure behavior, and acceptable staleness. Orders should not become an independent authority for the same status.&lt;/p&gt;

&lt;p&gt;That decision changes the agent's task. Instead of "add payment status to orders," the request becomes: "display billing's payment status on the order page; do not add a writable payment field to the orders model; specify what the page shows when billing is unavailable." The constraint is small enough to review and concrete enough to test. A contract test can catch an unauthorized write or a missing unavailable state. It cannot choose the domain owner on its own.&lt;/p&gt;

&lt;p&gt;For any domain boundary, put the contract in the instructions the coding agent actually receives: &lt;code&gt;AGENTS.md&lt;/code&gt; or &lt;code&gt;copilot-instructions.md&lt;/code&gt; for repository-wide rules, or a scoped &lt;code&gt;*.instructions.md&lt;/code&gt; for the code it governs. Name who owns each fact and state transition, how other domains may read it, which writes and dependencies are forbidden, and what happens when the owner is unavailable. Before editing, have the agent identify the relevant owners and explain how its proposed change crosses their boundary. If the instructions are missing or conflict with the code, or the request needs a new owner or exception, it should stop and ask the user to decide. It must not treat the nearest model as permission.&lt;/p&gt;

&lt;p&gt;Instructions make a boundary visible; checks make violations harder to ship. Where possible, add dependency rules, schema ownership checks, and contract tests for the allowed exchange and its failure path. Have the agent run those checks and report what they cover. They cannot prove that the domain model is correct, so a person still has to judge new ownership and consistency tradeoffs.&lt;/p&gt;

&lt;p&gt;This is the boundary guardian's work: name who owns a fact, define the permitted exchange, and make violations visible in scoped instructions, tests, APIs, and review. The goal is not to forbid every cross-service dependency. It is to give an agent a durable constraint it can follow and a check it cannot wave away, so the next change does not silently invent a second model.&lt;/p&gt;

&lt;p&gt;The same reasoning applies to planning. I argued that &lt;a href="https://www.likhansiddiquee.com/blog/sprint-planning-after-ai/" rel="noopener noreferrer"&gt;sprint plans still point at an old bottleneck&lt;/a&gt;. When implementation is faster, it is tempting to queue more small features. Before calling one small, check whether it adds a new owner, projection, or consistency rule. A one-line field can be the most expensive part of the feature.&lt;/p&gt;

&lt;p&gt;Code generation has changed what I can finish in an afternoon. It has not removed the need to decide where a fact belongs. If I skip that decision, faster implementation means I can accumulate architectural debt faster too.&lt;/p&gt;

&lt;p&gt;Where have you seen a tidy local change create a second owner for the same fact? What boundary would have caught it?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.likhansiddiquee.com/blog/architectural-debt-ai/" rel="noopener noreferrer"&gt;https://www.likhansiddiquee.com/blog/architectural-debt-ai/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Task state is a call stack for agent work</title>
      <dc:creator>Likhan Siddiquee</dc:creator>
      <pubDate>Mon, 31 Aug 2026 20:19:20 +0000</pubDate>
      <link>https://dev.to/likhan/task-state-is-a-call-stack-for-agent-work-a22</link>
      <guid>https://dev.to/likhan/task-state-is-a-call-stack-for-agent-work-a22</guid>
      <description>&lt;p&gt;I was one hero image away from publishing &lt;a href="https://www.likhansiddiquee.com/blog/sprint-planning-after-ai/" rel="noopener noreferrer"&gt;a post about sprint planning&lt;/a&gt;. Generate it, wire the manifest, build the route. Three steps, then done.&lt;/p&gt;

&lt;p&gt;I opened the image prompt for one last look and found it arguing with itself. It named colours in the same prompt that told the generator to avoid them. I asked why. That small question pulled us out of publishing and into an audit of every image instruction in the repository.&lt;/p&gt;

&lt;p&gt;The corrected render arrived as a 1.63 MB PNG. I asked another small question: is that normal? Now we were comparing existing heroes, JPEG quality, chroma sampling, and edge artifacts. A second investigation had opened inside the first task.&lt;/p&gt;

&lt;p&gt;The agent did not start either tangent. I did. On another day the agent spots the neighboring defect and asks whether to follow it. The risk is the same: the original task falls out of active context while one of us follows the more interesting question.&lt;/p&gt;

&lt;p&gt;At the bottom of this stack, the post still needed its manifest entry, route build, and removal of a forward link to an unpublished part two.&lt;/p&gt;

&lt;p&gt;We returned to all three because the task had a return address.&lt;/p&gt;

&lt;h2&gt;
  
  
  Task state is a call stack
&lt;/h2&gt;

&lt;p&gt;A to-do list records what remains. A call stack records more: the current frame, its local variables, and the address where execution should return when a nested call finishes.&lt;/p&gt;

&lt;p&gt;That is what &lt;code&gt;.local/scratch/task-state.md&lt;/code&gt; does for my agent work.&lt;/p&gt;

&lt;p&gt;In the analogy, the current frame is the immediate question. Its local variables are the working hypothesis, evidence, rejected paths, blockers, and decisions made so far. Its return address is the next step still waiting in the ledger.&lt;/p&gt;

&lt;p&gt;Before either of us changes focus, the instruction tells the agent to checkpoint the ledger. The tangent becomes the current work while the unfinished task remains recorded beneath it. When the tangent closes, we update its result and resume the waiting next step.&lt;/p&gt;

&lt;p&gt;In call-stack terms, the publishing task can be read like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publish sprint-planning post
  -&amp;gt; create hero
     -&amp;gt; I notice excluded colours inside the positive prompt
        -&amp;gt; we inspect the prompt rules
        -&amp;gt; return: corrected sidecar
     -&amp;gt; I question the 1.63 MB render
        -&amp;gt; we audit the export settings
        -&amp;gt; return: normalized hero
  -&amp;gt; wire manifest
  -&amp;gt; remove unpublished forward link
  -&amp;gt; build and inspect route shell
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The stack did not stop us from going deep. It made the deep dive reversible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The tangent can start on either side
&lt;/h2&gt;

&lt;p&gt;Most descriptions of agent drift make the agent sound like the only source. It chases a neighboring bug, reads too many files, or turns a local fix into an architecture review.&lt;/p&gt;

&lt;p&gt;I interrupt the plan too. I notice a strange output and ask how it happened. I challenge an assumption halfway through implementation. I open a related file while the agent is running and bring a new concern back into chat. Sometimes the side question is more useful than the task that&lt;br&gt;
exposed it.&lt;/p&gt;

&lt;p&gt;From the work's point of view, ownership of the tangent does not matter. Focus changed while a parent task remained unfinished. The parent needs a snapshot before the new question consumes the active context.&lt;/p&gt;

&lt;p&gt;This is why task state has to be shared. It is not the agent's private plan, and it is not my private set of browser tabs. It is the handoff surface between our two attention streams.&lt;/p&gt;

&lt;h2&gt;
  
  
  The internal todo tracker is not enough
&lt;/h2&gt;

&lt;p&gt;An agent's internal todo tracker is useful. It can decompose the requested work, mark steps in progress, and show whether the current turn is moving.&lt;/p&gt;

&lt;p&gt;I do not treat it as durable task memory.&lt;/p&gt;

&lt;p&gt;It belongs to the active agent session. It may not survive context compaction, a new chat, an editor restart, or a switch to another agent. More importantly, it cannot see a branch that starts in my head. If I inspect an image, question a design choice, or return after lunch with a different hypothesis, the internal plan does not know until I explain the change.&lt;/p&gt;

&lt;p&gt;A todo item also tends to preserve the action while losing the state around it. "Normalize hero" does not say which settings were tested, why one candidate failed, what still needs visual judgment, or where publishing resumes afterward.&lt;/p&gt;

&lt;p&gt;The local task-state file is visible to both of us. I can correct it. The agent can update it. A new session can read it before reconstructing the task from Git and a partial transcript. Because it lives in the workspace rather than the chat, the work does not depend on one session remaining alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I keep the stack
&lt;/h2&gt;

&lt;p&gt;I do not rely on remembering to take a snapshot once the tangent has become interesting. This repository's always-on &lt;a href="https://github.com/lsiddiquee/mysite/blob/main/.github/copilot-instructions.md#context-persistence-never-lose-work-on-tangents" rel="noopener noreferrer"&gt;context-persistence instructions&lt;/a&gt; carry the protocol. Copilot reads that file as repository guidance, so the checkpoint triggers arrive with each new session rather than remaining in the chat that may disappear.&lt;/p&gt;

&lt;p&gt;The instruction is explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For any non-trivial, multi-step task, ensure &lt;code&gt;.local/scratch/task-state.md&lt;/code&gt; exists before the first edit. Create it only when absent.&lt;/li&gt;
&lt;li&gt;If it already exists, read and reconcile it with the request and working tree. Preserve unfinished state; never truncate the file to start a new task.&lt;/li&gt;
&lt;li&gt;Record the current focus, in-flight items and their status, the next step, open threads, and key findings.&lt;/li&gt;
&lt;li&gt;Checkpoint before switching focus or deep-diving, when a new sub-issue appears, and after a meaningful step completes.&lt;/li&gt;
&lt;li&gt;Re-read and reconcile the file when resuming.&lt;/li&gt;
&lt;li&gt;When completed history makes the active file hard to scan, move it to &lt;code&gt;.local/scratch/task-state-completed.md&lt;/code&gt; and promote any durable finding into tracked documentation in the same pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is how I ensure snapshotting happens at the transition, not as cleanup after context is lost. It is still an instruction, not a transactional runtime guarantee. Its advantage is that the rule survives the session, the artifact is visible to both of us, and a missed checkpoint is reviewable.&lt;/p&gt;

&lt;p&gt;The storage choice is part of the same instruction. This repository explicitly lists &lt;code&gt;.local/&lt;/code&gt; in &lt;code&gt;.gitignore&lt;/code&gt; because the contents are local until promoted. A current hypothesis, unfinished frame, or rejected path should not be pushed to every contributor as project guidance.&lt;/p&gt;

&lt;p&gt;Gitignored does not mean disposable. The devcontainer bind-mounts the repository workspace from the host. Rebuilding the container replaces the environment, not &lt;code&gt;.local/scratch/task-state.md&lt;/code&gt; inside that workspace. The stack is still there when the new container opens.&lt;/p&gt;

&lt;p&gt;I do not use VS Code chat memory as that recovery layer. A transcript may remain visible, but a new chat is not guaranteed to load it as working context; compaction can also remove the detail needed to resume. The task-state file has a stable path that the repository instruction tells the next session to read.&lt;/p&gt;

&lt;p&gt;The ledger has no fixed schema. This run uses &lt;code&gt;Current focus&lt;/code&gt;, &lt;code&gt;Ledger&lt;/code&gt;, &lt;code&gt;Chosen metadata&lt;/code&gt;, and &lt;code&gt;Boundaries&lt;/code&gt;; older states also use &lt;code&gt;Key findings&lt;/code&gt;, &lt;code&gt;Remaining&lt;/code&gt;, and &lt;code&gt;Gotchas&lt;/code&gt;. The headings follow the work.&lt;/p&gt;

&lt;p&gt;There is no literal stack API or required &lt;code&gt;Return&lt;/code&gt; field. I read &lt;code&gt;in-progress&lt;/code&gt; as the top frame, a pending item plus its next step as the return address, and open threads and findings as the state needed to resume. Completing the tangent and returning to that pending item behaves like a pop. Finished history moves into &lt;code&gt;task-state-completed.md&lt;/code&gt;; reusable findings are distilled into their tracked owner.&lt;/p&gt;

&lt;h2&gt;
  
  
  I ask the session what remains
&lt;/h2&gt;

&lt;p&gt;Most of the time I do not open the task-state file myself. I ask the session:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What are my pending items?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent reads the ledger, reconciles it with the working tree, and gives me the unfinished items with their current status. I choose one. That item becomes the current work, and the session already has the findings, blockers, and next step recorded around it.&lt;/p&gt;

&lt;p&gt;The question works in the same chat, after a context compaction, or in a new session because the answer comes from the workspace artifact. I am not asking the model to recall an old conversation. I am asking it to query shared state, then continue from the item I select.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is a protocol, not a file format
&lt;/h2&gt;

&lt;p&gt;Markdown under &lt;code&gt;.local/&lt;/code&gt; is how I implement the idea in these repositories. It is not the only way. An issue, worktree note, session database, or orchestration system can carry the same state.&lt;/p&gt;

&lt;p&gt;The direction matters more than the storage choice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The state is shared between the human and the agent.&lt;/li&gt;
&lt;li&gt;It survives longer than one chat or internal plan.&lt;/li&gt;
&lt;li&gt;A tangent records its parent return address before consuming the context.&lt;/li&gt;
&lt;li&gt;Resume begins by loading that state, not reconstructing it from the current diff.&lt;/li&gt;
&lt;li&gt;Completed frames leave the active stack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without those properties, changing the filename does not solve the handoff problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shared memory makes curiosity affordable
&lt;/h2&gt;

&lt;p&gt;I wrote about AI acting as a&lt;br&gt;
&lt;a href="https://www.likhansiddiquee.com/blog/velocity-microphone/" rel="noopener noreferrer"&gt;velocity microphone&lt;/a&gt;: it amplifies unclear intent and returns the consequences faster. It also makes investigation cheap. A side question can become a twenty-file audit before either of us feels the cost.&lt;/p&gt;

&lt;p&gt;I do not want to remove that curiosity from the workflow. I want a reliable way back.&lt;/p&gt;

&lt;p&gt;The ledger preserves the parent task's return address through context compaction, agent changes, and my own detours.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The velocity microphone: AI amplifies unclear intent</title>
      <dc:creator>Likhan Siddiquee</dc:creator>
      <pubDate>Mon, 24 Aug 2026 09:45:38 +0000</pubDate>
      <link>https://dev.to/likhan/the-velocity-microphone-ai-amplifies-unclear-intent-48i8</link>
      <guid>https://dev.to/likhan/the-velocity-microphone-ai-amplifies-unclear-intent-48i8</guid>
      <description>&lt;p&gt;Twenty minutes prompting. Three days repairing.&lt;/p&gt;

&lt;p&gt;That story is often used as proof that AI writes bad software.&lt;/p&gt;

&lt;p&gt;Sometimes it does. Models invent APIs, miss edge cases, and make poor design choices.&lt;/p&gt;

&lt;p&gt;But another failure is hiding inside that complaint: the classic tree-swing problem, compressed to machine speed.&lt;/p&gt;

&lt;p&gt;The engineer knows they want a swing. The brief asks for a “dynamic, scalable outdoor kinetic relaxation mechanism.” The model fills the gaps with a plausible direction. Nobody challenges the assumption.&lt;/p&gt;

&lt;p&gt;Twenty minutes later, the first real integration is also the first real test.&lt;/p&gt;

&lt;p&gt;A hallucinated API is a model failure.&lt;/p&gt;

&lt;p&gt;A coherent implementation of an unspoken assumption is an agreement failure.&lt;/p&gt;

&lt;p&gt;AI did not remove requirements engineering. It removed the waiting that used to expose weak requirements before they became architecture.&lt;/p&gt;

&lt;p&gt;The response is not one perfect prompt. It is a governance stack:&lt;/p&gt;

&lt;p&gt;• Durable repository instructions that preserve architectural intent&lt;br&gt;&lt;br&gt;
• Specialized agent and workflow files that define the job&lt;br&gt;&lt;br&gt;
• Task briefs with boundaries and acceptance evidence&lt;br&gt;&lt;br&gt;
• Scoped tools and permissions that limit the blast radius&lt;br&gt;&lt;br&gt;
• Hooks, tests, lint, schemas, security scans, and CI&lt;br&gt;&lt;br&gt;
• Human review and release gates for the remaining uncertainty  &lt;/p&gt;

&lt;p&gt;Instructions steer.&lt;br&gt;
Permissions constrain.&lt;br&gt;
Checks reject.&lt;br&gt;
Humans own the release.&lt;/p&gt;

&lt;p&gt;AI is a velocity microphone. It amplifies the signal, the ambiguity, and the consequences.&lt;/p&gt;

&lt;p&gt;I wrote about how to keep that speed useful:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.likhansiddiquee.com/blog/velocity-microphone/" rel="noopener noreferrer"&gt;https://www.likhansiddiquee.com/blog/velocity-microphone/&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  AgenticAI #SoftwareEngineering #AIGovernance #DeveloperExperience
&lt;/h1&gt;

</description>
      <category>agenticai</category>
      <category>ai</category>
      <category>aigovernance</category>
      <category>agents</category>
    </item>
  </channel>
</rss>
