Golem 1.6 is out: see what's new

The durable runtime for AI agents

Golem runs AI agents, their tools and their artifacts. Each agent runs in a lightweight WebAssembly VM that resumes mid-function after node restarts, rescaling, long waits and crashes. Tool calls run exactly once, under permissions agents can't bypass.

Built on Golem

Ziverge
Golem Social
WarpMind
Seeta AI Assistant
JournAI
Johnethel LMS
The core of Golem

A lightweight, durable VM with permissions agents can't bypass

Golem runs every agent and every tool call in its own WebAssembly VM. Every call out of a VM goes through Golem, which enforces your permissions and records external interactions so the VM can resume anywhere.

You don't write save or checkpoint code, design state tables or run a workflow engine.

Lightweight

WebAssembly, not Linux: each VM has its own memory, file system and environment, with no guest OS. Code can be tens of kilobytes; a VM starts in under 1 ms in the best case scenario.

Durable

A waiting VM is suspended, using no CPU or memory, for minutes or months. After a crash or node restart it's restored where it left off, with its files, without repeating completed calls.

Secure

A VM reaches its own files, the network, secrets and other VMs only through Golem's permission checks, which no code, including AI-generated code, can bypass.

10,000+
Active agents per node
2 ms
Agent cold start
1 MB
Min memory per agent
0 CPU/RAM
While waiting
Agents

Agents are your own arbitrary code, not prompts

triage-agent.ts notes-agent.ts draft-agent.ts planner-agent.ts checkout-agent.ts writer-agent.ts stock-agent.ts
const Email = z.object({ id: z.string(), body: z.string() })
const Label = z.enum(['urgent', 'billing', 'other'])

export const Triage = defineAgent({
  name: 'TriageAgent',
  // each inbox gets its own agent, in its own WebAssembly VM:
  id: { inbox: z.string() },
  methods: {
    triage: method({
      input: { emails: z.array(Email) },
      returns: z.record(z.string(), Label),
    }),
  },
}).implement({
  init: ({ id }) => ({ inbox: id.inbox }),
  methods: {
    async triage({ emails }) {
      const labels: Record<string, z.infer<typeof Label>> = {}
      for (const email of emails) {
        const label = Label.parse(await classify(email.body))
        // after a crash or restart, it resumes inside this loop:
        await applyLabel(this.inbox, email.id, label)
        labels[email.id] = label
      }
      return labels
    },
  },
})
import { DatabaseSync } from 'node:sqlite'
const Texts = z.array(z.string())

export const Notes = defineAgent({
  name: 'NotesAgent',
  id: { user: z.string() },
  methods: {
    add: method({ input: { text: z.string() }, returns: z.void() }),
    find: method({ input: { term: z.string() }, returns: Texts }),
  },
}).implement({
  init: () => {
    fs.mkdirSync('/data', { recursive: true })
    // SQLite uses this agent's durable file system, not a server:
    const db = new DatabaseSync('/data/notes.db')
    db.exec('CREATE TABLE IF NOT EXISTS notes (text TEXT NOT NULL)')
    return { db }
  },
  methods: {
    add({ text }) {
      this.db.prepare('INSERT INTO notes VALUES (?)').run(text)
    },
    find({ term }) {
      const rows = this.db
        .prepare('SELECT text FROM notes WHERE text LIKE ?')
        .all(`%${term}%`)
      return Texts.parse(rows.map((row) => row.text))
    },
  },
})
import { generateText, Output } from 'ai'
import { anthropic } from '@ai-sdk/anthropic'
import { openai } from '@ai-sdk/openai'
const Review = z.object({ ok: z.boolean(), notes: z.string() })
const Draft = z.object({ text: z.string(), review: Review })

