Build a durable Agent
This tutorial builds an Agent that remembers what happened earlier in its
Session. It keeps a live object from init in memory and a log file on the
Computer’s disk, then continues the conversation across several Turns, an idle
period and an interruption. It needs no model provider.
You need a project created with helmr init and the CLI configured (see
Quickstart). The examples use the demo Project’s staging
Environment. For the guarantees behind each step, see
Durability.
Define the Agent
Create agents/assistant.ts:
import { agent, template, image } from "@helmr/sdk"
import { appendFile, readFile } from "node:fs/promises"
const LOG = "/workspace/log.txt"
export const assistantComputer = template({
id: "assistant",
image: image("assistant").from("node:24-bookworm-slim"),
resources: { cpu: 1, memory: "1GiB" },
})
export const assistant = agent({
id: "assistant",
template: assistantComputer,
closeAfterIdle: "1d",
hibernateAfterIdle: "5m",
maxTurnDuration: "10m",
async init(ctx) {
// Runs once per Session process, before its first Turn.
return { turns: 0, startedAs: ctx.recovery.kind }
},
async turn(turn, ctx) {
const text = turn.input.map(part => part.text).join("").trim()
if (!text) throw new Error("Send some text")
ctx.initResult.turns += 1
await appendFile(LOG, `${turn.sequence}: ${text}\n`, { flush: true })
const lines = (await readFile(LOG, "utf8")).trimEnd().split("\n").length
await turn.output.write(`Recorded "${text}"`)
await turn.respond(`Turn ${ctx.initResult.turns} in this process. Log entries: ${lines}.`)
return { turnsInProcess: ctx.initResult.turns, logEntries: lines, startedAs: ctx.initResult.startedAs }
},
})
The Agent keeps two kinds of state:
ctx.initResultis the valueinitreturned. It is an ordinary live object; it could just as well hold an open client or a child process. It lasts as long as the Session’s process./workspace/log.txtis a file on the Computer’s disk. Helmr saves the disk periodically while the Computer runs and after a Turn’s handler returns successfully. The Turn becomescompletedwhen its result and response are committed; its disk save finishes in the background.
closeAfterIdle closes the Session after a day without work,
hibernateAfterIdle hibernates its Computer after five idle minutes, and
maxTurnDuration limits each Turn. See Agents.
Deploy and start
helmr deploy . --project demo --env staging
helmr session create assistant --project demo --env staging \
--text "first" --key user:ada \
--idempotency-key tutorial:assistant:1
Save the session_id from the receipt to enqueue more input. The Session key
user:ada lets you find this conversation later; creating another Session with
that key conflicts.
Wait for the Turn:
helmr session turn wait TURN_ID --session SESSION_ID --timeout 10m \
--project demo --env staging
The response reads Turn 1 in this process. Log entries: 1.
Continue the Session
helmr session enqueue SESSION_ID --project demo --env staging \
--text "second" --idempotency-key tutorial:assistant:2
Wait for the new turn_id. The response now reads
Turn 2 in this process. Log entries: 2. The counter in initResult kept its
value because init did not run again.
Leave the Session alone for more than five minutes, then enqueue third. After
five idle minutes Helmr hibernates its Computer with memory, processes and
disk, and the next input resumes it where it stopped. The counter continues at 3 and
startedAs is still initial.
Follow progress at any time with:
helmr session events SESSION_ID --project demo --env staging
When the process is lost
If the Session’s process is lost while a Turn runs, that Turn ends
interrupted and queued input runs on a new process. Helmr does not replay the
interrupted input. After three consecutive process failures the Session is
held; find the hold and resume:
helmr session get SESSION_ID --project demo --env staging
helmr session resume SESSION_ID --hold HOLD_ID --project demo --env staging
On the next input Helmr starts a new process on the same Computer and runs
init again with ctx.recovery.kind set to reconstructed. In this Agent the
counter starts again at 1, while the log file still has every entry the disk
kept. That is the rule to design for: keep anything that must survive
reconstruction on disk or in your own store, and make external effects safe to
repeat. Losing the worker of a running Computer restores the latest eligible
saved disk, which can predate completed requests. Helmr explains any gap and
requires acknowledgement before continuing. See Durability.
Interrupt and resume
You can stop work yourself the same way:
helmr session interrupt SESSION_ID --project demo --env staging
Interrupt stops the running Turn, keeps queued Turns waiting and holds the
Session. The receipt includes hold_id; resume with it when you want queued
work to continue:
helmr session resume SESSION_ID --hold HOLD_ID --project demo --env staging
Resuming lets queued and new Turns run. It does not rerun the interrupted input; send it again if you still want it. See Sessions.
Finish
helmr session close SESSION_ID --project demo --env staging
Close finishes work already accepted and then ends the Session. It does not release holds; resume first if the Session is held.
Next steps
- Start a model client or coding-agent process in
initand reuse it in every Turn; see Agents. - Ask a person for input with
turn.ask; see Answer questions. - Set up the Computer’s tools and files once with
prepare; see Prepare a Computer.