<?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: Juan Saez</title>
    <description>The latest articles on DEV Community by Juan Saez (@saez520).</description>
    <link>https://dev.to/saez520</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%2F3663214%2F92edb0a3-4836-4a64-a5f9-235d07e61f4b.jpg</url>
      <title>DEV Community: Juan Saez</title>
      <link>https://dev.to/saez520</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saez520"/>
    <language>en</language>
    <item>
      <title>Worktree lifecycle for AI agents</title>
      <dc:creator>Juan Saez</dc:creator>
      <pubDate>Thu, 15 Oct 2026 20:50:25 +0000</pubDate>
      <link>https://dev.to/saez520/worktree-lifecycle-for-ai-agents-1ooo</link>
      <guid>https://dev.to/saez520/worktree-lifecycle-for-ai-agents-1ooo</guid>
      <description>&lt;h2&gt;
  
  
  The problem is not creating worktrees, but managing them
&lt;/h2&gt;

&lt;p&gt;When two or more agents work in parallel on different features, a problem&lt;br&gt;
appears that Git does not solve by itself: how to manage changes so the agents&lt;br&gt;
do not interfere with one another.&lt;/p&gt;

&lt;p&gt;One possible strategy is agent-to-agent communication. Agents can delegate&lt;br&gt;
tasks, transfer context, declare ownership, and perform handoffs. This helps&lt;br&gt;
coordinate intent and dependencies, but it is not physical isolation. If one&lt;br&gt;
agent has to wait for another agent's response, contract, or commit before it&lt;br&gt;
can continue, the supposed parallelism can become a sequence of coordinated&lt;br&gt;
steps.&lt;/p&gt;

&lt;p&gt;Claude Code's documentation makes this distinction explicit: agent teams can&lt;br&gt;
communicate and share tasks, while worktrees are used when changes to files&lt;br&gt;
need to be isolated.&lt;/p&gt;

&lt;p&gt;Worktrees complement that strategy. Each agent works in its own working tree&lt;br&gt;
and can modify files without directly overwriting another agent's checkout.&lt;br&gt;
This reduces interference during editing, but it does not eliminate semantic&lt;br&gt;
conflicts: two agents can still design incompatible changes or depend on&lt;br&gt;
different interfaces.&lt;/p&gt;

&lt;p&gt;Communication does not disappear either. It may still be necessary to&lt;br&gt;
coordinate a contract, a commit, a merge, or a later integration.&lt;/p&gt;

&lt;p&gt;That raises another question: what happens after a worktree is created, and&lt;br&gt;
how is its lifecycle managed?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdttq0hec41qeigkwwnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhdttq0hec41qeigkwwnw.png" alt="Isolation is not lifecycle" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  An isolated worktree has no lifecycle by itself
&lt;/h2&gt;

&lt;p&gt;Git provides worktrees, but it does not define how their entire lifecycle&lt;br&gt;
should be managed.&lt;/p&gt;

&lt;p&gt;A first management layer can be found in the&lt;br&gt;
&lt;a href="https://www.skills.sh/obra/superpowers/using-git-worktrees" rel="noopener noreferrer"&gt;&lt;code&gt;obra/superpowers/using-git-worktrees&lt;/code&gt;&lt;/a&gt;&lt;br&gt;
skill. It detects whether the agent is already inside a worktree, distinguishes&lt;br&gt;
a submodule from a worktree, selects a location, verifies that the directory&lt;br&gt;
is ignored, creates the worktree, prepares dependencies, and runs a test&lt;br&gt;
baseline.&lt;/p&gt;

&lt;p&gt;That flow solves provisioning and initial setup. Operational questions still&lt;br&gt;
remain: how is the work integrated? What happens when a merge fails? How do we&lt;br&gt;
avoid removing a branch with an unexpected state? What happens if the process&lt;br&gt;
dies during shutdown? How are local changes in the destination recovered?&lt;/p&gt;