export const Drafter = defineAgent({
  name: 'DraftAgent',
  id: { topic: z.string() },
  methods: {
    write: method({ input: { idea: z.string() }, returns: Draft }),
  },
}).implement({
  init: ({ id }) => ({ topic: id.topic }),
  methods: {
    async write({ idea }) {
      // draft with one provider, review with another:
      const draft = await generateText({
        model: openai('gpt-4.1'),
        prompt: `Product update on ${this.topic}: ${idea}`,
      })
      const { output } = await generateText({
        model: anthropic('claude-sonnet-4-5'),
        output: Output.object({ schema: Review }),
        prompt: `Review this product update:\n${draft.text}`,
      })
      return { text: draft.text, review: output }
    },
  },
})
export const Planner = defineAgent({
  name: 'PlannerAgent',
  id: { topic: z.string() },
  methods: { plan: method({ input: {}, returns: Plan }) },
}).implement({
  init: ({ id }) => ({ topic: id.topic }),
  methods: {
    async plan() {
      // wait for a typed result from a Rust ResearchAgent:
      const facts = await ResearchAgent.get(this.topic).gather()
      // send now, without waiting for a result:
      SlackAgent.get('ops').post.trigger(facts.summary)
      // discover agents, their methods, and available tools:
      const agents = reflection.getAllAgentTypes().map((a) =>
        `${a.name}: ${a.methods.map((m) => m.name).join(', ')}`)
      const tools = reflection.getAllToolTypes().map((t) => t.name)
      return { facts, agents, tools }
    },
  },
})
import { setTimeout as sleep } from 'node:timers/promises'

export const Checkout = defineAgent({
  name: 'CheckoutAgent',
  id: { orderId: z.string() },
  // webhooks require this agent to have an HTTP mount:
  http: http.mount('/checkout/{orderId}'),
  methods: {
    pay: method({ input: { amount: z.int() }, returns: z.void() }),
  },
}).implement({
  init: ({ id }) => ({ orderId: id.orderId }),
  methods: {
    async pay({ amount }) {
      const paid = createWebhook()
      await createPaymentLink(this.orderId, amount, paid.getUrl())
      // schedule a call on another agent for tomorrow:
      const reminder = Mailer.client.get({ orderId: this.orderId })
        .remind.schedule(inDays(1))
      // it waits here for days if needed, using no CPU or memory:
      const event = PaymentEvent.parse((await paid).json())
      reminder.cancel()
      if (event.status !== 'succeeded') return
      // sleep for a week; its deadline survives restarts:
      await sleep(7 * 24 * 60 * 60_000)
      await sendSurvey(this.orderId)
    },
  },
})
export const Writer = defineAgent({
  name: 'WriterAgent',
  id: { doc: z.string() },
  http: http.mount('/docs/{doc}'),
  methods: {
    draft: method({
      input: { brief: z.string() },
      // clients resume this Durable Stream from an offset:
      returns: s.stream(z.string()),
      http: http.post('/draft'),
    }),
  },
}).implement({
  init: () => ({}),
  methods: {
    draft({ brief }) {
      return AgentStream.from((async function* () {
        for (const section of ['Summary', 'Details', 'Actions']) {
          // completed model calls are not repeated on recovery:
          const reply = await openai.responses.create({
            model: 'gpt-4.1',
            input: `${brief}\nWrite the ${section} section.`,
          })
          yield reply.output_text
        }
      })())
    },
  },
})
export const Stock = defineAgent({
  name: 'StockAgent',
  id: { sku: z.string() },
  methods: {
    restock: method({ input: { n: z.int() }, returns: z.void() }),
    level: method({
      input: {}, returns: z.int(),
      // repeat calls come from a cache, without loading the agent:
      // the cache clears when a restock succeeds:
      // outgoing HTTP, agent calls and key-value writes fail:
      readOnly: true,
    }),
  },
}).implement({
  init: () => ({ units: 0 }),
  methods: {
    restock({ n }) { this.units += n },
    level() { return this.units },
  },
})
  • Overview Agents are typed code in TypeScript, Effect, Rust, Scala, MoonBit or Go, each in its own WebAssembly VM. After a crash or restart, execution resumes mid-function.
  • Per-agent databases Each agent has its own durable file system, so an embedded database such as SQLite keeps its file there, beside the agent: no database server to run or connect to.
  • Any model, with your libraries Call any model with the libraries you already use, such as the OpenAI and Anthropic SDKs, Vercel AI SDK, TanStack AI or Effect AI, or a provider's own HTTP API.
  • Agents working together Typed calls between agents, across languages: wait for the result or send and move on. Agents can also discover which other agents and tools exist and what they offer.
  • Awaitable webhooks, timers and schedules Approvals and payment callbacks become promises your code awaits. Sleeps, timers and scheduled calls fire on time across restarts, with no idle cost.
  • Durable streaming Streaming endpoints become resumable streams: clients that reload or reconnect pick up an agent's output where it left off, even after a crash.
  • Read-only methods Golem caches the results of methods marked read-only and answers repeat calls without loading the agent, until a regular method succeeds. Outgoing calls in them fail.
