Build a durable agent

Tasks are a good fit for bounded one-shot work. An Actor adds a stable Session with ordered input and output, while each period of execution is still a Run.

Define the Actor

Add a declaration beside your existing Sandbox:

import { actor } from "@helmr/sdk"

export const assistant = actor({
  id: "assistant",
  idleTimeout: "90s",
  async run(session, ctx) {
    await session.output.write({ type: "ready", runId: ctx.run.id })
    for (;;) {
      const turn = await session.receive()
      if (turn === null) return
      await turn.output.write({ type: "reply", received: turn.input })
      await turn.complete()
    }
  },
})

The Actor-level idleTimeout supplies the default hot-wait duration for Session input, Token, and timer waits inside the Actor. An individual wait can override it where its API accepts idleTimeout. It is separate from a response timeout and is not a billing cap. receive() can durably park the managed Run without closing the Session. New input resumes work without discarding the Session’s identity or ordered history. A later continuation may use a different Run ID.

Deploy and start

helmr deploy . --project demo --env development

WORKSPACE_ID="$(helmr workspace create hello \
  --project demo --env development \
  --key tutorial:assistant \
  --idempotency-key tutorial:assistant:workspace)"

helmr actor start assistant \
  --project demo --env development \
  --workspace "$WORKSPACE_ID" \
  --key user:ada \
  --idempotency-key tutorial:assistant:start \
  --json

The response contains both session_id and the boot run_id. Save the Session ID for all later interaction.

Continue the Session

helmr actor enqueue SESSION_ID \
  --project demo --env development \
  --data-json '{"type":"message","text":"summarize our work"}' \
  --idempotency-key tutorial:assistant:message:2

helmr actor events SESSION_ID \
  --project demo --env development \
  --after 0 --jsonl

Event reads are finite pages; output and lifecycle events share one sequence. A page ending does not complete a Turn. Pass the last durable sequence back through --after to read only newer records. Input idempotency keys should come from a stable upstream event ID so delivery retries do not duplicate application commands.

Inspect the current managed Run with helmr actor get SESSION_ID. When the conversation is finished, close the Session explicitly:

helmr actor close SESSION_ID \
  --project demo --env development \
  --idempotency-key tutorial:assistant:close

Closing stops future input admission; it does not turn Actor output into Run logs. Use the Session output channel for application messages and Run logs and events for execution diagnostics.