{"slug": "why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it", "title": "Why Your Multi-Turn AI Agents Lose Their Train of Thought (And How to Fix It)", "summary": "An engineer discovered that multi-turn AI agents in tools like Claude Code and OpenCode lose context between turns because each subagent invocation creates a new session with no history. The infrastructure to preserve context already exists—sessions persist in SQLite with full message history—but is not used by default. The fix involves explicitly passing session IDs to resume existing sessions instead of creating new ones.", "body_md": "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.\n\nThis 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 brand new session. No history. No context. No memory of what that subagent already thought, asked, or decided.\n\nThe good news: the infrastructure to fix this already exists in these tools. It's just that nobody uses it by default.\n\n// OpenCode — tool/task.ts, lines 144-155\n\nconst session = taskID\n\n? yield* sessions.get(SessionID.make(taskID))\n\n.pipe(Effect.catchCause(() => Effect.succeed(undefined)))\n\n: undefined\n\nconst nextSession =\n\nsession ?? // If it exists, reuse it\n\n(yield* sessions.create({ // Otherwise, create a new one\n\nparentID: ctx.sessionID,\n\ntitle: \"params.description + `(@${next.name} subagent)`\n\n,\"\n\npermission: [...],\n\n}))\n\nHere's the flow: the LLM decides to call task(), the system looks up the subagent definition (permissions, model, system prompt), creates a new session with parentID 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.\n\nClaude Code exposes resume: sessionId 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 task_id parameter that lets you resume an existing session, but if you don't explicitly pass it, the system creates a new session. And since the main agent — the one calling task() — has no way of knowing which task_id it used last time, the default wins every time.\n\nThe 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.\n\nFinding 1: Sessions store EVERYTHING.\n\nOpenCode persists sessions in SQLite. Every message, every tool call, every output, every step of reasoning gets recorded:\n\nSessionTable MessageTable PartTable\n\n──────────── ──────────── ─────────\n\nid (PK) id (PK) id (PK)\n\nparent_id (FK→self) session_id (FK) message_id (FK)\n\ntitle data (JSON) session_id (FK)\n\nagent data (JSON)\n\ntime_created ↑ text | tool | reasoning\n\ntime_updated ↑ snapshots | patches\n\nWhen 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.\n\nFinding 2: Subagents can't delete their sessions.\n\nSubagents' default permissions don't include task or todowrite. There is no end_session, close, terminate, or anything similar. A subagent cannot — by accident or by design — destroy its own session.\n\nFinding 3: The LLM loop exits — it doesn't destroy.\n\nWhen the subagent finishes responding, the main loop checks an exit condition:\n\n// OpenCode — prompt.ts, lines 1267-1274\n\nif (\n\nlastAssistant?.finish &&\n\n![\"tool-calls\"].includes(lastAssistant.finish) &&\n\n!hasToolCalls &&\n\nlastUser.id < lastAssistant.id\n\n) {\n\nbreak // ← Exits the loop. Nothing else.\n\n}\n\nThat break doesn't call sessions.remove(). 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.\n\nFinding 4: No TTL, no timeout, no automatic cleanup.\n\nI searched for ttl, expir, timeout.*session, auto.*delete across the entire codebase. Zero results for sessions. Sessions live until someone deletes them manually. They don't expire.\n\nThe irony: 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 task_id.\n\n\"Give me the session for spec-42's clarifier\"\n\n\"Has spec-42's constructor finished?\"\n\n\"Resume the conversation with the planner where we left off\"\n\nTo OpenCode, a session is ses_1d6f79327ffe7JM4ZcELwlMV0D. 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.\n\nThe handshake is the layer that translates domain knowledge into session references. Three functions:\n\nDiscovery: given a spec ID, find the right session's task_id\n\nNaming: instead of ses_1d6f79327ffe, you see Kael-planner, Aitana-validator\n\nOrchestration state: is the planner running? Did the validator approve?\n\nThe analogy is DNS. A web server can serve content if you give it the right IP. DNS translates github.com to 140.82.121.3. The handshake translates spec-42 → constructor to ses_1d6e78035ffe. It doesn't replace persistence. It complements it.\n\nIn practice, two scenarios:\n\nScenario A — New: No task_id exists for this agent. Call task() normally. Capture the task_id from the response. Persist it in a map: \"spec-42/constructor\" → \"ses_1d6e78035ffe\".\n\nScenario B — Resume: A task_id already exists. Retrieve it from the map. Call task() with that task_id. OpenCode loads the full session. The agent doesn't \"remember\" by magic — it sees its entire history.\n\nThe 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.\n\nFewer tokens, lower latency. When an agent resumes its session, it doesn't need to re-run grep 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.\n\nReal iterative refinement. 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.\n\nAuditability. 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.\n\nLangGraph implements checkpoints with thread_id + checkpointer. The thread_id is the direct equivalent of our task_id. The difference is that LangGraph needs you to configure SqliteSaver or PostgresSaver — FlowTask uses OpenCode's SQLite, which was already there. [docs]\n\nTemporal runs workflows with durable execution: 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. [docs]\n\nMicrosoft Agent Framework defines supersteps 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. [docs]\n\nThe difference: FlowTask solves it with 200 lines of protocol. Your tool already has the rest.\n\nIf your agents depend on multi-turn reasoning, don't accept the default of a fresh session every time.\n\nThe full pattern is implemented in FlowTask for reference.\n\nLangGraph implements checkpoints with thread_id + checkpointer. The thread_id is the direct equivalent of our task_id. The difference is that LangGraph needs you to configure SqliteSaver or PostgresSaver — FlowTask uses OpenCode's SQLite, which was already there. [docs]\n\nTemporal runs workflows with durable execution: 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. [docs]\n\nMicrosoft Agent Framework defines supersteps 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. [docs]\n\nThe difference: FlowTask solves it with 200 lines of protocol. Your tool already has the rest.\n\nIf your agents depend on multi-turn reasoning, don't accept the default of a fresh session every time.\n\nThe full pattern is implemented in FlowTask for reference.", "url": "https://wpnews.pro/news/why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it", "canonical_source": "https://dev.to/langridgep21025/why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it-4fng", "published_at": "2026-06-15 04:05:51+00:00", "updated_at": "2026-06-15 04:10:35.958925+00:00", "lang": "en", "topics": ["ai-agents", "large-language-models", "developer-tools"], "entities": ["Claude Code", "OpenCode", "SQLite"], "alternates": {"html": "https://wpnews.pro/news/why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it", "markdown": "https://wpnews.pro/news/why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it.md", "text": "https://wpnews.pro/news/why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it.txt", "jsonld": "https://wpnews.pro/news/why-your-multi-turn-ai-agents-lose-their-train-of-thought-and-how-to-fix-it.jsonld"}}