Tools

Every tool call in its own VM, executed exactly once

digest-agent.ts build-agent.ts stripe-refund.ts orders-access.ts support-agent.ts account-agent.ts
export const summarize = toolDefinition('summarize')
  .body((b) => b.positional('url', z.string()).returns(z.string()))

summarize.implement({
  // each call gets a fresh VM that can't read the agent's memory:
  summarize: async ({ url }) => {
    // recovery reuses this completed fetch's response:
    const page = await (await fetch(url)).text()
    return ok(await summarizeText(page))
  },
})

export const Digest = defineAgent({
  name: 'DigestAgent',
  id: { topic: z.string() },
  methods: {
    build: method({ input: { urls: Urls }, returns: Urls }),
  },
}).implement({
  init: () => ({}),
  methods: {
    async build({ urls }) {
      const out: string[] = []
      for (const url of urls) {
        out.push(await summarize.client().summarize({ url }))
      }
      return out
    },
  },
})
export const outline = toolDefinition('outline', {
  // it works on the agent's files when its binding allows it:
  requiresFilesystem: true,
}).body((b) => b.positional('file', z.string()).returns(Headings))
outline.implement({
  outline: async ({ file }) => {
    const lines = fs.readFileSync(file, 'utf8').split('\n')
    return ok(lines.filter((line) => line.startsWith('#')))
  },
})

export const Builder = defineAgent({
  name: 'BuildAgent',
  id: { project: z.string() },
  methods: { build: method({ input: {}, returns: Headings }) },
}).implement({
  init: () => ({}),
  methods: {
    async build() {
      // bash calls the bound git, npm, tsc and node tools:
      const run = await BashClient.newClient().run(
        'git pull && npm install && tsc && node dist/build.js',
        '/workspace/site', 600)
      if (run.exitCode !== 0) throw new Error(run.stderr)
      const file = '/workspace/site/dist/report.md'
      return outline.client().outline({ file })
    },
  },
})
export async function stripeRefund(
  revealedKey: string, charge: string, amount: number,
): Promise<Response> {
  // Golem adds an idempotency key unless you set one:
  // Stripe honors that key, so a retry refunds once:
  return fetch('https://api.stripe.com/v1/refunds', {
    method: 'POST',
    headers: { Authorization: 'Bearer ' + revealedKey },
    body: new URLSearchParams({ charge, amount: String(amount) }),
  })
}
// define a grant for connections to the orders API only:
const ordersApi: permissions.PatternGrant = {
  class: 'network', owner: '', recipient: '*',
  verb: 'connect', resourceId: 'orders.example.com:443',
}

export function shareOrders(target: AgentId) {
  const bounds = { lowerPositive: [ordersApi] }
  const parent = permissions.deriveFromWallet(bounds).unwrap()
  // install a one-hour child; keep its parent for revocation:
  const child = parent.derive(
    bounds, permissions.Lifespan.iso8601('PT1H'),
  ).unwrap()
  child.install(permissions.Holder.agent(target)).unwrap()
  return parent
}

export function revokeOrders(parent: Card) {
  // revoking it revokes the child and every card derived from it:
  parent.revoke().unwrap()
}
export const Support = defineAgent({
  name: 'SupportAgent',
  id: { ticket: z.string() },
  // the agent receives an opaque handle, not the key:
  config: { stripeKey: s.secret(z.string()) },
  methods: {
    issueRefund: method({
      input: { charge: z.string(), amount: z.int() },
      returns: Refund,
    }),
  },
}).implement({
  init: () => ({}),
  methods: {
    issueRefund({ charge, amount }) {
      // refund needs a grant to reveal it; each reveal is recorded:
      // a binding can let a tool forward handles but reveal none:
      return refund.client().refund({
        charge, amount, apiKey: this.config.stripeKey,
      })
    },
  },
})
import { CrmFindCustomerClient }
  from 'crm-find-customer-tool-guest-client'

