<?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: Mariusz Czajkowski</title>
    <description>The latest articles on DEV Community by Mariusz Czajkowski (@mczaykowski).</description>
    <link>https://dev.to/mczaykowski</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1706910%2F79e257b2-975e-478a-bfa8-e025266da454.png</url>
      <title>DEV Community: Mariusz Czajkowski</title>
      <link>https://dev.to/mczaykowski</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mczaykowski"/>
    <language>en</language>
    <item>
      <title>I built a tiny runtime for resumable agent workers</title>
      <dc:creator>Mariusz Czajkowski</dc:creator>
      <pubDate>Tue, 26 May 2026 12:48:58 +0000</pubDate>
      <link>https://dev.to/mczaykowski/i-built-a-tiny-runtime-for-resumable-agent-workers-1bgp</link>
      <guid>https://dev.to/mczaykowski/i-built-a-tiny-runtime-for-resumable-agent-workers-1bgp</guid>
      <description>&lt;p&gt;A while ago I needed a resumable agent runtime.&lt;/p&gt;

&lt;p&gt;I did not want something as large as Temporal, and I did not want another agent framework like LangChain. I wanted something small enough to understand, but solid enough to adapt across the different verticals I was building.&lt;/p&gt;

&lt;p&gt;It started with a few bare-bones questions.&lt;/p&gt;

&lt;p&gt;The moment an agent leaves a notebook, script, or chat session, the hard problems change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What work exists?&lt;/li&gt;
&lt;li&gt;Which worker owns it right now?&lt;/li&gt;
&lt;li&gt;What was the last durable step?&lt;/li&gt;
&lt;li&gt;Can another worker resume after a crash?&lt;/li&gt;
&lt;li&gt;Which resources are locked?&lt;/li&gt;
&lt;li&gt;What did the agent produce?&lt;/li&gt;
&lt;li&gt;Can operators inspect what happened?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The effect of it is &lt;strong&gt;Roost&lt;/strong&gt; as a small runtime layer for that problem.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/mczaykowski/Roost" rel="noopener noreferrer"&gt;https://github.com/mczaykowski/Roost&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The basic idea
&lt;/h2&gt;

&lt;p&gt;Roost treats an agent as a durable step machine.&lt;/p&gt;

&lt;p&gt;An engine implements two methods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;engine_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;

    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init_snapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WorkItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WorkItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Snapshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The engine owns the domain-specific transition.&lt;/p&gt;

&lt;p&gt;Roost owns the operational substrate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Queue
  -&amp;gt; acquire lease
  -&amp;gt; load latest Snapshot
  -&amp;gt; Engine.step(snapshot, item)
  -&amp;gt; compare-and-swap save Snapshot
  -&amp;gt; re-enqueue or mark done
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;durable snapshots&lt;/li&gt;
&lt;li&gt;per-work leases&lt;/li&gt;
&lt;li&gt;at-least-once execution&lt;/li&gt;
&lt;li&gt;retry-safe progress&lt;/li&gt;
&lt;li&gt;delayed continuation&lt;/li&gt;
&lt;li&gt;resource claims&lt;/li&gt;
&lt;li&gt;event history&lt;/li&gt;
&lt;li&gt;content-addressed artifacts&lt;/li&gt;
&lt;li&gt;failed-work inspection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is intentionally small. It is not trying to be a prompt framework, model router, workflow DSL, or hosted agent platform.&lt;/p&gt;

&lt;p&gt;Roost does not help an agent think.&lt;/p&gt;

&lt;p&gt;Roost helps an agent keep going.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;A lot of agent tooling focuses on the thinking loop: prompts, tools, retrieval, planning, memory, model routing.&lt;/p&gt;

&lt;p&gt;That is useful, but once agents run as workers for minutes, hours, or days, the bottleneck becomes more boring and more operational.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a worker dies halfway through a task&lt;/li&gt;
&lt;li&gt;the same job is delivered twice&lt;/li&gt;
&lt;li&gt;a long-running task needs to wait before its next step&lt;/li&gt;
&lt;li&gt;two workers should not touch the same resource at the same time&lt;/li&gt;
&lt;li&gt;an operator needs to know what happened&lt;/li&gt;
&lt;li&gt;the output needs to be inspectable later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can solve this with a workflow engine, a custom queue, a database table, or a pile of scripts.&lt;/p&gt;