&lt;p&gt;That is the space &lt;code&gt;worktree.sh&lt;/code&gt; attempts to cover: not replacing Git's&lt;br&gt;
worktree creation, but adding a lifecycle layer for agents that create, use,&lt;br&gt;
integrate, and abandon work environments in an automated way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7we8liguvnk4j4mc4cr6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7we8liguvnk4j4mc4cr6.png" alt="Worktree lifecycle" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  &lt;code&gt;worktree.sh&lt;/code&gt;: a lifecycle for worktrees
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;worktree.sh&lt;/code&gt; is a tool for managing the lifecycle of worktrees. Its&lt;br&gt;
parent-only interface includes these operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;create&lt;/code&gt;: creates and validates the worktree and its working branch.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;complete&lt;/code&gt;: verifies the state, performs the squash merge, and removes
resources when the closing conditions are met.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;cleanup&lt;/code&gt;: removes the worktree without integrating it; destructive discard
requires &lt;code&gt;--discard&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;recover&lt;/code&gt;: resumes pending &lt;code&gt;preserve-dirty&lt;/code&gt; transactions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;prune&lt;/code&gt;: cleans up Git's worktree records and reports unassociated
directories.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;list&lt;/code&gt;: shows the worktrees registered by Git.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tool also adds ID validation, base-branch selection, predictable paths and&lt;br&gt;
branches, locks for incompatible operations, and journals that record closing&lt;br&gt;
phases.&lt;/p&gt;
&lt;h2&gt;
  
  
  An agent's workflow
&lt;/h2&gt;

&lt;p&gt;The workflow starts when the agent receives a &lt;code&gt;work-id&lt;/code&gt;. From that ID,&lt;br&gt;
&lt;code&gt;worktree.sh&lt;/code&gt; creates a separate branch and working tree from the detected base&lt;br&gt;
branch or from the branch provided explicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;work request
    ↓
create worktree
    ↓
agent modifies isolated files
    ↓
prepare / verify, when configured
    ↓
complete or cleanup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A minimal way to start work is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.flowtask/scripts/worktree.sh create hotfix/test-001 &lt;span class="nt"&gt;--base&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent works inside the worktree without directly modifying the shared&lt;br&gt;
checkout. When it finishes, it can run configured validations and close the&lt;br&gt;
work with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.flowtask/scripts/worktree.sh &lt;span class="nb"&gt;complete &lt;/span&gt;hotfix/test-001 &lt;span class="nt"&gt;--base&lt;/span&gt; main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;complete&lt;/code&gt; verifies the worktree's identity and state, integrates commits with&lt;br&gt;
a squash merge, and only then removes the branch and worktree when the closing&lt;br&gt;
validations succeed.&lt;/p&gt;

&lt;p&gt;If the work must be abandoned without integration, the workflow uses&lt;br&gt;
&lt;code&gt;cleanup&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;.flowtask/scripts/worktree.sh cleanup hotfix/test-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Forced discard requires explicit authorization through &lt;code&gt;--discard&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyejtgtab4kkm4t4gzcrd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyejtgtab4kkm4t4gzcrd.png" alt="An agent using a managed worktree" width="800" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There is also a more delicate case: the destination may contain local changes&lt;br&gt;
that should not become commits yet. With &lt;code&gt;--preserve-dirty&lt;/code&gt;, the tool captures&lt;br&gt;
eligible tracked and untracked changes, temporarily cleans the destination,&lt;br&gt;
performs the squash merge, and then attempts to restore those changes.&lt;/p&gt;

&lt;p&gt;Ignored files are not part of that preservation. If restoration encounters a&lt;br&gt;
conflict, the transaction becomes &lt;code&gt;pending_manual&lt;/code&gt;: the backup is preserved and&lt;br&gt;
resolution requires human intervention. In that case, &lt;code&gt;recover&lt;/code&gt; can resume the&lt;br&gt;
transaction, but it does not resolve the conflict automatically.&lt;/p&gt;
&lt;h2&gt;
  
  
  What happens when something fails
&lt;/h2&gt;

&lt;p&gt;The lifecycle must also define what happens if a process dies during a merge,&lt;br&gt;
the worktree changes, or restoration finds a different state.&lt;/p&gt;

&lt;p&gt;For that purpose, &lt;code&gt;worktree.sh&lt;/code&gt; records closing phases in journals. A journal&lt;br&gt;
identifies which steps have been confirmed and from where an operation can be&lt;br&gt;
resumed. It is not merely an informational log: it is part of the state the&lt;br&gt;
tool uses to decide whether it can continue or must stop.&lt;/p&gt;

&lt;p&gt;For example, if &lt;code&gt;complete&lt;/code&gt; attempts a squash merge and encounters a conflict,&lt;br&gt;
the closing operation stops and preserves the worktree and branch. The tool&lt;br&gt;
does not remove resources that may be needed to investigate or resolve the&lt;br&gt;
conflict.&lt;/p&gt;

