{"slug": "rio-durable", "title": "Rio Durable", "summary": "Developer soasme published a draft design document for Rio Durable, a proposed durability layer for the Rio coding agent that persists computation state across restarts while explicitly not restoring the working environment of files, processes, installed packages and external services. The design replaces Rio's earlier Jupyter-notebook execution model with a single SKILL.state object holding the goal, an ordered list of note/script/command cells and runtime info, and exposes one model tool, step, through which the model submits a JSON Patch to update that state. The document states the behavior is a proposed design, not an implemented and tested durability guarantee.", "body_md": "# Rio Durable\n\nI’ve been working on [Rio](https://github.com/soasme/rio), a coding agent designed to run without a human continuously steering it. A task may take minutes, hours, or days. The agent needs to keep making progress even when nobody is watching.\n\nThe first versions of Rio used Jupyter notebooks as both context and execution environment. The model could write and edit cells, execute them, and use their results to decide what to do next.\n\nThis worked surprisingly well. A notebook is already a structured, executable document. It has cells, outputs, and a format that can be saved to disk. There is no need to invent a new programming language or an elaborate workflow graph.\n\nBut a notebook is not a durable computation runtime.\n\nA running kernel holds variables, open files, subprocesses, and other state in memory. When the kernel dies, that state disappears. The notebook may contain the code and its previous outputs, but it cannot tell us whether an interrupted operation completed.\n\nRio Durable is an attempt to solve this problem with fewer abstractions.\n\nThe [design document](https://github.com/soasme/rio/blob/main/docs/design/durable.md) is currently a draft. The behavior described here is the proposed design, not an implemented and tested durability guarantee.\n\n## What should survive?\n\nConsider an agent fixing a Flask application. It reads the source, edits a route, and starts the tests. The machine restarts before the tests finish.\n\nWhat should happen next?\n\nRestarting the entire task wastes completed work. Replaying the interrupted script may repeat changes that already happened. Restoring the Python kernel is not necessarily possible, and restoring its memory would not restore the filesystem or external services.\n\nRio makes a distinction between **computation state** and the **working environment**.\n\nComputation state describes what the agent knows, what it intended to run, what results it received, and what remains uncertain. The working environment consists of files, processes, installed packages, and external services.\n\nRio persists the former. It does not promise to restore the latter.\n\nAfter a restart, the agent receives its previous computation state. It inspects the current environment and decides how to continue.\n\nThis is the central durability contract.\n\n## State is context\n\nRio uses one `SKILL.state` object as the model’s context. It contains the goal, an ordered collection of cells, and runtime information.\n\nA simplified state looks like this:\n\n```\n{\n  \"schema\": \"rio.state/1\",\n  \"id\": \"s1\",\n  \"revision\": 12,\n  \"goal\": \"Fix the Flask route and pass tests\",\n  \"cells\": [\n    {\n      \"id\": 1,\n      \"previous_id\": null,\n      \"kind\": \"note\",\n      \"text\": \"Inspect app/routes.py\"\n    },\n    {\n      \"id\": 2,\n      \"previous_id\": null,\n      \"kind\": \"cmd\",\n      \"argv\": [\"pytest\", \"-q\"]\n    }\n  ],\n  \"runtime\": {\n    \"next_cell_id\": 3,\n    \"cells\": {},\n    \"inbox\": [],\n    \"usage\": {}\n  }\n}\n```\n\nA cell is either a note, a Python script, or a command. Notes hold context. Scripts and commands request execution.\n\nThe model manages this context itself. It can add cells, replace them, summarize observations into notes, and remove cells it no longer needs.\n\nThere is no separate conversation transcript that must be compacted into another representation. The selected cells are the working context. The complete history stays in persistent storage.\n\nThe distinction matters for long-running agents. An agent working for a month should not need to carry a month’s worth of tool calls into every model request. It needs the current state and enough evidence to make its next decision.\n\n## One tool: step\n\nMost coding agents expose tools such as `read`, `write`, `edit`, and `bash`. Rio Durable exposes a single model tool called `step`.\n\nThe model submits a JSON Patch to update State.\n\nFor example, it can replace a note:\n\n```\n{\n  \"patch\": [\n    {\n      \"op\": \"test\",\n      \"path\": \"/revision\",\n      \"value\": 12\n    },\n    {\n      \"op\": \"test\",\n      \"path\": \"/cells/0/id\",\n      \"value\": 1\n    },\n    {\n      \"op\": \"replace\",\n      \"path\": \"/cells/0\",\n      \"value\": {\n        \"id\": 3,\n        \"previous_id\": 1,\n        \"kind\": \"note\",\n        \"text\": \"Check the route and its tests\"\n      }\n    }\n  ]\n}\n```\n\nThe Harness validates the patch and commits it atomically. It then evaluates the new State and schedules any new executable cells.\n\nThe model does not directly mutate the persistent database. It proposes a State transition. The Harness decides whether that transition is valid.\n\nEach model round has a durable identity. A repeated submission cannot apply the same patch twice.\n\nThis gives Rio a simple boundary between model decisions and computation.\n\n## Immutable cells\n\nEvery cell is immutable. Replacing a cell creates a new version with a new ID.\n\nSuppose State contains cells `[1, 2]`. Updating Cell 1 creates Cell 3, leaving the current array as `[3, 2]`. Cell 3 records Cell 1 as its predecessor.\n\nCell 1 remains in Session history, but it is no longer part of the current context.\n\nFor executable cells, each new version requests exactly one execution. Rebuilding State does not execute code. Repeatedly evaluating State does not execute it either.\n\nTo execute the same code again, the model creates another version.\n\nThis removes the need for an execution flag, a retry counter on each cell, or a system that compares source code to decide whether it has changed.\n\nIt also gives results a precise identity. An output belongs to the cell version that produced it, not to whatever code happens to occupy that position later.\n\n## Scripts instead of kernels\n\nRio’s notebook prototype used a shared Python kernel. That made it convenient to pass variables between cells, but it also made execution depend on hidden state.\n\nRio Durable removes the shared kernel.\n\nEvery Python cell is an independent script. It starts in a fresh process and declares its dependencies using [PEP 723](https://peps.python.org/pep-0723/).\n\n```\n# /// script\n# requires-python = \">=3.12\"\n# dependencies = [\"httpx==0.28.1\"]\n# ///\n\nimport httpx\n\nresponse = httpx.get(\"https://example.com\")\nprint(response.status_code)\n```\n\nThe Harness materializes the script and runs it with `uv run`. It records the output and exit status.\n\nA command cell runs an argv array directly, without an implicit shell. For example, `[\"pytest\", \"-q\"]` runs the tests.\n\nCells do not share Python variables or import definitions from other cells. They communicate through ordinary files, services, and recorded observations.\n\nThis is less convenient than a notebook kernel for interactive exploration. It is easier to reason about after a restart.\n\nEach execution has an explicit beginning, a durable identity, and a recorded outcome. Package environments and caches can be recreated when dependencies are available. There is no interpreter stack to restore.\n\n## Surviving crashes\n\nAn executable cell has three phases:\n\n| Phase | Meaning | After restart | \n|---|---|---|\n| `queued` | Execution accepted but not started | Can be dispatched | \n| `started` | Launch authorized; code may have run | Mark as uncertain | \n| `finished` | Final outcome committed | Preserve result | \n\nRio commits the `started` record before launching the process.\n\nIf Rio crashes after that commit, it cannot reliably know whether the process started, how far it ran, or whether it completed an external operation.\n\nIt therefore records `UnknownExecution`.\n\n```\n{\n  \"type\": \"UnknownExecution\",\n  \"cell_id\": 2,\n  \"reason\": \"worker_lost_before_result_commit\",\n  \"worker_stopped\": true,\n  \"evidence_refs\": [\"session:event:104\"]\n}\n```\n\nRio does not automatically run that cell again.\n\nInstead, it gives the uncertainty to the model. The model can create new cells to inspect the filesystem, query an API, examine logs, or run a test.\n\nIf a script edited a Flask route before the crash, the agent can read the file and check the tests. It does not need to repeat the original edit blindly.\n\nAn inspection may establish that the desired final state exists. It may not prove that every operation in the interrupted script completed.\n\nRio preserves that distinction. A resolution note records the evidence and the model’s decision, while the original uncertainty remains in history.\n\nThis does not provide exactly-once execution of external effects. It provides durable knowledge of what was accepted, what finished, and what remains unknown.\n\n## SQLite as the source of truth\n\nRio uses SQLite for persistent Sessions.\n\nThe database records State changes, immutable cells, execution authorization, outputs, model rounds, observations, timers, and run endings.\n\nThe current State is reconstructed from committed records. Rebuilding it is a pure operation: it does not execute scripts or contact the model.\n\nA single transaction accepts a valid patch, allocates new cell IDs, creates execution records, advances the State revision, and consumes observations delivered to that model round.\n\nExternal work happens after the commit.\n\nThis ordering is important. A crash before a patch commits leaves no accepted work. A crash after the commit leaves a durable record of work that still needs attention.\n\nRio uses SQLite WAL mode with `synchronous=FULL` for its intended local durability guarantees. It also needs exclusive Session ownership and reliable worker cleanup before recovery can admit new execution.\n\nSQLite protects committed computation records under the supported storage assumptions. It does not protect against lost disks, corrupted storage, or effects in an external service.\n\n## Events wake the agent\n\nAn agent does not need a running worker while it is waiting.\n\nRio uses a persistent Inbox for observations. A finished execution, an expired timer, or an external event can add an observation and wake the model.\n\nThe model sees a fixed snapshot of State for each round. Events arriving while it generates a response go into Inbox and become visible in a subsequent round.\n\nThis avoids rewriting the model’s context in the middle of generation.\n\nA code cell can register a timer. The timer survives a restart, but it does not suspend and resume the original Python stack. When it expires, Rio creates an observation that can trigger another model decision.\n\nIf there is no executable work, no timer, and no external wake-up source, Rio does not keep calling the model in the hope that something changes.\n\nFor an agent intended to run for months, an idle Session should consume almost no resources.\n\n## Cancellation and completion are State changes\n\nRio also avoids separate model tools for cancellation and completion.\n\nA note can request cancellation by naming a target cell. Another note can request a successful or failed conclusion.\n\nThese notes are committed through the same `step` interface. The Harness recognizes their roles and processes the requests.\n\nRemoving a cell from context does not cancel its execution. Context management and execution control are separate operations.\n\nA successful conclusion requires no outstanding work, no unresolved execution uncertainty, and passing results from any required validators. The model decides whether the natural-language goal has been achieved. The Harness checks the mechanical conditions.\n\nRio has no human approval state in this core protocol. Permissions and execution limits must be enforced outside the model’s decisions. When the agent cannot proceed within those limits, it records failure rather than waiting indefinitely for approval.\n\n## Why not tasks?\n\n[Pi Durable](https://earendil.com/posts/pi-durable/) takes a broader approach. It provides durable tasks, checkpoints, tool execution policies, ownership, extension hooks, and application documents. Its task model can represent work that waits for other work, resumes from checkpoints, or runs compensating actions.\n\nRio Durable deliberately does less.\n\nAn executable cell is the unit of work. A Session is the durable history. State is the model’s current context. Inbox carries observations.\n\nThere is no general Task class, workflow graph, dependency scheduler, persistent scratchpad, or operation-level effect ledger.\n\nThis choice has a cost. Rio cannot resume a Python function from its last internal operation. It cannot automatically replay the safe parts of a script or compensate for completed effects. An interrupted cell may require the model to investigate and repeat work.\n\nFor Rio’s intended workload, that is an acceptable tradeoff to test.\n\nA coding agent can inspect the working environment. It can check whether a file exists, whether a test passes, or whether a commit has been created. These observations can inform recovery without requiring every filesystem or network operation to implement a separate durability protocol.\n\nThat does not make arbitrary side effects safe to repeat. Deployments, payments, and other externally consequential operations still need permissions and application-level safeguards.\n\nThe question is whether cell-level durability is sufficient for useful autonomous coding work, not whether it can reproduce every guarantee of a general-purpose durable workflow engine.\n\n## What remains to be tested\n\nThe most important part of this design is not the happy path. It is what happens at the boundaries between database commits, process execution, and external effects.\n\nRio needs fault-injection tests for crashes before dispatch, after launch authorization, during execution, and before result commits. Recovery must also handle surviving child processes, uncertain database commits, lost output buffers, and model requests that complete without an accepted patch.\n\nProcess termination tests are not enough to establish power-loss durability. Both need to be tested.\n\nI also want to measure whether the simpler model improves actual coding performance: completion rate, token usage, execution overhead, recovery quality, and the number of model rounds spent resolving uncertainty.\n\nThe design assumes that an LLM can manage its context and investigate interrupted work effectively. That assumption needs evidence.\n\n## Less runtime, more computation\n\nRio started with the idea that executable cells could serve as an agent’s context. The notebook prototype showed that the approach is practical, but it also exposed the problems of tying computation to a shared kernel.\n\nRio Durable keeps the cells and removes the kernel.\n\nThe runtime persists decisions, observations, and execution boundaries. It does not try to preserve every detail of a running program. The model manages the context and decides how to continue when the environment changes.\n\nThe goal is not to build another workflow engine. It is to find the smallest runtime that allows an autonomous coding agent to make progress across process failures, restarts, and long periods of inactivity.\n\nThe design is available in [Rio’s repository](https://github.com/soasme/rio/blob/main/docs/design/durable.md). The next step is implementation, fault injection, and evaluation.", "url": "https://wpnews.pro/news/rio-durable", "canonical_source": "https://julin.ai/2026/10/09/rio-durable/", "published_at": "2026-10-08 11:00:00+00:00", "updated_at": "2026-10-23 08:42:34.651359+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools"], "entities": ["Rio", "Rio Durable", "soasme", "Jupyter", "Flask", "SKILL.state"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/rio-durable", "markdown": "https://wpnews.pro/news/rio-durable.md", "text": "https://wpnews.pro/news/rio-durable.txt", "jsonld": "https://wpnews.pro/news/rio-durable.jsonld"}}