&lt;p&gt;Roost is my attempt at a small, agent-shaped version of that layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple demo: crash-safe URL watchlist
&lt;/h2&gt;

&lt;p&gt;The demo engine is a URL watchlist worker.&lt;/p&gt;

&lt;p&gt;It fetches a URL over multiple steps, saves each observation into a snapshot, waits between checks, and writes a final JSON artifact.&lt;/p&gt;

&lt;p&gt;You can kill the worker halfway through, restart it, and Roost resumes from the latest saved snapshot.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--extra&lt;/span&gt; redis &lt;span class="nt"&gt;--extra&lt;/span&gt; dev
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 6379:6379 redis:7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In one terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run roost worker &lt;span class="nt"&gt;--engines&lt;/span&gt; watchlist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In another:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;WORK_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;uv run roost enqueue &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--engine&lt;/span&gt; watchlist &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--resource&lt;/span&gt; domain:example.com &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--payload&lt;/span&gt; &lt;span class="s1"&gt;'{"url":"https://example.com","claim":"Example Domain is reachable","checks_required":3,"delay_seconds":5}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

uv run roost status &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then kill the worker with &lt;code&gt;Ctrl-C&lt;/code&gt;, start it again, and inspect the same work item.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run roost worker &lt;span class="nt"&gt;--engines&lt;/span&gt; watchlist
uv run roost status &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WORK_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There is also a local end-to-end script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;scripts/e2e_watchlist.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No LLM key is required. The demo is about runtime behavior, not model behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  Local console
&lt;/h2&gt;

&lt;p&gt;Roost includes a small local console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run roost ui
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It shows live work, saved state, events, failed work, and artifacts.&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.amazonaws.com%2Fuploads%2Farticles%2F2dym697bqkk1rari90nh.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.amazonaws.com%2Fuploads%2Farticles%2F2dym697bqkk1rari90nh.png" alt="Roost Console Work View" width="800" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The detail view lets you inspect payloads, snapshots, and outputs:&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.amazonaws.com%2Fuploads%2Farticles%2Fryac67tbv7okmdqhjs13.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.amazonaws.com%2Fuploads%2Farticles%2Fryac67tbv7okmdqhjs13.png" alt="Roost Console Detail" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this fits
&lt;/h2&gt;

&lt;p&gt;Roost is not a replacement for LangChain, LlamaIndex, CrewAI, AutoGen, Temporal, Celery, or your own agent loop.&lt;/p&gt;

&lt;p&gt;It sits at a different layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;LangChain helps decide what an agent should do.
Temporal helps coordinate workflows.
Celery runs jobs.
Roost keeps long-running agent workers alive, inspectable, and resumable.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The current backend is Redis + SAQ. Execution is at-least-once, so engines need to make &lt;code&gt;step()&lt;/code&gt; retry-safe from the same snapshot.&lt;/p&gt;

&lt;p&gt;That tradeoff is intentional. I would rather expose the semantics clearly than pretend exactly-once execution exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I’m looking for feedback on
&lt;/h2&gt;

&lt;p&gt;I’m especially interested in feedback on the abstraction boundary.&lt;/p&gt;

&lt;p&gt;Is this useful as a small runtime under agent loops?&lt;/p&gt;

&lt;p&gt;Would you rather reach for Temporal, Celery, or a custom queue?&lt;/p&gt;

&lt;p&gt;Does the &lt;code&gt;init_snapshot()&lt;/code&gt; / &lt;code&gt;step()&lt;/code&gt; model feel too small, or exactly small enough?&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/mczaykowski/Roost" rel="noopener noreferrer"&gt;https://github.com/mczaykowski/Roost&lt;/a&gt;&lt;/p&gt;

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