&lt;p&gt;In a &lt;code&gt;--preserve-dirty&lt;/code&gt; transaction, if the process dies after capturing the&lt;br&gt;
destination changes but before restoring them, &lt;code&gt;recover&lt;/code&gt; can resume the flow&lt;br&gt;
from the recorded state.&lt;/p&gt;

&lt;p&gt;If restoration encounters a conflict, the transaction enters &lt;code&gt;pending_manual&lt;/code&gt;,&lt;br&gt;
keeps the backup, and requires human intervention. &lt;code&gt;recover&lt;/code&gt; can continue the&lt;br&gt;
reconciliation after the state is corrected, but it does not decide how to&lt;br&gt;
resolve the conflict.&lt;/p&gt;

&lt;p&gt;This behavior is deliberate: when the state cannot be verified, the tool&lt;br&gt;
prefers to stop and preserve resources rather than perform an irreversible&lt;br&gt;
action.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tool's current state
&lt;/h2&gt;

&lt;p&gt;The current manager has a &lt;strong&gt;parent-only&lt;/strong&gt; flow—the main repository creates and&lt;br&gt;
closes its own worktrees—and a submodule flow.&lt;/p&gt;

&lt;p&gt;Submodule initialization is explicit: it does not run by default when an agent&lt;br&gt;
creates a worktree. This avoids populating dependencies the agent does not&lt;br&gt;
need.&lt;/p&gt;

&lt;p&gt;To act directly on a submodule, &lt;code&gt;target-direct&lt;/code&gt; treats the submodule as the&lt;br&gt;
target repository. The flow validates its relationship with the parent&lt;br&gt;
repository, the path, the &lt;code&gt;gitlink&lt;/code&gt; (the parent's reference to the submodule&lt;br&gt;
commit), and branch identity.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;link-parent&lt;/code&gt; is also available for controlled gitlink updates, with the&lt;br&gt;
&lt;code&gt;staged-only&lt;/code&gt; mode modifying only the index; it does not commit, merge, or push&lt;br&gt;
the parent.&lt;/p&gt;

&lt;p&gt;The tool does not yet expose an MCP interface or subtree integration. Evolution&lt;br&gt;
toward MCP is already included in the project roadmap.&lt;/p&gt;
&lt;h2&gt;
  
  
  Project evolution
&lt;/h2&gt;

&lt;p&gt;The manager's roadmap is organized into these stages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Stabilize the standalone CLI
        ↓
2. Migrate the backend to Python
        ↓
3. Keep worktree.sh as a compatibility shim
        ↓
4. Expose the backend through MCP
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main direction is to separate the manager's core, preserve its guarantees&lt;br&gt;
during the migration, and later provide an interface suitable for agents and&lt;br&gt;
other orchestrators.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu562d5uvci5661ehwxrm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu562d5uvci5661ehwxrm.png" alt="Worktree manager evolution" width="800" height="398"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code and the manager's evolution are available in the&lt;br&gt;
&lt;a href="https://github.com/Saez520/worktree-manager" rel="noopener noreferrer"&gt;worktree-manager repository&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>git</category>
      <category>devops</category>
    </item>
    <item>
      <title>Why Your Multi-Turn AI Agents Lose Their Train of Thought (And How to Fix It)</title>
      <dc:creator>Juan Saez</dc:creator>
      <pubDate>Wed, 10 Jun 2026 15:36:09 +0000</pubDate>
      <link>https://dev.to/saez520/why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it-4be2</link>
      <guid>https://dev.to/saez520/why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it-4be2</guid>
      <description>&lt;h2&gt;
  
  
  1. The Agent That Forgot Everything
&lt;/h2&gt;

&lt;p&gt;I have an agent that clarifies requirements. I give it a problem, it asks questions, I answer, it refines, and after three or four rounds it should have a spec ready. Simple.&lt;/p&gt;

&lt;p&gt;Round one works fine. It asks reasonable questions. I answer. But when I ask it to continue — same session, same agent, next step in the pipeline — the clarifier starts from scratch. It repeats questions I already answered. It ignores constraints we already agreed on. Sometimes it contradicts its own analysis from five minutes ago.&lt;/p&gt;

&lt;p&gt;This isn't an LLM bug. It's an architecture problem. Claude Code, OpenCode, and pretty much any coding agent that delegates work to subagents shares the same behavior: every invocation of a subagent — even the same one, to continue a conversation — creates a &lt;strong&gt;brand new session&lt;/strong&gt;. No history. No context. No memory of what that subagent already thought, asked, or decided.&lt;/p&gt;

