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 typed 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))
        // it resumes here after a crash, with memory and files:
        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 },
  },
})
  • Same guarantees and primitives Code in TypeScript, Effect, Rust, Scala, MoonBit or Go resumes mid-function with memory and files after a crash.
  • Per-agent databases Each agent's durable file system can hold an embedded database such as SQLite: no server to run or connect to.
  • Any model, with your libraries Model calls can use OpenAI or Anthropic SDKs, Vercel AI SDK, TanStack AI, Effect AI or a provider's own HTTP API.
  • Agents working together Agents make typed cross-language calls, wait or move on, and discover what other agents and tools offer.
  • Awaitable webhooks, timers, schedules Approval and payment webhooks are promises. Sleeps and scheduled calls fire on time across restarts, at no idle cost.
  • Resumable streaming endpoints Clients that reload or reconnect pick up an agent's streamed output where they left off, even after a crash.
  • Cached read-only methods Repeat calls skip loading the agent until a regular method succeeds. Outgoing HTTP and agent calls fail.
Tools

Every tool call in its own VM, executed exactly once

digest-agent.ts build-agent.ts stripe-refund.ts orders-access.ts refund-policy.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()
}
const refuse = (val: string) =>
  new ToolInvokeError({ tag: 'constraint-violation', val })
// once configured, this wraps every refund call:
// agent code cannot list, remove or bypass it:
export const refundPolicy = refund.middleware({
  name: 'refund-policy',
  implementation: {
    refund: async (args, { underlying, principal }) => {
      // reject calls by their arguments and enforce a rate limit:
      if (args.amount > 100_000) throw refuse('over the limit')
      await checkRateLimit(principal)
      if (args.amount > 10_000) {
        // the durable caller can wait for a person across restarts:
        const approval = createPromise()
        await askFinance(approval, args.charge, args.amount)
        const verdict = await awaitPromise(approval)
        if (!approved(verdict)) throw refuse('declined')
      }
      // forwarding cannot widen the agent's permissions:
      const receipt = await underlying.refund(args)
      // record the refund and hide the customer's email:
      await audit(args.charge, args.amount, receipt.id)
      return { ...receipt, customerEmail: '[redacted]' }
    },
  },
})
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 }) {
      // a client generated from the MCP server's schemas:
      // Golem checks, records and replays it like any tool call:
      // a retry resends the same idempotency key to the server:
      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 Each call's fresh VM can't read agent memory. Recovery reuses recorded results rather than repeating those calls.
  • Tools without a sandbox service Shell, Git, Node.js, npm and TypeScript are bundled and can use the agent's files. Custom tools use any Golem SDK.
  • HTTP retries from agents and tools Golem retries interrupted requests with an idempotency key it adds; APIs that honor it, like Stripe, act once.
  • Delegated, revocable permissions One agent lends another and its tools only the orders API for an hour. Revoking it revokes every derived copy.
  • Middleware agent code can't bypass Hooks can wrap every tool call to reject by argument, hold refunds for human approval, redact, audit and rate-limit.
  • Secrets Agents pass API keys to tools as opaque handles. Reveals need a grant and are recorded; bindings can forbid them.
  • Serve agents and tools over MCP Typed MCP clients get tool-call guarantees. Grant opt-in immutable releases across environments and accounts.
Artifacts

Charts, pages and apps your agents make on request

analyst-agent.ts app-tool.ts dashboard-agent.ts chart-agent.ts export-router.ts billing-agent.ts private-chart-agent.ts
export const Analyst = defineAgent({
  name: 'AnalystAgent',
  id: { chat: z.string() },
  http: http.mount('/chats/{chat}', {
    // Golem serves files this agent writes, never half-written:
    exposeFiles: [{ route: '/*', path: '/out/$1' }],
  }),
  methods: { ask: method({ input: { question: z.string() },
    returns: z.string(), http: http.post('/ask') }) },
}).implement({
  init: ({ id }) => ({ chat: id.chat }),
  methods: {
    async ask({ question }) {
      // the model calls this tool when a user asks for a graph:
      const chart = tool({
        description: 'Chart sales for a quarter and link to it',
        inputSchema: Quarter,
        execute: async ({ quarter: q }) => {
          const name = crypto.randomUUID()
          fs.mkdirSync('/out', { recursive: true })
          fs.writeFileSync(`/out/${name}.png`, await chartPng(q))
          fs.writeFileSync(`/out/${name}.html`, pageFor(q, name))
          return `/chats/${this.chat}/${name}.html`
        },
      })
      return (await generateText({ model, prompt: question,
        tools: { chart }, stopWhen: stepCountIs(5) })).text
    },
  },
})
// AppAgent's model calls this tool to save an app it just wrote:
export const present = toolDefinition('present', {
  requiresFilesystem: true,
}).body((b) => b.positional('name', Name)
  .positional('html', z.string()).returns(z.string()))
present.implement({
  present: async ({ name, html }) => {
    // the app is saved in the calling agent's own files:
    fs.mkdirSync('/out', { recursive: true })
    fs.writeFileSync(`/out/${name}.html`, html)
    return ok(`${name}.html`)
  },
})
export const Apps = defineAgent({
  name: 'AppAgent',
  id: { chat: z.string() },
  http: http.mount('/apps/{chat}', {
    exposeFiles: [{ route: '/*', path: '/out/$1' }],
  }),
  methods: {
    sales: method({
      input: { region: z.string() }, returns: z.array(Row),
      // the app's buttons fetch('sales?region=...') from here:
      http: http.get('/sales?region={region}'),
    }),
  },
}).implement({
  init: () => ({}),
  // each click runs this as durable code in the agent's own VM:
  methods: { sales: ({ region }) => querySales(region) },
})
export const Dashboards = defineAgent({
  name: 'DashboardAgent',
  id: { user: z.string() },
  // the path names the agent: /dashboards/ana is Ana's own agent:
  http: http.mount('/dashboards/{user}', {
    auth: true,
    exposeFiles: [{ route: '/*', path: '/out/$1' }],
  }),
  methods: {
    ask: method({ input: { request: z.string() },
      returns: z.string(), http: http.post('/ask') }),
  },
}).implement({
  // the first request to /dashboards/ana creates her agent:
  init: ({ id }) => ({ user: id.user }),
  methods: {
    async ask({ request }) {
      const name = crypto.randomUUID()
      fs.mkdirSync('/out', { recursive: true })
      // the model builds this dashboard for her one request:
      fs.writeFileSync(`/out/${name}.html`,
        await dashboardFor(this.user, request))
      // what it made for her is served under her path:
      return `/dashboards/${this.user}/${name}.html`
    },
  },
})
const Png = s.unstructuredBinary({ mimeTypes: ['image/png'] })

export const Charts = defineAgent({
  name: 'ChartAgent',
  id: { team: z.string() },
  http: http.mount('/charts/{team}'),
  methods: {
    chart: method({
      // over MCP, a chat assistant calls this when a user asks:
      description: 'Draw a sales chart for one quarter',
      input: { quarter: z.string() },
      // the reply is the PNG itself, over HTTP or MCP:
      returns: Png,
      http: http.get('/chart.png?quarter={quarter}'),
    }),
  },
}).implement({
  init: ({ id }) => ({ team: id.team }),
  methods: {
    async chart({ quarter }) {
      // the chart is drawn for this call, and no file is written:
      const val = drawChart(await querySales(this.team, quarter))
      return { tag: 'inline', val, mimeType: 'image/png' }
    },
  },
})
// asked to export results as CSV, the agent replies with a link:
// the link is /exports/<chat>/<random id>.csv, for that user only:
export const Exports = defineHttpRouter('ExportsRouter')
  .mount('/exports')
  .implementRaw(async (request) => {
    const path = /^\/exports\/([\w-]+)\/([\w-]{36})\.csv$/
    const m = request.path.match(path)
    if (request.method !== 'GET' || !m) {
      return { status: 404, headers: [], body: empty() }
    }
    // that user's agent builds the CSV now and streams it:
    const csv = await Analyst.client.get({ chat: m[1] })
      .exportCsv({ id: m[2] })
    return {
      status: 200,
      headers: [
        header('content-type', 'text/csv'),
        // the browser saves it as a download:
        header('content-disposition',
          'attachment; filename="results.csv"'),
      ],
      body: csv,
    }
  })
export const Billing = defineAgent({
  name: 'BillingAgent',
  id: { org: z.string() },
  // Golem checks the user's sign-in before any code here runs:
  http: http.mount('/billing/{org}', { auth: true }),
  methods: {
    ask: method({
      // Golem passes in the user who signed in:
      input: { question: z.string(), caller: s.principal() },
      returns: z.string(), http: http.post('/ask'),
    }),
  },
}).implement({
  init: ({ id }) => ({ org: id.org }),
  methods: {
    async ask({ question, caller }) {
      if (caller.tag !== 'oidc') throw new Error('sign-in required')
      // the issuer can be Google, GitLab or any OIDC provider:
      const { issuer, sub } = caller
      // the model makes a page from that user's own invoices:
      const invoices = await invoicesOf(this.org, issuer, sub)
      return pageFor(question, invoices)
    },
  },
})
const Png = s.unstructuredBinary({ mimeTypes: ['image/png'] })

export const PrivateCharts = defineAgent({
  name: 'PrivateChartsAgent',
  id: { team: z.string() },
  http: http.mount('/private/{team}', { auth: true }),
  methods: {
    ask: method({
      input: { question: z.string(), caller: s.principal() },
      returns: z.number(), http: http.post('/ask'),
    }),
    open: method({ input: { n: z.number(), caller: s.principal() },
      returns: s.option(Png), http: http.get('/charts/{n}') }),
  },
}).implement({
  init: (): { charts: Chart[] } => ({ charts: [] }),
  methods: {
    async ask({ question, caller }) {
      // the agent charts a signed-in user's question for that user:
      const png = await answerWithChart(question)
      this.charts.push({ askedBy: userKey(caller), png })
      return this.charts.length
    },
    open({ n, caller }) {
      const made = this.charts[n - 1]
      // anyone else, even signed in, gets a 404:
      if (made?.askedBy !== userKey(caller)) return undefined
      return { tag: 'inline', val: made.png, mimeType: 'image/png' }
    },
  },
})
  • Overview Asked for a sales graph, a tool writes a PNG and HTML page in agent files. Golem serves both, never half-written.
  • Apps for one user's request, no deploy A tool saves the model's small app in agent files. Buttons call endpoints running durable code in the agent's own VM.
  • One agent per user, from the URL Ana's first request to /dashboards/ana creates her agent. Pages and files it makes for her appear under her path.
  • Images and PDFs as the reply An agent can reply with a PNG or PDF it just made: bytes with their MIME type over HTTP, binary content over MCP.
  • CSV downloads through a router A router matches path and method, sets status, content type and download headers, and streams the agent's CSV.
  • OAuth2 sign-in with PKCE Login uses Google, GitLab or any OpenID Connect provider. Golem checks its short-lived token before agent code runs.
  • Only for the person who asked Sign-in says who asks, not what they may see. The agent records who asked for each chart; others get a 404.

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.

Does showing an artifact need a build or deploy step?

No. The agent makes a new chart, page, file or small app for one user's request and shows it to that user. Golem serves it directly from the agent. The agent does not change an existing app and needs no access to your build or deploy infrastructure.

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.

Build your first durable agent

Install one binary and run the platform locally.