<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged topic-reliability</title>
<link>https://coderlegion.com/tag/topic-reliability</link>
<description>Powered by Question2Answer</description>
<item>
<title>Crash Recovery and Durable Execution in Always-On AI Agents</title>
<link>https://coderlegion.com/31726/crash-recovery-and-durable-execution-in-always-on-ai-agents</link>
<description>&lt;p&gt;Moving an AI agent from an interactive CLI session to an autonomous, 24/7 background daemon introduces a severe distributed systems failure mode: interrupted multi-step execution. &lt;strong&gt;Quick solution:&lt;/strong&gt; Implement a write-ahead log in SQLite alongside deterministic SHA-256 idempotency keys derived from task IDs and step indices before dispatching state-mutating tool calls.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-python&quot;&gt;import hashlib
import json

def generate_idempotency_key(task_id: str, step_index: int, arguments: dict) -&amp;gt; str:
    payload = f&quot;{task_id}:{step_index}:{json.dumps(arguments, sort_keys=True)}&quot;
    return hashlib.sha256(payload.encode(&quot;utf-8&quot;)).hexdigest()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Prerequisites for this architecture include a reliable local storage engine such as SQLite configured with write-ahead log mode, and external APIs that accept idempotency tokens in their request headers. If the runner crashes at step seven due to an out-of-memory error, a naive supervisor restarts the agent from step one. This causes runaway token waste by re-querying the language model for all prior steps, and triggers side-effect disasters such as duplicate git branches, duplicate pull requests, or multiple webhook notifications because the agent re-executes external mutations from scratch.&lt;/p&gt;