&lt;p&gt;The good news: the infrastructure to fix this already exists in these tools. It's just that nobody uses it by default.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. How Coding Agents Delegate
&lt;/h2&gt;

&lt;p&gt;The pattern is the same everywhere. A main agent receives your prompt, reasons about it, and when it needs specialized help, it calls a delegation tool — typically named &lt;code&gt;task()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// OpenCode — tool/task.ts, lines 144-155&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taskID&lt;/span&gt;
  &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SessionID&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;catchCause&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Effect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;succeed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nextSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt;                    &lt;span class="c1"&gt;// If it exists, reuse it&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;yield&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;     &lt;span class="c1"&gt;// Otherwise, create a new one&lt;/span&gt;
    &lt;span class="na"&gt;parentID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;params.description + ` (@${next.name} subagent)`,&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="na"&gt;permission&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[...],&lt;/span&gt;
  &lt;span class="p"&gt;}))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the flow: the LLM decides to call &lt;code&gt;task()&lt;/code&gt;, the system looks up the subagent definition (permissions, model, system prompt), creates a new session with &lt;code&gt;parentID&lt;/code&gt; pointing to the main session, and kicks off an independent LLM loop. The subagent does its work, returns the result, and the main agent continues.&lt;/p&gt;

&lt;p&gt;Claude Code exposes &lt;code&gt;resume: sessionId&lt;/code&gt; in its SDK for exactly this — the same pattern: pass the session ID and the agent resumes with full history; omit it and a new session is created. OpenCode has the &lt;code&gt;task_id&lt;/code&gt; parameter that lets you resume an existing session, but &lt;strong&gt;if you don't explicitly pass it, the system creates a new session&lt;/strong&gt;. And since the main agent — the one calling &lt;code&gt;task()&lt;/code&gt; — has no way of knowing which &lt;code&gt;task_id&lt;/code&gt; it used last time, the default wins every time.&lt;/p&gt;

&lt;p&gt;The subagent gets called, works, finishes. The next time you need it — even if it's the exact same agent to continue the exact same conversation — it's born with no past.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. What Actually Happens to the Session
&lt;/h2&gt;

&lt;p&gt;Here's what I found when I dug into OpenCode's source code: the infrastructure &lt;strong&gt;already persists everything&lt;/strong&gt;. The problem isn't technical — it's a design choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding 1: Sessions store EVERYTHING.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;OpenCode persists sessions in SQLite. Every message, every tool call, every output, every step of reasoning gets recorded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SessionTable         MessageTable          PartTable
────────────         ────────────          ─────────
id (PK)              id (PK)               id (PK)
parent_id (FK→self)  session_id (FK)       message_id (FK)
title                data (JSON)           session_id (FK)
agent                                      data (JSON)
time_created                               ↑ text | tool | reasoning
time_updated                               ↑ snapshots | patches
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a subagent executes 15 tool calls, analyzes 8 files, and produces a 500-word response — all of it lands on disk. And it stays there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding 2: Subagents can't delete their sessions.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Subagents' default permissions don't include &lt;code&gt;task&lt;/code&gt; or &lt;code&gt;todowrite&lt;/code&gt;. There is no &lt;code&gt;end_session&lt;/code&gt;, &lt;code&gt;close&lt;/code&gt;, &lt;code&gt;terminate&lt;/code&gt;, or anything similar. A subagent cannot — by accident or by design — destroy its own session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding 3: The LLM loop exits — it doesn't destroy.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the subagent finishes responding, the main loop checks an exit condition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// OpenCode — prompt.ts, lines 1267-1274&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;lastAssistant&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;finish&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-calls&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastAssistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;hasToolCalls&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
  &lt;span class="nx"&gt;lastUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;lastAssistant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;break&lt;/span&gt;  &lt;span class="c1"&gt;// ← Exits the loop. Nothing else.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;break&lt;/code&gt; doesn't call &lt;code&gt;sessions.remove()&lt;/code&gt;. It doesn't archive the session. It doesn't touch a single field in the database. The in-memory runner cleans itself up, but in SQLite the session sits there intact, with all its messages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding 4: No TTL, no timeout, no automatic cleanup.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I searched for &lt;code&gt;ttl&lt;/code&gt;, &lt;code&gt;expir&lt;/code&gt;, &lt;code&gt;timeout.*session&lt;/code&gt;, &lt;code&gt;auto.*delete&lt;/code&gt; across the entire codebase. Zero results for sessions. Sessions live until someone deletes them manually. They don't expire.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The irony&lt;/strong&gt;: the infrastructure already does exactly what we need. It persists context. It keeps the history. It destroys nothing. You just need to ask it to reuse a session. And all it takes is passing the right &lt;code&gt;task_id&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. The Handshake: 200 Lines That Change Everything