export const Account = defineAgent({
  name: 'AccountAgent',
  id: { team: z.string() },
  methods: {
    describe: method({
      // over MCP, this method becomes a tool with this description:
      description: 'Summarize a customer account by email',
      input: { email: z.string() }, returns: z.string(),
    }),
  },
}).implement({
  init: () => ({}),
  methods: {
    async describe({ email }) {
      // call the CRM's MCP tool through its generated typed client:
      const call = CrmFindCustomerClient.newClient()
        .crm_find_customer(email)
      const { result } = await call.collect()
      if (result.status === 'rejected') throw result.reason
      // the MCP server's output schema types this result:
      const { name, plan } = result.value.structured
      return `${name} is on the ${plan} plan`
    },
  },
})
  • Overview Golem runs each tool call in a fresh VM that can't read the agent's memory. An interrupted call replays with recorded results, without repeating completed effects.
  • Bundled and custom tools Shell, Git, Node.js, npm and TypeScript come bundled, and can work on the agent's own files. Build your own tools with any Golem SDK; no separate sandbox service needed.
  • Retries that take effect once Golem retries interrupted HTTP requests from agents and tools with an idempotency key it adds, so they take effect once where the API honors such keys, as Stripe does.
  • Delegated, revocable permissions Share a narrow slice of an agent's access, such as the orders API only for one hour, with another agent and the tools it calls. Revoking it revokes every derived copy.
  • Secrets Agents pass API keys to tools as opaque handles. Revealing a value takes a grant and is recorded, and a binding can forbid a tool to reveal any.
  • MCP and tool releases Call existing MCP servers through generated clients typed by their schemas. Expose agents and tools over MCP, and publish tools as versioned releases others can use.
Artifacts

Pages, apps and files your agents build for people

brief-agent.ts board-agent.ts exports-router.ts payslip-agent.ts
export const Brief = defineAgent({
  name: 'BriefAgent',
  id: { client: z.string() },
  http: http.mount('/briefs/{client}', {
    // Golem serves this agent's own file at /briefs/{client}:
    exposeFiles: [{ route: '/', path: '/out/index.html' }],
  }),
  methods: {
    write: method({
      input: { notes: z.string() }, returns: z.void(),
    }),
  },
}).implement({
  init: ({ id }) => ({ client: id.client }),
  methods: {
    async write({ notes }) {
      const res = await openai.responses.create({
        model: 'gpt-4.1',
        input: `Write an HTML brief for ${this.client}:\n${notes}`,
      })
      fs.mkdirSync('/out', { recursive: true })
      // it writes the page into its own files:
      // a read waits for this call, so it never gets half a page:
      fs.writeFileSync('/out/index.html', res.output_text)
    },
  },
})
const boardPage = `<button id="add">Add</button>
<script>add.onclick = () => fetch('cards', {
  method: 'POST', body: JSON.stringify({ title: 'New card' }),
})</script>`

export const Board = defineAgent({
  name: 'BoardAgent',
  id: { team: z.string() },
  // the URL names the agent: one BoardAgent per team:
  http: http.mount('/boards/{team}', {
    exposeFiles: [{ route: '/app', path: '/index.html' }],
  }),
  methods: {
    add: method({
      input: { title: z.string() }, returns: z.array(z.string()),
      // the page's Add button posts to this endpoint:
      http: http.post('/cards'),
    }),
  },
}).implement({
  init: () => {
    fs.writeFileSync('/index.html', boardPage)
    return { cards: [] as string[] }
  },
  methods: {
    add({ title }) {
      this.cards.push(title)
      return this.cards
    },
  },
})
const bytes = (text: string) => new TextEncoder().encode(text)

export const Exports = defineHttpRouter('ExportsRouter')
  .mount('/exports')
  .implementRaw(async (request) => {
    const id = request.path.match(/^\/exports\/([\w-]+)\.csv$/)?.[1]
    if (request.method !== 'GET' || !id) {
      const body = AgentStream.from<Uint8Array>([])
      return { status: 404, headers: [], body }
    }
    // stream the CSV from the agent that builds this export:
    const rows = await Export.client.get({ id }).csv()
    return {
      status: 200,
      headers: [
        { name: 'content-type', value: bytes('text/csv') },
        // browsers save it as a file instead of opening it:
        { name: 'content-disposition', value: bytes(
          `attachment; filename="${id}.csv"`) },
      ],
      body: rows,
    }
  })
const Pdf = s.unstructuredBinary({ mimeTypes: ['application/pdf'] })