&lt;h3 id=&quot;the-non-determinism-and-token-waste-dilemma&quot;&gt;The Non-Determinism and Token Waste Dilemma&lt;/h3&gt;
&lt;p&gt;Because large language models are inherently non-deterministic, restarting a failed multi-step task from scratch may produce an entirely different reasoning path on the second run. This divergence often leaves orphaned resources scattered across external systems. Furthermore, re-running completed steps burns thousands of unnecessary input and reasoning tokens. In production environments, this operational cost scales linearly with the frequency of background daemon restarts.&lt;/p&gt;
&lt;p&gt;To prevent these failures, agent engineers must borrow architectural patterns from database theory, specifically write-ahead logging and idempotent state machines. You can examine how similar reliability guarantees are implemented in distributed systems by reviewing how &lt;a rel=&quot;nofollow&quot; href=&quot;https://temporal.io/how-it-works&quot;&gt;Temporal works&lt;/a&gt;. By logging every intent before execution and caching intermediate outputs, the agent runtime maintains strict state consistency without needing a heavy external orchestration cluster.&lt;/p&gt;
&lt;h3 id=&quot;the-three-stage-durability-protocol&quot;&gt;The Three-Stage Durability Protocol&lt;/h3&gt;
&lt;p&gt;To guarantee crash resilience and zero duplicate mutations, the agent daemon implements a strict three-stage durability protocol comprising intent logging, deterministic dispatch, and atomic state committing.&lt;/p&gt;
&lt;h4 id=&quot;1-the-write-ahead-journal-pattern&quot;&gt;1. The Write-Ahead Journal Pattern&lt;/h4&gt;
&lt;p&gt;Before executing any tool that produces side effects, the daemon writes a pending execution journal entry to persistent disk storage or a local SQLite database with the status marked as pending, along with the step index, tool name, and arguments hash. Only after the tool returns successfully is the journal entry updated to committed alongside its serialized output.&lt;/p&gt;
&lt;h4 id=&quot;2-tool-classification-read-only-versus-state-mutating&quot;&gt;2. Tool Classification: Read-Only Versus State-Mutating&lt;/h4&gt;
&lt;p&gt;The runtime classifies all available agent tools into two distinct operational tiers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Idempotent and Read-Only:&lt;/strong&gt; Tools like reading files, performing grep searches, listing directories, or fetching web pages are safe to replay. If a crash occurs, these can be re-executed safely or satisfied immediately from the local step cache without interacting with external APIs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transactional and State-Mutating:&lt;/strong&gt; Tools like committing code, creating pull requests, sending webhooks, or updating databases must be protected. Every mutation tool call requires a deterministic idempotency key generated from the task identifier and step sequence. External APIs receive this key, guaranteeing that duplicate execution attempts resolve cleanly as safe no-ops.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-replaying-context-without-token-re-spend&quot;&gt;3. Replaying Context Without Token Re-Spend&lt;/h4&gt;
&lt;p&gt;When a crashed agent daemon boots up after an unexpected termination, it inspects the local task journal rather than feeding the raw conversation history back to the model. It re-hydrates the working memory context using previously committed outputs. The language model is only called for the unresolved step, preserving call budgets and avoiding reasoning divergence.&lt;/p&gt;
&lt;h3 id=&quot;protecting-external-apis-with-idempotency-keys&quot;&gt;Protecting External APIs with Idempotency Keys&lt;/h3&gt;
&lt;p&gt;When a mutation tool executes, it must pass a stable idempotency key that survives process restarts. If the daemon crashes immediately after the external API receives the request but before the local journal can record the success status, the supervisor will trigger a retry upon reboot. When the retry dispatches the same request with the identical idempotency key, the external provider recognizes the duplicate identifier and returns the original response instead of executing the mutation a second time.&lt;/p&gt;
&lt;p&gt;For engineers automating background tasks reliably across infrastructure boundaries, reviewing implementation patterns like &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/count-publication-attempts-without-double-counting-retries/&quot;&gt;Count Publication Attempts Without Double Counting Retries&lt;/a&gt; offers valuable context on managing stateless background loops and handling idempotency safely. Always ensure that your cloud functions or worker scripts handle retries idempotently, as network partitions and timeouts can cause duplicate delivery attempts from upstream webhooks or message queues.&lt;/p&gt;
&lt;h3 id=&quot;production-decision-matrix&quot;&gt;Production Decision Matrix&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Dimension &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Naive Loop Retries &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;In-Memory Snapshotter &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Journaled Durable Execution &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Recommendation&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Crash Recovery&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Starts over from Step 1 &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Recovers if snapshot intact; lost on OOM kill &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Recovers exactly at last uncommitted step &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Use journaled durable execution for production daemons&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Token Cost on Failure&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Full re-burn of all prior steps &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Partial re-burn of active window &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Zero re-burn for completed steps &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Minimizes operational inference costs&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Mutation Safety&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;High risk of duplicate PRs and commits &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Moderate risk &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Zero duplication via deterministic keys &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Essential for external API integrations&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Infrastructure Overhead&lt;/strong&gt;&lt;/td&gt;&lt;td align=&quot;left&quot;&gt;None &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Low &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Low (single SQLite WAL table) &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Balances robustness with operational simplicity&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Divergence Risk&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;High due to fresh LLM roll &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Moderate &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Zero as prior step outputs are pinned &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Maintains deterministic execution flow&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;verification-and-execution-checklist&quot;&gt;Verification and Execution Checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Write-Ahead Log table created in SQLite with columns for task_id, step_index, tool_name, args_hash, status, output, and created_at.&lt;/li&gt;
&lt;li&gt;[ ] Simulated SIGKILL crash test executed by killing the agent process with kill -9 midway through step four of an eight-step workflow.&lt;/li&gt;
&lt;li&gt;[ ] Verified that upon daemon reboot, steps one through three load directly from the local disk cache with zero LLM API calls.&lt;/li&gt;
&lt;li&gt;[ ] Verified that step four presents the exact same idempotency key and completes successfully without duplicating external resources.&lt;/li&gt;
&lt;li&gt;[ ] Measured token consumption by comparing resume token count versus full restart token count to verify greater than seventy percent token savings on crash.&lt;/li&gt;
&lt;/ul&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31726/crash-recovery-and-durable-execution-in-always-on-ai-agents</guid>
<pubDate>Mon, 19 Oct 2026 13:23:42 +0000</pubDate>
</item>
<item>
<title>Refactor Cloudflare Workflow Without Breaking Durable Steps</title>
<link>https://coderlegion.com/31723/refactor-cloudflare-workflow-without-breaking-durable-steps</link>
<description>&lt;p&gt;A large workflow method can easily exceed maintainability limits, but standard refactoring techniques risk breaking the persisted runtime execution state in &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/normalize-cloudflare-workflows-trigger-payloads/&quot;&gt;Cloudflare Workflows&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick solution:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;// Keep exact step names and retry options when extracting helper functions
export async function run({ step, payload }) {
  return await processArticlePhase(step, payload);
}