&lt;/h2&gt;

&lt;p&gt;The problem isn't one of capability — it's one of &lt;strong&gt;indexing&lt;/strong&gt;. OpenCode can persist sessions and resume them if you pass a &lt;code&gt;task_id&lt;/code&gt;. What it can't do is answer questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Give me the session for spec-42's clarifier"&lt;/li&gt;
&lt;li&gt;"Has spec-42's constructor finished?"&lt;/li&gt;
&lt;li&gt;"Resume the conversation with the planner where we left off"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To OpenCode, a session is &lt;code&gt;ses_1d6f79327ffe7JM4ZcELwlMV0D&lt;/code&gt;. It doesn't know what "spec-42" is, which agent ran in that session, or which step of the workflow you're on. That's domain knowledge.&lt;/p&gt;

&lt;p&gt;The handshake is the layer that translates domain knowledge into session references. Three functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Discovery&lt;/strong&gt;: given a spec ID, find the right session's &lt;code&gt;task_id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naming&lt;/strong&gt;: instead of &lt;code&gt;ses_1d6f79327ffe&lt;/code&gt;, you see &lt;code&gt;Kael-planner&lt;/code&gt;, &lt;code&gt;Aitana-validator&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration state&lt;/strong&gt;: is the planner running? Did the validator approve?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The analogy is DNS. A web server can serve content if you give it the right IP. DNS translates &lt;code&gt;github.com&lt;/code&gt; to &lt;code&gt;140.82.121.3&lt;/code&gt;. The handshake translates &lt;code&gt;spec-42 → constructor&lt;/code&gt; to &lt;code&gt;ses_1d6e78035ffe&lt;/code&gt;. It doesn't replace persistence. It complements it.&lt;/p&gt;

&lt;p&gt;In practice, two scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario A — New&lt;/strong&gt;: No &lt;code&gt;task_id&lt;/code&gt; exists for this agent. Call &lt;code&gt;task()&lt;/code&gt; normally. Capture the &lt;code&gt;task_id&lt;/code&gt; from the response. Persist it in a map: &lt;code&gt;"spec-42/constructor" → "ses_1d6e78035ffe"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scenario B — Resume&lt;/strong&gt;: A &lt;code&gt;task_id&lt;/code&gt; already exists. Retrieve it from the map. Call &lt;code&gt;task()&lt;/code&gt; with that &lt;code&gt;task_id&lt;/code&gt;. OpenCode loads the full session. The agent doesn't "remember" by magic — it sees its entire history.&lt;/p&gt;

&lt;p&gt;The result: a 5-agent pipeline (clarifier → planner → auditor → constructor → validator) where each agent can resume with full context. Seven iterations on the same spec without losing a single reference.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Three Benefits You Didn't Expect
&lt;/h2&gt;

&lt;p&gt;The obvious benefit is that the agent stops repeating questions. But there are three more that only show up in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fewer tokens, lower latency.&lt;/strong&gt; When an agent resumes its session, it doesn't need to re-run &lt;code&gt;grep&lt;/code&gt; to find the relevant files, re-read documentation it already read, or re-analyze code it already understood. All of that is in the tool call history. Every tool call not re-executed is tokens saved and seconds the user doesn't wait for.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real iterative refinement.&lt;/strong&gt; A clarifier that goes through three rounds of questions sharpens its understanding each time. Without session continuity, round three is just as generic as round one — the agent doesn't know what it already asked or what you already answered. With it, each iteration builds on the last.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auditability.&lt;/strong&gt; When something goes wrong, the session history shows you exactly what the agent did, which tools it used, and why. Without continuity, that record fragments into orphaned sessions. With the handshake, you have a traceable reasoning chain end to end.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. This Isn't New — The Industry Already Does It
&lt;/h2&gt;