export const Payslips = defineAgent({
  name: 'PayslipAgent',
  id: { employee: z.string() },
  http: http.mount('/payslips/{employee}', {
    // Golem checks the sign-in token before any agent code runs:
    auth: true,
  }),
  methods: {
    latest: method({
      input: { caller: s.principal() }, returns: s.option(Pdf),
      http: http.get('/latest.pdf'),
    }),
  },
}).implement({
  init: ({ id }) => ({ employee: id.employee }),
  methods: {
    latest({ caller }) {
      // the agent decides who may open the payslip:
      if (caller.tag !== 'oidc' || caller.sub !== this.employee) {
        return undefined
      }
      const val = fs.readFileSync('/payslips/latest.pdf')
      return { tag: 'inline', val, mimeType: 'application/pdf' }
    },
  },
})
  • Overview An agent writes a page, report or app into its own files, and Golem serves it from that agent at a URL declared in its code. A file is never served half-written.
  • Interactive apps A page's buttons call HTTP endpoints declared on agent methods. The URL names the agent, so each user or team gets its own durable agent behind the same app.
  • Reports and downloads Serve agent output in any format. A router sets content types and download headers, and streams a CSV, PDF or other file from the agent that builds it.
  • Private to the right people Users sign in with Google, GitLab or any OpenID Connect provider. Golem checks the token before agent code runs, and the agent decides what each caller may open.

From developers building on Golem

"Tried Temporal, Dapr, Restate, and Fermyon Spin. Golem gives me what I'd given up on finding: code-level customizability with no-code-level operational simplicity."

Samuel Iyeh
Samuel Iyeh
AI Software Engineer
Building Johnethel LMS

"I tried Akka event sourcing and Step Functions first. What I love about Golem is clean code — durable state and automatic retry built in."

Peter Kotula
Peter Kotula
Software Engineer
Building Golem Social

"I crashed a running agent mid-test — its .schedule() reminder still fired on time. Golem's durability isn't bolted on; it's the runtime."

Rahul Joshi
Rahul Joshi
Full-Stack AI Developer
Building WarpMind

"Like AWS Lambda, but the function has memory across invocations and durable reminders. Durability is invisible — exactly how infrastructure should feel."

Seeta Ramayya Vadali
Seeta Vadali
Senior Consultant
Building Seeta AI

"For JournAI, I modeled log analysis as durable agents. Golem's guarantees around state, retries, and recovery let me focus on the logic, not the infrastructure."

Daniele Torelli
Daniele Torelli
Senior Software Engineer
Building JournAI
Operations

Run it locally, self-hosted or managed

Local

One binary runs the whole platform on your laptop. Restart it, and your agents are still there.

Self-hosted

Run the full open source platform in production, on your own Docker or Kubernetes infrastructure.

Golem Cloud

Deploy to the managed service and let us run it, with no servers of your own. Free during the Developer Preview.

In every deployment
  • Observability and audit OpenTelemetry traces and metrics. Each agent's oplog records its external interactions and permission decisions for audit.
  • Live updates and rollback Update running agents after a compatibility check. Roll an agent back to an earlier point, or clone it to try another approach.
  • Quotas Rate, capacity and concurrency limits throttle, reject or terminate runaway agents and tools, and can change without a redeploy.

Questions developers ask

Do I still need my database?

Probably not. Each agent's whole execution is durable, and each agent has its own durable file system and can have its own database. Golem Social, a whole social network, runs on Golem with no external database. Keep existing databases where other systems share the data.

Can I keep my agent framework?

Yes. A framework shapes the agent loop and usually saves the conversation between turns. It runs inside a Golem agent, which keeps the agent's whole execution durable, in-progress tool calls included, and enforces permissions in the runtime.

How is this different from an actor platform?

Actor platforms persist each entity's state. Golem keeps the agent's whole execution durable, including its call stack, files and environment, so it continues in the middle of a call. Tool calls run exactly once, and the runtime enforces permissions.

How does it compare with a workflow engine?

Workflow engines usually ask you to split work into activities they record. In Golem, ordinary code is durable as it runs, with no restructuring, and each agent also gets its own VM, with a durable file system, and runtime-checked permissions.

Is Golem open source?

Yes. Golem uses the BUSL-1.1 license, which converts to Apache-2.0 after four years. Self-hosting for production workloads is permitted.

Can I keep my existing web app?

Yes. Its server can call agents through a generated client or the REST API, and it keeps its own front-end and hosting. Serving artifacts from Golem is an option, not a requirement.

Build your first durable agent

Install one binary and run the platform locally.