async function processArticlePhase(step, payload) {
  return await step.do(
    &quot;fetch-external-source&quot;,
    { retries: { limit: 3, delay: &quot;5 seconds&quot; } },
    async () =&amp;gt; {
      return await fetchSourceData(payload.url);
    }
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;technical-context-and-real-problem&quot;&gt;Technical Context and Real Problem&lt;/h3&gt;
&lt;p&gt;When a serverless orchestration script grows beyond a few hundred lines, developers naturally reach for method extraction. In standard TypeScript applications, pulling code into a helper function is a low-risk structural change. Cloudflare Workflows, however, persist execution history, step identifiers, and cached outputs. If you change a step name, alter its retry configuration, or accidentally reorder execution branches during extraction, the workflow runtime can fail to resume correctly on replay.&lt;/p&gt;
&lt;p&gt;Developers frequently discover that refactoring breaks asynchronous pause-and-resume boundaries or causes duplicate side effects. Maintaining clear separation between pure utility logic and stateful orchestration calls is essential. You can consult the official documentation on &lt;a rel=&quot;nofollow&quot; href=&quot;https://developers.cloudflare.com/workflows/build/rules-of-workflows/&quot;&gt;Cloudflare Workflows rules&lt;/a&gt; to understand how the underlying execution engine evaluates steps.&lt;/p&gt;
&lt;h3 id=&quot;durable-engineering-question&quot;&gt;Durable Engineering Question&lt;/h3&gt;
&lt;p&gt;How can a complex workflow phase be decomposed into helper methods while keeping its durable execution contract and replay behavior stable?&lt;/p&gt;
&lt;h3 id=&quot;engineering-decision-and-strategy&quot;&gt;Engineering Decision and Strategy&lt;/h3&gt;
&lt;p&gt;When splitting a large workflow method, treat durable step identifiers and retry settings as part of your public runtime API. When you need to keep your codebase clean while managing technical debt, you can also review strategies on how to &lt;a rel=&quot;nofollow&quot; href=&quot;https://raylabs.app/articles/separate-seo-title-readability-from-ranking-rules/&quot;&gt;Separate Seo Title Readability From Ranking&lt;/a&gt; for other domain models. Move pure preparation, validation, and data transformation work into standard helper functions first. Isolate external API calls, database writes, and state mutations behind narrow, explicitly named steps.&lt;/p&gt;
&lt;h4 id=&quot;step-extraction-comparison&quot;&gt;Step Extraction Comparison&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th align=&quot;left&quot;&gt;Extraction Approach &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;When to Use It &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Trade-Off or Failure Mode &lt;/th&gt;&lt;th align=&quot;left&quot;&gt;Recommendation&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Inline Helper Extraction&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Small utilities and data mapping functions &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Can mix pure code with implicit side effects if not isolated &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Use for pure functions with no &lt;code&gt;step.do&lt;/code&gt; calls.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Step-Bound Service Method&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Complex logic containing multiple durable steps &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Requires passing the &lt;code&gt;step&lt;/code&gt; context object down through parameters &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Use when a logical phase must be modularized without altering step names.&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td align=&quot;left&quot;&gt;&lt;strong&gt;Complete Phase Encapsulation&lt;/strong&gt; &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Splitting an entire orchestrator into distinct file modules &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Harder to trace total execution flow across multiple files &lt;/td&gt;&lt;td align=&quot;left&quot;&gt;Use only when file size limits demand it, keeping step names identical.&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id=&quot;implementation-example&quot;&gt;Implementation Example&lt;/h3&gt;
&lt;p&gt;Consider a monolithic workflow phase that handles raw data ingestion, transformation, and storage. Extracting the transformation logic into a helper function reduces cognitive complexity without altering the underlying durable step execution order.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-typescript&quot;&gt;import { WorkflowEntrypoint, WorkflowStep, WorkflowEvent } from &quot;cloudflare:workers&quot;;

interface Env {}

export class ArticleWorkflow extends WorkflowEntrypoint&amp;lt;Env, any&amp;gt; {
  async run(event: WorkflowEvent&amp;lt;any&amp;gt;, step: WorkflowStep) {
    const rawData = await step.do(&quot;fetch-source-data&quot;, async () =&amp;gt; {
      const res = await fetch(event.payload.url);
      return await res.json();
    });

    // Extracted helper handles transformation without breaking step durability
    const cleanedData = await step.do(&quot;transform-payload&quot;, async () =&amp;gt; {
      return transformArticleContent(rawData);
    });

    await step.do(&quot;persist-records&quot;, async () =&amp;gt; {
      return await saveToDatabase(cleanedData);
    });
  }
}

function transformArticleContent(raw: any) {
  return {
    title: raw.title?.trim() ?? &quot;Untitled&quot;,
    body: raw.content ?? &quot;&quot;,
    processedAt: new Date().toISOString()
  };
}

async function saveToDatabase(data: any) {
  // Simulated database write
  return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;trade-offs-and-failure-modes&quot;&gt;Trade-Offs and Failure Modes&lt;/h3&gt;
&lt;p&gt;Decomposing workflow phases introduces specific failure modes that do not exist in traditional stateless functions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Altered Step Identifiers:&lt;/strong&gt; Changing the string name inside &lt;code&gt;step.do(&quot;new-name&quot;, ...)&lt;/code&gt; breaks existing suspended instances waiting to resume under the old identifier.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implicit Execution Ordering:&lt;/strong&gt; Moving conditional branches inside helper functions can silently alter the deterministic sequence required by the workflow runtime.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lost Context Binding:&lt;/strong&gt; Passing the &lt;code&gt;step&lt;/code&gt; object into deeply nested helpers can obscure where side effects occur, making dry runs harder to debug.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;verification-and-execution-checklist-1&quot;&gt;Verification and Execution Checklist&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;[ ] Compare step identifiers, retry settings, serializable outputs, and write boundaries before and after extraction.&lt;/li&gt;
&lt;li&gt;[ ] Separate an actual cached replay fixture from a syntax or typecheck result to ensure runtime stability.&lt;/li&gt;
&lt;li&gt;[ ] Run type checks and workflow behavior tests to verify that no replay execution paths were inadvertently modified.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Refactoring Cloudflare Workflows requires treating step names, retry intervals, and execution boundaries as immutable runtime contracts. By isolating pure transformation logic into helper functions and keeping orchestrator steps explicit, you can maintain clean codebases without breaking active workflow instances.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/31723/refactor-cloudflare-workflow-without-breaking-durable-steps</guid>
<pubDate>Mon, 19 Oct 2026 13:14:13 +0000</pubDate>
</item>
</channel>
</rss>