&lt;p&gt;What's interesting isn't that it works. It's that &lt;strong&gt;the industry already solved this problem with the same pattern&lt;/strong&gt; — just with more infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LangGraph&lt;/strong&gt; implements checkpoints with &lt;code&gt;thread_id&lt;/code&gt; + &lt;code&gt;checkpointer&lt;/code&gt;. The &lt;code&gt;thread_id&lt;/code&gt; is the direct equivalent of our &lt;code&gt;task_id&lt;/code&gt;. The difference is that LangGraph needs you to configure &lt;code&gt;SqliteSaver&lt;/code&gt; or &lt;code&gt;PostgresSaver&lt;/code&gt; — FlowTask uses OpenCode's SQLite, which was already there. &lt;a href="https://langchain-ai.github.io/langgraph/concepts/persistence/" rel="noopener noreferrer"&gt;[docs]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temporal&lt;/strong&gt; runs workflows with &lt;em&gt;durable execution&lt;/em&gt;: when a worker crashes at step 5 of 10, another worker picks up the workflow, replays the event history from the beginning, skips already-completed activities, and resumes from the last checkpoint. OpenCode solves the same conceptual problem — use history to avoid repeating completed work — at the LLM context level. The difference: Temporal guarantees this against infrastructure failures with deterministic replay; OpenCode does it at the conversational context layer of the LLM. &lt;a href="https://docs.temporal.io/encyclopedia/detecting-workflow-reset" rel="noopener noreferrer"&gt;[docs]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Agent Framework&lt;/strong&gt; defines &lt;em&gt;supersteps&lt;/em&gt; with checkpoint storage: each superstep captures the full state upon completion. Each agent in our pipeline is a superstep that persists its state when done. &lt;a href="https://learn.microsoft.com/en-us/agent-framework/workflows/checkpoints" rel="noopener noreferrer"&gt;[docs]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The difference: FlowTask solves it with 200 lines of protocol. Your tool already has the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Conclusion
&lt;/h2&gt;

&lt;p&gt;Session continuity isn't an infrastructure problem — it's a design omission. The tools already persist all the context. All that's missing is the handshake that reuses it.&lt;/p&gt;

&lt;p&gt;If your agents depend on multi-turn reasoning, don't accept the default of a fresh session every time.&lt;/p&gt;

&lt;p&gt;The full pattern is implemented in &lt;a href="https://github.com/Saez520/FlowTask" rel="noopener noreferrer"&gt;FlowTask&lt;/a&gt; for reference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LangGraph&lt;/strong&gt; implements checkpoints with &lt;code&gt;thread_id&lt;/code&gt; + &lt;code&gt;checkpointer&lt;/code&gt;. The &lt;code&gt;thread_id&lt;/code&gt; is the direct equivalent of our &lt;code&gt;task_id&lt;/code&gt;. The difference is that LangGraph needs you to configure &lt;code&gt;SqliteSaver&lt;/code&gt; or &lt;code&gt;PostgresSaver&lt;/code&gt; — FlowTask uses OpenCode's SQLite, which was already there. &lt;a href="https://langchain-ai.github.io/langgraph/concepts/persistence/" rel="noopener noreferrer"&gt;[docs]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Temporal&lt;/strong&gt; runs workflows with &lt;em&gt;durable execution&lt;/em&gt;: when a worker crashes at step 5 of 10, another worker picks up the workflow, replays the event history from the beginning, skips already-completed activities, and resumes from the last checkpoint. OpenCode solves the same conceptual problem — use history to avoid repeating completed work — at the LLM context level. The difference: Temporal guarantees this against infrastructure failures with deterministic replay; OpenCode does it at the conversational context layer of the LLM. &lt;a href="https://docs.temporal.io/encyclopedia/detecting-workflow-reset" rel="noopener noreferrer"&gt;[docs]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Microsoft Agent Framework&lt;/strong&gt; defines &lt;em&gt;supersteps&lt;/em&gt; with checkpoint storage: each superstep captures the full state upon completion. Each agent in our pipeline is a superstep that persists its state when done. &lt;a href="https://learn.microsoft.com/en-us/agent-framework/workflows/checkpoints" rel="noopener noreferrer"&gt;[docs]&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The difference: FlowTask solves it with 200 lines of protocol. Your tool already has the rest.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Conclusion
&lt;/h2&gt;

&lt;p&gt;Session continuity isn't an infrastructure problem — it's a design omission. The tools already persist all the context. All that's missing is the handshake that reuses it.&lt;/p&gt;

&lt;p&gt;If your agents depend on multi-turn reasoning, don't accept the default of a fresh session every time.&lt;/p&gt;

&lt;p&gt;The full pattern is implemented in &lt;a href="https://github.com/Saez520/FlowTask" rel="noopener noreferrer"&gt;FlowTask&lt;/a&gt; for reference.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>architecture</category>
      <category>llm</category>
    </item>
  </channel>
</rss>
