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.
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.
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.
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.
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.
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.
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 },
},
}) const Email = Schema. Struct({
id: Schema.String, body: Schema.String,
})
const Label = Schema. Literals([ "urgent", "billing", "other"])
export const Triage = defineAgent({
name: "TriageAgent",
// each inbox gets its own agent, in its own WebAssembly VM:
id: { inbox: Schema.String },
methods: {
triage: method({
input: { emails: Schema. Array(Email) },
success: Schema. Record(Schema.String, Label),
}),
},
}). implement({
init: ({ inbox }) => Effect. succeed({ inbox }),
methods: ({ inbox }) => ({
triage: ({ emails }) => Effect. gen( function* () {
const labels: Record<string, typeof Label.Type> = {}
for ( const email of emails) {
const label = yield* classify(email.body). pipe(
Effect. flatMap(Schema. decodeUnknownEffect(Label)))
// after a crash or restart, it resumes inside this loop:
yield* applyLabel(inbox, email.id, label)
labels[email.id] = label
}
return labels
}). pipe(Effect.orDie),
}),
}) import { SqliteClient } from "@golemcloud/effect-golem/sqlite"
const Texts = Schema. Array(Schema.String)
export const Notes = defineAgent({
name: "NotesAgent",
id: { user: Schema.String },
methods: {
add: method({
input: { text: Schema.String }, success: Schema.Void,
}),
find: method({
input: { term: Schema.String }, success: Texts,
}),
},
}). implement({
init: () => Effect. gen( function* () {
fs. mkdirSync( "/data", { recursive: true })
// SQLite uses this agent's durable file system, not a server:
return yield* SqliteClient. make({ filename: "/data/notes.db" })
}). pipe(Effect. tap(( db) =>
db`CREATE TABLE IF NOT EXISTS notes (text TEXT NOT NULL)` )),
methods: ( db) => ({
add: ({ text }) => db`INSERT INTO notes VALUES (${ text})`. pipe(
Effect.asVoid, Effect.orDie),
find: ({ term }) => db`SELECT text FROM notes
WHERE text LIKE ${`%${term}%`}`. pipe(
Effect. map(( rows) => rows. map(( row) => row.text)),
Effect. flatMap(Schema. decodeUnknownEffect(Texts)),
Effect.orDie),
}),
}) import { LanguageModel } from "effect/ai"
import { AnthropicLanguageModel } from "@effect/ai-anthropic"
import { OpenAiLanguageModel } from "@effect/ai-openai"
const Review = Schema. Struct({
ok: Schema.Boolean, notes: Schema.String,
})
const Draft = Schema. Struct({ text: Schema.String, review: Review })
export const Drafter = defineAgent({
name: "DraftAgent",
id: { topic: Schema.String },
methods: { write: method({
input: { idea: Schema.String }, success: Draft,
}) },
}). implement({
init: ({ topic }) => Effect. succeed({ topic }),
methods: ({ topic }) => ({
write: ({ idea }) => Effect. gen( function* () {
// draft with one provider, review with another:
const draft = yield* LanguageModel. generateText({
prompt: `Product update on ${topic}: ${idea}`,
}). pipe(Effect. provide(OpenAiLanguageModel. model( "gpt-4.1" )))
const { value } = yield* LanguageModel. generateObject({
schema: Review,
prompt: `Review this product update:\n${draft. text}`,
}). pipe(Effect. provide(
AnthropicLanguageModel. model( "claude-sonnet-4-5")))
return { text: draft.text, review: value }
}). pipe(Effect. provide(providers), Effect.orDie),
}),
}) export const Planner = defineAgent({
name: "PlannerAgent",
id: { topic: Schema.String },
methods: { plan: method({ input: {}, success: Plan }) },
}). implement({
init: ({ topic }) => Effect. succeed({ topic }),
methods: ({ topic }) => ({
plan: () => Effect. gen( function* () {
// wait for a typed result from a Rust ResearchAgent:
const research = yield* ResearchAgent. get(topic)
const facts = yield* research. gather()
// send now, without waiting for a result:
const slack = yield* SlackAgent. get( "ops")
yield* slack.post. trigger(facts.summary)
// discover agents, their methods, and available tools:
const agents = ( yield* Reflection.getAllAgentTypes). map(( a) =>
`${a. name}: ${a. methods. map(( m) => m. name). join( ", ")}`)
const tools = ( yield* Reflection. getAllToolTypes())
. map(( t) => t.name)
return { facts, agents, tools }
}). pipe(Effect.scoped, Effect.orDie),
}),
}) export const Checkout = defineAgent({
name: "CheckoutAgent",
id: { orderId: Schema.String },
// webhooks require this agent to have an HTTP mount:
http: Http. mount( "/checkout/{orderId}"),
methods: {
pay: method({
input: { amount: Schema.Int }, success: Schema.Void,
}),
},
}). implement({
init: ({ orderId }) => Effect. succeed({ orderId }),
methods: ({ orderId }) => ({
pay: ({ amount }) => Effect. gen( function* () {
const paid = yield* Webhook.create
yield* createPaymentLink(orderId, amount, paid.url)
// schedule a call on another agent for tomorrow:
const mailer = yield* Mailer.client. get({ orderId })
const reminder = yield* mailer.remind. schedule( inDays( 1), {})
// it waits here for days if needed, using no CPU or memory:
const payload = yield* paid.await
const event = yield* payload. decode(PaymentEvent)
yield* reminder. cancel()
if (event.status !== "succeeded") return
// sleep for a week; its deadline survives restarts:
yield* Effect. sleep( "7 days")
yield* sendSurvey(orderId)
}). pipe(Effect.scoped, Effect.orDie),
}),
}) export const Writer = defineAgent({
name: "WriterAgent",
id: { doc: Schema.String },
http: Http. mount( "/docs/{doc}"),
methods: {
draft: method({
input: { brief: Schema.String },
// clients resume this Durable Stream from an offset:
success: WitTypes. AgentStream(Schema.String),
http: [Http. post( "/draft")],
}),
},
}). implement({
init: () => Effect.void,
methods: () => ({
draft: ({ brief }) => Effect. succeed(
Stream. fromIterable([ "Summary", "Details", "Actions"]). pipe(
Stream. mapEffect(( section) =>
// completed model calls are not repeated on recovery:
LanguageModel. generateText({
prompt: `${brief}\nWrite the ${section} section.` ,
}),
),
Stream. map(( reply) => reply.text),
Stream. provide(OpenAiLanguageModel. model( "gpt-4.1" )),
Stream. provide(openai), Stream.orDie,
),
),
}),
}) export const Stock = defineAgent({
name: "StockAgent",
id: { sku: Schema.String },
methods: {
restock: method({
input: { n: Schema.Int }, success: Schema.Void,
}),
level: method({
input: {}, success: Schema.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: () => Ref. make( 0),
methods: ( units) => ({
restock: ({ n }) => Ref. update(units, ( u) => u + n),
level: () => Ref. get(units),
}),
}) #[derive( Schema)] pub struct Email { id: String, body: String }
#[derive( Schema, Clone, Copy)]
pub enum Label { Urgent, Billing, Other }
#[agent_definition]
pub trait TriageAgent {
// each inbox gets its own agent, in its own WebAssembly VM:
fn new(inbox : String) -> Self;
async fn triage( &self, emails: Vec<Email>)
-> Result<HashMap<String, Label>, AppError>;
}
struct TriageAgentImpl { inbox: String }
#[agent_implementation]
impl TriageAgent for TriageAgentImpl {
fn new(inbox : String) -> Self { Self { inbox } }
async fn triage( &self, emails: Vec<Email>)
-> Result<HashMap<String, Label>, AppError> {
let mut labels = HashMap::new();
for email in emails {
let label: Label = classify( &email. body).await? ;
// after a crash or restart, it resumes inside this loop:
apply_label( &self. inbox, &email. id, label).await? ;
labels. insert(email . id, label);
}
Ok(labels)
}
} use rusqlite::Connection;
#[agent_definition]
pub trait NotesAgent {
fn new(user : String) -> Self;
fn add( &mut self, text: String) -> Result<(), AppError>;
fn find( &self, term: String) -> Result<Vec<String>, AppError>;
}
fn open() -> Result<Connection, AppError> {
fs::create_dir_all( "/data")?;
// SQLite uses this agent's durable file system, not a server:
let db = Connection::open( "/data/notes.db" )?;
db. execute_batch(
"CREATE TABLE IF NOT EXISTS notes (text TEXT NOT NULL)" )?;
Ok(db)
}
struct NotesAgentImpl;
#[agent_implementation]
impl NotesAgent for NotesAgentImpl {
fn new(_user : String) -> Self { Self }
fn add( &mut self, text: String) -> Result<(), AppError> {
open() ?. execute( "INSERT INTO notes VALUES (?)" , [text])?;
Ok(())
}
fn find( &self, term: String) -> Result<Vec<String>, AppError> {
let db = open() ?;
let mut stmt =
db. prepare( "SELECT text FROM notes WHERE text LIKE ?")?;
let rows = stmt. query_map([ format!( "%{term}%")], |r| r. get( 0))?;
Ok(rows . collect::<Result<_, _>>() ?)
}
} use golem_ai_llm::LlmProvider;
use golem_ai_llm_anthropic::{Anthropic, AnthropicConfig};
use golem_ai_llm_openai::{OpenAI, OpenAiConfig};
#[derive( Schema)] pub struct Review { ok: bool, notes: String }
#[derive( Schema)] pub struct Draft { text: String, review: Review }
#[agent_definition]
pub trait DraftAgent {
fn new(topic : String) -> Self;
async fn write( &self, idea: String) -> Result<Draft, AppError>;
}
struct DraftAgentImpl { topic: String }
#[agent_implementation]
impl DraftAgent for DraftAgentImpl {
fn new(topic : String) -> Self { Self { topic } }
async fn write( &self, idea: String) -> Result<Draft, AppError> {
// draft with one provider, review with another:
let draft = text( OpenAI::send(
OpenAiConfig::from_env() ?,
vec![user( &format!( "Product update on {}: {idea}",
self. topic))],
model( "gpt-4.1" ),
).await )?;
let review = json( Anthropic::send(
AnthropicConfig::from_env() ?,
vec![user( &format!( "Review this product update:\n{draft}"))],
json_model::<Review>( "claude-sonnet-4-5"),
).await )?;
Ok( Draft { text: draft, review })
}
} #[agent_definition]
pub trait PlannerAgent {
fn new(topic : String) -> Self;
async fn plan( &self) -> Result<Plan, AppError>;
}
struct PlannerAgentImpl { topic: String }
#[agent_implementation]
impl PlannerAgent for PlannerAgentImpl {
fn new(topic : String) -> Self { Self { topic } }
async fn plan( &self) -> Result<Plan, AppError> {
// wait for a typed result from a TypeScript ResearchAgent:
let facts = ResearchAgent::get( self. topic. clone()) ?
. gather() .await? ;
// send now, without waiting for a result:
SlackAgent::get( "ops". into()) ?
. trigger_post(facts . summary. clone()) ?;
// discover agents, their methods, and available tools:
let agents = reflection::get_all_agent_types() ?. iter()
. map( |a| format!( "{}: {}", a. name(), a . methods() . iter()
. map( |m| m. name()) . collect::<Vec<_>>() . join( ", ")))
. collect();
let tools = reflection::get_all_tool_types() ?. iter()
. map( |t| t. lookup_name() . to_string()) . collect();
Ok( Plan { facts, agents, tools })
}
} use golem_rust::wasip3::clocks::monotonic_clock::wait_for as sleep;
// webhooks require this agent to have an HTTP mount:
#[agent_definition(mount = "/checkout/{order_id}")]
pub trait CheckoutAgent {
fn new(order_id : String) -> Self;
async fn pay( &mut self, amount: u64) -> Result<(), AppError>;
}
struct CheckoutAgentImpl { order_id: String }
#[agent_implementation]
impl CheckoutAgent for CheckoutAgentImpl {
fn new(order_id : String) -> Self { Self { order_id } }
async fn pay( &mut self, amount: u64) -> Result<(), AppError> {
let paid = create_webhook() ?;
create_payment_link( &self. order_id, amount, paid. url()) .await? ;
// schedule a call on another agent for tomorrow:
let reminder = MailerAgentClient::get( self. order_id. clone())
. schedule_cancelable_remind( in_days( 1))?;
// it waits here for days if needed, using no CPU or memory:
let event: PaymentEvent = paid.await. json() ?;
reminder. cancel();
if event. status != "succeeded" { return Ok(()); }
// sleep for a week; its deadline survives restarts:
sleep( 7 * 24 * 60 * 60 * 1_000_000_000).await ;
send_survey( &self. order_id).await? ;
Ok(())
}
} #[agent_definition(mount = "/docs/{doc}")]
pub trait WriterAgent {
fn new(doc : String) -> Self;
#[endpoint(post = "/draft")]
// clients resume this Durable Stream from an offset:
fn draft( &self, brief: String) -> AgentStream<String>;
}
struct WriterAgentImpl;
#[agent_implementation]
impl WriterAgent for WriterAgentImpl {
fn new(_doc : String) -> Self { Self }
fn draft( &self, brief: String) -> AgentStream<String> {
let ( mut out, stream) = AgentStream::new();
spawn_local( async move {
for section in ["Summary", "Details", "Actions"] {
// completed model calls are not repeated on recovery:
let reply = text( OpenAI::send(
OpenAiConfig::from_env() . expect( "no OpenAI config"),
vec![user( &format!(
"{brief}\nWrite the {section} section." ))],
model( "gpt-4.1" ),
).await ). expect( "model call failed");
if out. write_one(reply) .await. is_err() { return; }
}
});
stream
}
} #[agent_definition]
pub trait StockAgent {
fn new(sku : String) -> Self;
fn restock( &mut self, n: u64);
// 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:
#[read_only]
fn level( &self) -> u64;
}
struct StockAgentImpl { units: u64 }
#[agent_implementation]
impl StockAgent for StockAgentImpl {
fn new(_sku : String) -> Self { Self { units: 0 } }
fn restock( &mut self, n: u64) { self. units += n; }
fn level( &self) -> u64 { self. units }
} final case class Email( id: String, body: String) derives Schema
enum Label derives Schema:
case Urgent, Billing, Other
@agentDefinition()
trait TriageAgent extends BaseAgent:
// each inbox gets its own agent, in its own WebAssembly VM:
class Id( val inbox: String)
def triage( emails: List[Email]): Future[Map[String, Label]]
@agentImplementation()
final class TriageAgentImpl( inbox: String) extends TriageAgent:
def triage( emails: List[Email]): Future[Map[String, Label]] =
emails.foldLeft( Future.successful( Map.empty[ String, Label])):
(labels, email) =>
for
done <- labels
label <- classify(email.body).map( Label.valueOf)
// after a crash or restart, it resumes inside this loop:
_ <- applyLabel(inbox, email.id, label)
yield done + (email.id -> label) Scala, MoonBit and Go agents each have their own durable file system too, but no supported SQLite library.
import ai.{ generateText, Output}
import aisdk. anthropic. anthropic
import aisdk. openai. openai
final case class Review( ok: Boolean, notes: String) derives Schema
final case class Draft( text: String, review: Review) derives Schema
@agentDefinition()
trait DraftAgent extends BaseAgent:
class Id( val topic: String)
def write( idea: String): Future[Draft]
@agentImplementation()
final class DraftAgentImpl( topic: String) extends DraftAgent:
def write( idea: String): Future[Draft] =
for
// draft with one provider, review with another:
draft <- generateText(
model = openai( "gpt-4.1" ),
prompt = s"Product update on $topic: $idea",
)
reviewed <- generateText(
model = anthropic( "claude-sonnet-4-5"),
output = Output.`object`[ Review],
prompt = s"Review this product update:\n${draft.text} ",
)
yield Draft(draft.text, reviewed.output) @agentDefinition()
trait PlannerAgent extends BaseAgent:
class Id( val topic: String)
def plan() : Future[Plan]
@agentImplementation()
final class PlannerAgentImpl( topic: String) extends PlannerAgent:
def plan() : Future[Plan] =
// wait for a typed result from a Rust ResearchAgent:
ResearchAgentClient.get(topic).gather().map : facts =>
// send now, without waiting for a result:
SlackAgentClient.get( "ops").post.trigger(facts.summary)
// discover agents, their methods, and available tools:
val agents = Reflection.getAllAgentTypes().orThrow.map : a =>
s"${a.name} : ${a.methods.map(_.name).mkString( ", ")}"
val tools = Reflection.getAllToolTypes().orThrow.map(_.name)
Plan(facts, agents, tools) // webhooks require this agent to have an HTTP mount:
@agentDefinition(mount = "/checkout/{orderId}")
trait CheckoutAgent extends BaseAgent:
class Id( val orderId: String)
def pay( amount: Int): Future[Unit]
@agentImplementation()
final class CheckoutAgentImpl( orderId: String)
extends CheckoutAgent:
def pay( amount: Int): Future[Unit] =
val paid = HostApi.createWebhook()
for
_ <- createPaymentLink(orderId, amount, paid.url)
// schedule a call on another agent for tomorrow:
reminder <- MailerAgentClient.get(orderId)
.remind.scheduleCancelableAt(inDays( 1))
// it waits here for days if needed, using no CPU or memory:
event <- paid.await().map(_.json[ PaymentEvent]())
_ = reminder.cancel()
_ <- if event.status != "succeeded" then Future.unit
else
// sleep for a week; its deadline survives restarts:
sleep( 7 * 24 * 60 * 60_000)
.flatMap(_ => sendSurvey(orderId))
yield () @agentDefinition(mount = "/docs/{doc}")
trait WriterAgent extends BaseAgent:
class Id( val doc: String)
@endpoint( "POST", "/draft")
// clients resume this Durable Stream from an offset:
def draft( brief: String): AgentStream[String]
@agentImplementation()
final class WriterAgentImpl( doc: String) extends WriterAgent:
def draft( brief: String): AgentStream[String] =
val sections = Iterator( "Summary", "Details", "Actions")
AgentStream.fromPull : () =>
if !sections.hasNext then Future.successful( None)
else
val section = sections.next()
// completed model calls are not repeated on recovery:
openai.responses.create( ResponseParams(
model = "gpt-4.1" ,
input = s"$brief\nWrite the $section section." ,
)).toFuture.map(reply => Some(reply.output_text)) @agentDefinition()
trait StockAgent extends BaseAgent:
class Id( val sku: String)
def restock( n: Int): Unit
// 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
def level() : Int
@agentImplementation()
final class StockAgentImpl( sku: String) extends StockAgent:
private var units = 0
def restock( n: Int): Unit = units += n
def level() : Int = units #derive.golem_schema
struct Email { id : String; body : String }
#derive.golem_schema
enum Label { Urgent; Billing; Other }
#derive.agent
struct TriageAgent {
inbox : String
}
// each inbox gets its own agent, in its own WebAssembly VM:
fn TriageAgent::new(inbox : String) -> TriageAgent {
{ inbox, }
}
pub async fn TriageAgent::triage(
self : Self,
emails : Array[Email],
) -> Map[String, Label] {
let labels : Map[String, Label] = {}
for email in emails {
let label = classify(email.body)
// after a crash or restart, it resumes inside this loop:
apply_label(self.inbox, email.id, label)
labels[email.id] = label
}
labels
} Scala, MoonBit and Go agents each have their own durable file system too, but no supported SQLite library.
#derive.golem_schema
struct Review { ok : Bool; notes : String } derive( FromJson)
#derive.golem_schema
struct Draft { text : String; review : Review }
#derive.agent
struct DraftAgent {
topic : String
}
fn DraftAgent::new(topic : String) -> DraftAgent {
{ topic, }
}
pub async fn DraftAgent::write(
self : Self, idea : String,
) -> Draft {
// draft with one provider, review with another:
let draft = @llm. generate_text(
model=@llm. openai( "gpt-4.1" ),
prompt="Product update on \{self.topic }: \{idea}",
)
let review : Review = @llm. generate_object(
model=@llm. anthropic( "claude-sonnet-4-5"),
prompt="Review this product update:\n\{draft}",
)
{ text: draft, review }
} #derive.agent
struct PlannerAgent {
topic : String
}
fn PlannerAgent::new(topic : String) -> PlannerAgent {
{ topic, }
}
pub async fn PlannerAgent::plan(self : Self) -> Plan {
// wait for a typed result from a Rust ResearchAgent:
let facts = @research. ResearchAgentClient::get(self.topic)
. gather()
// send now, without waiting for a result:
@slack. SlackAgentClient::get( "ops"). trigger_post(facts.summary)
// discover agents, their methods, and available tools:
let agents = @reflection. get_all_agent_types(). map(a =>
"\{a. name() }: \{a. methods(). map( m => m. name()). join( ", ")}")
let tools = @reflection. get_all_tool_types()
. map(t => t. lookup_name())
{ facts, agents, tools }
} #derive.agent
// webhooks require this agent to have an HTTP mount:
#derive.mount ( "/checkout/{order_id}")
struct CheckoutAgent {
order_id : String
}
fn CheckoutAgent::new(order_id : String) -> CheckoutAgent {
{ order_id, }
}
pub async fn CheckoutAgent::pay(self : Self, amount : Int) -> Unit {
let paid = @webhook. create()
create_payment_link(self.order_id, amount, paid. url())
// schedule a call on another agent for tomorrow:
let reminder = MailerAgentClient::get(self.order_id)
. schedule_cancelable_remind( in_days( 1))
// it waits here for days if needed, using no CPU or memory:
let body = @json. parse(paid. wait(). text())
let event : PaymentEvent = @json. from_json(body)
reminder. cancel()
if event.status != "succeeded" { return }
// sleep for a week; its deadline survives restarts:
@monotonicClock. wait_for( 7UL * 24 * 60 * 60 * 1_000_000_000)
send_survey(self.order_id)
} #derive.agent
#derive.mount ( "/docs/{doc}")
struct WriterAgent {
doc : String
}
fn WriterAgent::new(doc : String) -> WriterAgent {
{ doc, }
}
#derive.endpoint ( post="/draft")
pub fn WriterAgent::draft(
self : Self,
brief : String,
// clients resume this Durable Stream from an offset:
) -> @schema. AgentStream[String] {
@schema. AgentStream::produce( async fn(out) {
for section in ["Summary", "Details", "Actions"] {
// completed model calls are not repeated on recovery:
let reply = @llm. generate_text(
model=@llm. openai( "gpt-4.1" ),
prompt="\{brief}\nWrite the \{section} section." ,
)
guard out. write_one(reply) is Accepted else { return }
}
})
} #derive.agent
struct StockAgent {
sku : String
mut units : Int
}
fn StockAgent::new(sku : String) -> StockAgent {
{ sku, units: 0 }
}
pub fn StockAgent::restock(self : Self, n : Int) -> Unit {
self.units += n
}
// 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:
#derive.read_only
pub fn StockAgent::level(self : Self) -> Int {
self.units
} type Email struct{ ID, Body string }
// each inbox gets its own agent, in its own WebAssembly VM:
type TriageID struct{ Inbox string }
type TriageIn struct{ Emails []Email }
var Triager = golem. DefineAgent[TriageID](
golem. Spec{Name: "TriageAgent"})
var Triage = Triager. Method[TriageIn, map[string]Label]( "triage")
var _ = Triager. Implement( func( id TriageID) *TriageID {
return &id
}). Handle(Triage, golem. Adapt(( *TriageID).triage))
func ( t *TriageID) triage( in TriageIn) map[string]Label {
labels := map[string]Label{}
for _, email := range in.Emails {
label := parseLabel( classify(email.Body))
// after a crash or restart, it resumes inside this loop:
applyLabel(t.Inbox, email.ID, label)
labels[email.ID] = label
}
return labels
} Scala, MoonBit and Go agents each have their own durable file system too, but no supported SQLite library.
var g = genkit. Init(ctx,
genkit. WithPlugins( &openai. OpenAI{}, &anthropic. Anthropic{}))
type Review struct {
OK bool `json:"ok"`
Notes string `json:"notes"`
}
type Draft struct {
Text string
Review Review
}
type DraftID struct{ Topic string }
type WriteIn struct{ Idea string }
var Drafter = golem. DefineAgent[DraftID](
golem. Spec{Name: "DraftAgent"})
var Write = Drafter. Method[WriteIn, Draft]( "write")
var _ = Drafter. Implement( func( id DraftID) *DraftID {
return &id
}). Handle(Write, golem. Adapt( func( d *DraftID, in WriteIn) Draft {
// draft with one provider, review with another:
draft := golem. Must(genkit. GenerateText(ctx, g,
ai. WithModelName( "openai/gpt-4.1" ),
ai. WithPrompt( "Product update on "+d.Topic +": "+in.Idea)))
review, _, err := genkit. GenerateData[Review](ctx, g,
ai. WithModelName( "anthropic/claude-sonnet-4-5"),
ai. WithPrompt( "Review this product update:\n"+draft))
golem. Must0(err)
return Draft{Text: draft, Review: *review}
})) type PlannerID struct{ Topic string }
var Planner = golem. DefineAgent[PlannerID](
golem. Spec{Name: "PlannerAgent"})
var Plan = Planner. Method[golem. Unit, PlanResult]( "plan")
var _ = Planner. Implement( func( id PlannerID) *PlannerID {
return &id
}). Handle(Plan, golem. Adapt0( func( p *PlannerID) PlanResult {
// wait for a typed result from a Rust ResearchAgent:
facts := researcher. MustGetResearchAgent(
researcher. ResearchAgentID{Topic: p.Topic}). MustGather()
// send now, without waiting for a result:
slack. MustGetSlackAgent( slack. SlackAgentID{Channel: "ops"}).
MustTriggerPost(facts.Summary)
// discover agents, their methods, and available tools:
var agents, tools []string
for _, a := range reflection. DiscoverAgentTypes() {
var methods []string
for _, m := range a. Methods() {
methods = append(methods, m. Name())
}
agents = append(agents,
a. Name() +": "+strings. Join(methods, ", "))
}
for _, t := range reflection. DiscoverTools() {
tools = append(tools, t. Name())
}
return PlanResult{Facts: facts, Agents: agents, Tools: tools}
})) type CheckoutID struct{ OrderID string }
type PayIn struct{ Amount int32 }
var Checkout = golem. DefineAgent[CheckoutID]( golem. Spec{
Name: "CheckoutAgent",
// webhooks require this agent to have an HTTP mount:
HTTP: &golem. Mount{Path: "/checkout/{orderId}"},
})
var Pay = Checkout. Method[PayIn, golem. Unit]( "pay")
var _ = Checkout. Implement( func( id CheckoutID) *CheckoutID {
return &id
}). Handle(Pay, golem. AdaptUnit( func( c *CheckoutID, in PayIn) {
paid := golem. NewWebhook[PaymentEvent]()
createPaymentLink(c.OrderID, in.Amount, paid. URL())
// schedule a call on another agent for tomorrow:
mailer := Mailer. Get( MailerID{OrderID: c.OrderID})
reminder := Remind. MustSchedule(mailer, inDays( 1), golem. Unit{})
// it waits here for days if needed, using no CPU or memory:
event := paid. MustAwait()
reminder. Cancel()
if event.Status != "succeeded" {
return
}
// sleep for a week; its deadline survives restarts:
time. Sleep( 7 * 24 * time.Hour)
sendSurvey(c.OrderID)
})) type WriterID struct{ Doc string }
type DraftIn struct{ Brief string }
var Writer = golem. DefineAgent[WriterID]( golem. Spec{
Name: "WriterAgent", HTTP: &golem. Mount{Path: "/docs/{doc}"},
})
var Draft = Writer. Method[DraftIn, golem. AgentStream[string]](
// clients resume this Durable Stream from an offset:
"draft", golem. HTTP(golem. POST( "/draft")))
var _ = Writer. Implement( func( id WriterID) *WriterID {
return &id
}). Handle(Draft, golem. Adapt(( *WriterID).draft))
func ( *WriterID) draft( in DraftIn) golem. AgentStream[string] {
return golem. ProduceStream(in.write)
}
func ( in DraftIn) write( w *golem. AgentStreamWriter[string]) error {
sections := []string{"Summary", "Details", "Actions"}
for _, section := range sections {
// completed model calls are not repeated on recovery:
reply := golem. Must(ai.Responses. New(ctx,
responses. ResponseNewParams{Model: "gpt-4.1" ,
Input: responses. ResponseNewParamsInputUnion{
OfString: openai. String(in.Brief +
"\nWrite the " + section + " section." )}}))
if err := w. Write(reply. OutputText()); err != nil {
return err
}
}
return nil
} type StockID struct{ SKU string }
type RestockIn struct{ N int32 }
type stock struct{ units int32 }
var Stock = golem. DefineAgent[StockID](
golem. Spec{Name: "StockAgent"})
var Restock = Stock. Method[RestockIn, golem. Unit]( "restock")
var Level = Stock. Method[golem. Unit, int32]( "level",
// 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:
golem. ReadOnly())
var impl = Stock. Implement( func( StockID) *stock { return &stock{} })
var _ = impl. Handle(Restock, golem. AdaptUnit(( *stock).restock))
var _ = impl. Handle(Level, golem. Adapt0(( *stock).level))
func ( s *stock) restock( in RestockIn) { s.units += in.N }
func ( s *stock) level() int32 { return s.units } 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`
},
},
}) export const summarize = Tool. toolDefinition( "summarize"). body(
( b) => b. positional( "url", Schema.String). returns(Schema.String))
summarize. implement({
// each call gets a fresh VM that can't read the agent's memory:
summarize: ({ url }) => Effect. gen( function* () {
// recovery reuses this completed fetch's response:
const page = yield* HttpClient. get(url). pipe(
Effect. flatMap(( res) => res.text))
return Tool. ok( yield* summarizeText(page))
}). pipe(Effect.orDie),
}, FetchHttpClient.layer)
export const Digest = defineAgent({
name: "DigestAgent",
id: { topic: Schema.String },
methods: {
build: method({ input: { urls: Urls }, success: Urls }),
},
}). implement({
init: () => Effect.void,
methods: () => ({
build: ({ urls }) => Effect. gen( function* () {
const out: string[] = []
for ( const url of urls) {
out. push( yield* summarize. client()({ url }))
}
return out
}). pipe(Effect.orDie),
}),
}) export const outline = Tool. toolDefinition( "outline", {
// it works on the agent's files when its binding allows it:
requiresFilesystem: true,
}). body(( b) => b. positional( "file", Schema.String)
. returns(Headings))
outline. implement({
outline: ({ file }) => Effect. sync(() => {
const lines = fs. readFileSync(file, "utf8"). split( "\n")
return Tool. ok(lines. filter(( line) => line. startsWith( "#")))
}),
})
export const Builder = defineAgent({
name: "BuildAgent",
id: { project: Schema.String },
methods: { build: method({ input: {}, success: Headings }) },
}). implement({
init: () => Effect.void,
methods: () => ({
build: () => Effect. gen( function* () {
// bash calls the bound git, npm, tsc and node tools:
const run = yield* BashClient. newClient(). run(
"git pull && npm install && tsc && node dist/build.js" ,
"/workspace/site", 600)
if (run.exitCode !== 0) return yield* Effect. die(run.stderr)
const file = "/workspace/site/dist/report.md"
const headings: unknown = yield* outline. client()({ file })
return yield* Schema. decodeUnknownEffect(Headings)(headings)
}). pipe(Effect.orDie),
}),
}) export const stripeRefund = (
revealedKey: string, charge: string, amount: number,
) =>
// Golem adds an idempotency key unless you set one:
// Stripe honors that key, so a retry refunds once:
HttpClientRequest. post( "https://api.stripe.com/v1/refunds" ). pipe(
HttpClientRequest. bearerToken(revealedKey),
HttpClientRequest. bodyUrlParams({ charge, amount }),
HttpClient.execute,
) // 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 const shareOrders = Effect. fn( function* ( target: AgentId) {
const bounds = { lowerPositive: [ordersApi] }
const parent = yield* Permissions. deriveFromWallet(bounds)
// install a one-hour child; keep its parent for revocation:
const child = yield* Permissions. derive(
parent, bounds, Permissions.Lifespan. fromIso8601( "PT1H"),
)
yield* Permissions. install(
child, Permissions.Holder. agent(target))
return parent
})
export const revokeOrders = ( parent: Card) =>
// revoking it revokes the child and every card derived from it:
Permissions. revoke(parent) // the agent receives an opaque handle, not the key:
class SupportConfig extends defineConfig( "SupportAgent.Config" , {
stripeKey: Schema. Redacted(Schema.String),
}) {}
export const Support = defineAgent({
name: "SupportAgent",
id: { ticket: Schema.String },
config: SupportConfig,
methods: {
issueRefund: method({
input: { charge: Schema.String, amount: Schema.Int },
success: Refund,
}),
},
}). implement({
init: () => Effect.void,
methods: () => ({
issueRefund: ({ charge, amount }) => Effect. gen( function* () {
const config = yield* SupportConfig
// refund needs a grant to reveal it; each reveal is recorded:
// a binding can let a tool forward handles but reveal none:
return yield* refund. client()({
charge, amount, apiKey: yield* config.stripeKey.borrow,
})
}). pipe(Effect.orDie),
}),
}) import { CrmFindCustomerClient }
from "crm-find-customer-tool-guest-client"
export const Account = defineAgent({
name: "AccountAgent",
id: { team: Schema.String },
methods: {
describe: method({
// over MCP, this method becomes a tool with this description:
description: "Summarize a customer account by email",
input: { email: Schema.String }, success: Schema.String,
}),
},
}). implement({
init: () => Effect.void,
methods: () => ({
describe: ({ email }) => Effect. gen( function* () {
// call the CRM's MCP tool through its generated typed client:
const call = yield* CrmFindCustomerClient. newClient()
. crm_find_customer(email)
const { result } = yield* call.collect
const value = yield* Effect. fromResult(result)
// the MCP server's output schema types this result:
const { name, plan } = value.structured
return `${name} is on the ${plan} plan`
}). pipe(Effect.scoped, Effect.orDie),
}),
}) #[tool_definition]
pub trait Summarize {
async fn summarize( &self, url: Url) -> Result<String, AppError>;
}
#[tool_implementation]
impl Summarize for SummarizeImpl {
// each call gets a fresh VM that can't read the agent's memory:
async fn summarize( &self, url: Url) -> Result<String, AppError> {
let req = Request::get(url) . body( vec![])?;
// recovery reuses this completed fetch's response:
let page = http::response_text( http::send(req) .await? ).await? ;
Ok( summarize_text( &page).await? )
}
}
#[agent_definition]
pub trait DigestAgent {
fn new(topic : String) -> Self;
async fn build( &self, urls: Urls) -> Result<Urls, AppError>;
}
#[agent_implementation]
impl DigestAgent for DigestAgentImpl {
fn new(_topic : String) -> Self { Self }
async fn build( &self, urls: Urls) -> Result<Urls, AppError> {
let mut out = vec![];
for url in urls {
out. push( SummarizeClient::new() . summarize(url) .await? );
}
Ok(out)
}
} // it works on the agent's files when its binding allows it:
#[tool_definition(requires_filesystem = true)]
pub trait Outline {
fn outline( &self, file: String) -> Result<Headings, AppError>;
}
#[tool_implementation]
impl Outline for OutlineImpl {
fn outline( &self, file: String) -> Result<Headings, AppError> {
Ok( fs::read_to_string(file) ?. lines()
. filter( |l| l. starts_with( '#')). map( Into::into). collect())
}
}
#[agent_definition]
pub trait BuildAgent {
fn new(project : String) -> Self;
async fn build( &self) -> Result<Headings, AppError>;
}
#[agent_implementation]
impl BuildAgent for BuildAgentImpl {
fn new(_project : String) -> Self { Self }
async fn build( &self) -> Result<Headings, AppError> {
// bash calls the bound git, npm, tsc and node tools:
let run = BashClient::new() . run(
"git pull && npm install && tsc && node dist/build.js" . into(),
"/workspace/site". into(), 600).await? ;
if run. exit_code != 0 { return Err(run . stderr. into()); }
let file = "/workspace/site/dist/report.md" . to_string();
Ok( OutlineClient::default() . outline(file) .await? )
}
} pub async fn stripe_refund(
revealed_key: &str, charge: &str, amount: u64,
) -> Result<http::Response, AppError> {
// Golem adds an idempotency key unless you set one:
// Stripe honors that key, so a retry refunds once:
let form = [( "charge", charge), ( "amount", &amount. to_string())];
let req = Request::post( "https://api.stripe.com/v1/refunds" )
. header( AUTHORIZATION, format!( "Bearer {revealed_key}"))
. header( CONTENT_TYPE, "application/x-www-form-urlencoded")
. body( serde_urlencoded::to_string(form) ?. into_bytes()) ?;
Ok( http::send(req) .await? )
} // define a grant for connections to the orders API only:
fn orders_api() -> PatternGrant {
PatternGrant::new(
"network", "", "*", "connect", "orders.example.com:443" ,
)
}
pub fn share_orders(target : AgentId)
-> Result<PersistentCard, AppError> {
let bounds = Bounds {
lower_positive: vec![orders_api()], .. Bounds::default()
};
let parent = permissions::derive_from_wallet( &bounds, None)?;
// install a one-hour child; keep its parent for revocation:
let hour = Lifespan::iso8601( "PT1H")?;
let child = parent. derive( &bounds, Some( &hour))?;
child. install( &Holder::Agent(target)) ?;
Ok(parent)
}
pub fn revoke_orders(parent : PersistentCard)
-> Result<(), AppError> {
// revoking it revokes the child and every card derived from it:
parent. revoke() ?;
Ok(())
} #[derive( ConfigSchema)]
pub struct SupportConfig {
// the agent receives an opaque handle, not the key:
#[config_schema(secret)] stripe_key : Secret<String>,
}
#[agent_definition]
pub trait SupportAgent {
fn new(ticket : String,
#[agent_config] config: Config<SupportConfig>) -> Self;
async fn issue_refund( &self, charge: String, amount: u64)
-> Result<Receipt, AppError>;
}
struct SupportAgentImpl { config: Config<SupportConfig> }
#[agent_implementation]
impl SupportAgent for SupportAgentImpl {
fn new(_ticket : String,
#[agent_config] config: Config<SupportConfig>) -> Self {
Self { config }
}
async fn issue_refund( &self, charge: String, amount: u64)
-> Result<Receipt, AppError> {
// refund needs a grant to reveal it; each reveal is recorded:
// a binding can let a tool forward handles but reveal none:
let api_key = self. config. get() ?. stripe_key. handle() ?;
Ok( RefundClient::new() . refund(charge, amount, api_key) .await? )
}
} use crm_find_customer_tool_guest_client::CrmFindCustomerClient;
#[agent_definition]
pub trait AccountAgent {
fn new(team : String) -> Self;
// over MCP, this method becomes a tool with this description:
#[description( "Summarize a customer account by email")]
async fn describe( &self, email: String)
-> Result<String, AppError>;
}
struct AccountAgentImpl;
#[agent_implementation]
impl AccountAgent for AccountAgentImpl {
fn new(_team : String) -> Self { Self }
async fn describe( &self, email: String)
-> Result<String, AppError> {
// call the CRM's MCP tool through its generated typed client:
let call = CrmFindCustomerClient::new()
. crm_find_customer(email) .await? ;
let result = call. collect() .await. result?;
// the MCP server's output schema types this result:
let customer = result. structured;
let (name, plan) = (customer . name, customer. plan);
Ok( format!( "{name} is on the {plan} plan"))
}
} @toolDefinition()
trait Summarize:
def summarize( url: String): Future[String]
@toolImplementation()
final class SummarizeImpl extends Summarize:
// each call gets a fresh VM that can't read the agent's memory:
def summarize( url: String): Future[String] =
// recovery reuses this completed fetch's response:
fetch(url).toFuture.flatMap(_.text().toFuture)
.flatMap(summarizeText)
@agentDefinition()
trait DigestAgent extends BaseAgent:
class Id( val topic: String)
def build( urls: Urls): Future[Urls]
@agentImplementation()
final class DigestAgentImpl( topic: String) extends DigestAgent:
def build( urls: Urls): Future[Urls] =
urls.foldLeft( Future.successful( List.empty[ String])):
(out, url) =>
for
done <- out
summary <- SummarizeClient().summarize(url).map(_.orThrow)
yield done :+ summary @toolDefinition(
// it works on the agent's files when its binding allows it:
requiresFilesystem = true,
)
trait Outline:
def outline( file: String): Headings
@toolImplementation()
final class OutlineImpl extends Outline:
def outline( file: String): Headings =
fs.readFileSync(file, "utf8").split( '\n').toList
.filter(_.startsWith( "#"))
@agentDefinition()
trait BuildAgent extends BaseAgent:
class Id( val project: String)
def build() : Future[Headings]
@agentImplementation()
final class BuildAgentImpl( project: String) extends BuildAgent:
def build() : Future[Headings] =
for
// bash calls the bound git, npm, tsc and node tools:
run <- BashClient.newClient().run(
"git pull && npm install && tsc && node dist/build.js" ,
"/workspace/site", UInt( 600)).map(_.orThrow)
_ = if run.exitCode.value != 0 then sys.error(run.stderr)
file = "/workspace/site/dist/report.md"
headings <- OutlineClient().outline(file).map(_.orThrow)
yield headings def stripeRefund(
revealedKey: String, charge: String, amount: Int,
): Future[Response] =
// Golem adds an idempotency key unless you set one:
// Stripe honors that key, so a retry refunds once:
fetch( "https://api.stripe.com/v1/refunds" , new RequestInit {
method = HttpMethod. POST
headers = js. Dictionary(
"Authorization" -> ( "Bearer " + revealedKey))
body = URLSearchParams(js. Dictionary(
"charge" -> charge, "amount" -> amount.toString))
}).toFuture // define a grant for connections to the orders API only:
val ordersApi = PermissionsApi. PatternGrant(
className = "network", owner = "", recipient = "*",
verb = "connect", resourceId = "orders.example.com:443" ,
)
def shareOrders( target: AgentId): PermissionsApi. PersistentCard =
val bounds =
PermissionsApi. Bounds(lowerPositive = List(ordersApi))
val parent = PermissionsApi.deriveFromWallet(bounds).orThrow
// install a one-hour child; keep its parent for revocation:
val child = parent.derive(
bounds, Some( PermissionsApi. Lifespan.iso8601( "PT1H")),
).orThrow
child.install( PermissionsApi. Holder. Agent(target)).orThrow
parent
def revokeOrders( parent: PermissionsApi. PersistentCard): Unit =
// revoking it revokes the child and every card derived from it:
parent.revoke().orThrow // the agent receives an opaque handle, not the key:
final case class SupportConfig( stripeKey: Secret[String])
derives Schema
@agentDefinition()
trait SupportAgent extends BaseAgent
with AgentConfig[SupportConfig]:
class Id( val ticket: String)
def issueRefund( charge: String, amount: Int): Future[Refund]
@agentImplementation()
final class SupportAgentImpl(
ticket: String, config: Config[SupportConfig],
) extends SupportAgent:
def issueRefund( charge: String, amount: Int): Future[Refund] =
// refund needs a grant to reveal it; each reveal is recorded:
// a binding can let a tool forward handles but reveal none:
RefundToolClient().refund(
charge, amount, config.value.stripeKey.handle(),
).map(_.orThrow) import golem. bridge. client. crm_find_customer. CrmFindCustomerClient
@agentDefinition()
trait AccountAgent extends BaseAgent:
class Id( val team: String)
// over MCP, this method becomes a tool with this description:
@description( "Summarize a customer account by email")
def describe( email: String): Future[String]
@agentImplementation()
final class AccountAgentImpl( team: String) extends AccountAgent:
def describe( email: String): Future[String] =
// call the CRM's MCP tool through its generated typed client:
val call = CrmFindCustomerClient.newClient()
.crmFindCustomer(email).orThrow
call.collect().map : collected =>
// the MCP server's output schema types this result:
val found = collected.result.orThrow.structured
s"${found.name} is on the ${found.plan} plan" #derive.tool ( "summarize")
struct Summarize {}
// each call gets a fresh VM that can't read the agent's memory:
pub async fn Summarize::summarize(url : String) -> String {
// recovery reuses this completed fetch's response:
let page = read_text( @http. send( get(url)). unwrap())
summarize_text(page)
}
#derive.agent
struct DigestAgent {
topic : String
}
fn DigestAgent::new(topic : String) -> DigestAgent {
{ topic, }
}
pub async fn DigestAgent::build(
self : Self, urls : Array[String],
) -> Array[String] {
let out = []
for url in urls {
out. push( SummarizeClient::new(). summarize(url). unwrap())
}
out
} // it works on the agent's files when its binding allows it:
#derive.tool ( "outline", requires_filesystem=true)
struct Outline {}
pub async fn Outline::outline(file : String) -> Array[String] {
let root = @fs. get_root_dir(). unwrap()
let text = @fs. read_string(root, file). unwrap()
let lines = text. split( "\n"). map(line => line. to_owned())
lines. filter(line => line. has_prefix( "#")). collect()
}
#derive.agent
struct BuildAgent {
project : String
}
fn BuildAgent::new(project : String) -> BuildAgent {
{ project, }
}
pub async fn BuildAgent::build(self : Self) -> Array[String] {
// bash calls the bound git, npm, tsc and node tools:
let run = @bash. BashClient::new(). run(
"git pull && npm install && tsc && node dist/build.js" ,
"/workspace/site", 600,
). unwrap()
if run.exit_code != 0 { fail(run.stderr) }
let file = "/workspace/site/dist/report.md"
OutlineClient::new(). outline(file). unwrap()
} pub async fn stripe_refund(
revealed_key : String, charge : String, amount : Int,
) -> @http. Response {
// Golem adds an idempotency key unless you set one:
// Stripe honors that key, so a retry refunds once:
@http. send( form_post(
"https://api.stripe.com/v1/refunds" ,
[( "charge", charge), ( "amount", amount. to_string())],
headers=[( "Authorization", "Bearer " + revealed_key)],
)). unwrap()
} // define a grant for connections to the orders API only:
let orders_api : @permissions. PatternGrant = {
class: "network", owner: "", recipient: "*",
verb: "connect", resource_id: "orders.example.com:443" ,
}
pub fn share_orders(
target : @types. AgentId,
) -> @permissions. PersistentCard raise {
let bounds = @permissions. Bounds::new(lower_positive =[orders_api])
let parent = @permissions. derive_from_wallet(bounds)
// install a one-hour child; keep its parent for revocation:
let child = parent. derive_child(
bounds, lifespan=@permissions. Lifespan::iso8601( "PT1H"),
)
child. install( Agent(target))
parent
}
pub fn revoke_orders(
parent : @permissions. PersistentCard,
) -> Unit raise {
// revoking it revokes the child and every card derived from it:
ignore(parent. revoke())
} #derive.config
struct SupportConfig {
// the agent receives an opaque handle, not the key:
stripe_key : @config. Secret[String]
}
#derive.agent
struct SupportAgent {
ticket : String
config : SupportConfig
}
fn SupportAgent::new(
ticket : String, config : @config. Config[SupportConfig],
) -> SupportAgent {
{ ticket, config: config.value }
}
pub async fn SupportAgent::issue_refund(
self : Self, charge : String, amount : Int,
) -> RefundResult {
// refund needs a grant to reveal it; each reveal is recorded:
// a binding can let a tool forward handles but reveal none:
RefundClient::new(). refund(
charge, amount,
@secrets. SecretHandle::from_config(self.config.stripe_key),
). unwrap()
} #derive.agent
struct AccountAgent {
team : String
}
fn AccountAgent::new(team : String) -> AccountAgent {
{ team, }
}
// over MCP, this method becomes a tool with this description:
/// Summarize a customer account by email
pub async fn AccountAgent::describe(
self : Self, email : String,
) -> String {
// call the CRM's MCP tool through its generated typed client:
let call = @crm. CrmFindCustomerClient::new()
. crm_find_customer(email). unwrap()
let { result, .. } = call. collect()
guard result is Ok(found) else { fail( "CRM lookup failed") }
// the MCP server's output schema types this result:
let { name, plan } = found.structured
"\{name} is on the \{plan} plan"
} var Tool = tool. DefineTool[Summarizer]( "summarize", tool. Spec{})
var Summarize = Tool. Body[Args, string](
func( a *Args, s *tool. CommandSpec) { s. Positional( &a.URL) })
var _ = Summarize. Handle(summarize)
// each call gets a fresh VM that can't read the agent's memory:
func summarize( _ *tool. Context, a Args) ( string, error) {
// recovery reuses this completed fetch's response:
res := golem. Must(http. Get(a.URL))
defer res.Body. Close()
page := golem. Must(io. ReadAll(res.Body))
return summarizeText( string(page)), nil
}
var Digest = golem. DefineAgent[DigestID](
golem. Spec{Name: "DigestAgent"})
var Build = Digest. Method[BuildIn, []string]( "build")
var _ = Digest. Implement( func( DigestID) *digest {
return &digest{}
}). Handle(Build, golem. Adapt( func( _ *digest, in BuildIn) []string {
var out []string
for _, url := range in.URLs {
out = append(out, golem. Must(Summarize. Call( func( a *Args) {
a.URL = url
})))
}
return out
})) var Outline = tool. DefineTool[Outliner]( "outline", tool. Spec{
// it works on the agent's files when its binding allows it:
RequiresFilesystem: true,
}). Body[Args, Headings]( func( a *Args, s *tool. CommandSpec) {
s. Positional( &a.File)
})
var _ = Outline. Handle(
func( _ *tool. Context, a Args) ( Headings, error) {
text := string(golem. Must(os. ReadFile(a.File)))
headings := regexp. MustCompile( "(?m)^#.*" )
return headings. FindAllString(text, -1), nil
})
var Builder = golem. DefineAgent[BuildID](
golem. Spec{Name: "BuildAgent"})
var Build = Builder. Method[golem. Unit, Headings]( "build")
var _ = Builder. Implement( func( id BuildID) *BuildID {
return &id
}). Handle(Build, golem. Adapt0( func( *BuildID) Headings {
// bash calls the bound git, npm, tsc and node tools:
script := "git pull && npm install && tsc && node dist/build.js"
run := golem. Must(bash.Run. Call( func( a *bash. RunArgs) {
a.Script, a.Cwd, a.Timeout = script, "/workspace/site", 600
}))
if run.ExitCode != 0 {
panic(run.Stderr)
}
file := "/workspace/site/dist/report.md"
return golem. Must(Outline. Call( func( a *Args) { a.File = file }))
})) func StripeRefund(
revealedKey, charge string, amount int32,
) ( *http. Response, error) {
// Golem adds an idempotency key unless you set one:
// Stripe honors that key, so a retry refunds once:
form := url. Values{"charge": {charge},
"amount": {fmt. Sprint(amount)}}
req := golem. Must(http. NewRequest( "POST",
"https://api.stripe.com/v1/refunds" ,
strings. NewReader(form. Encode())))
req.Header. Set( "Authorization", "Bearer "+revealedKey)
req.Header. Set( "Content-Type",
"application/x-www-form-urlencoded")
return http.DefaultClient. Do(req)
} // define a grant for connections to the orders API only:
var ordersAPI = golem. PatternGrant{
Class: "network", Owner: "", Recipient: "*",
Verb: "connect", ResourceID: "orders.example.com:443" ,
}
func ShareOrders( target golem. AgentID) golem. PersistentCard {
bounds := golem. Bounds{
LowerPositive: []golem. PatternGrant{ordersAPI}}
parent := golem. MustDeriveFromWallet(
bounds, golem. None[golem. Lifespan]())
// install a one-hour child; keep its parent for revocation:
child := parent. MustDerive(
bounds, golem. Some(golem. LifespanOf(time.Hour)))
golem. Must0(child. Install(golem. AgentHolder(target)))
return parent
}
func RevokeOrders( parent golem. PersistentCard) {
// revoking it revokes the child and every card derived from it:
golem. Must(parent. Revoke())
} type SupportID struct{ Ticket string }
type SupportConfig struct {
// the agent receives an opaque handle, not the key:
StripeKey golem. Secret[string]
}
type RefundIn struct {
Charge string
Amount int32
}
type support struct{}
var Support = golem. DefineConfiguredAgent[SupportID, SupportConfig](
golem. Spec{Name: "SupportAgent"})
var IssueRefund = Support. Method[RefundIn, refund. Refund](
"issueRefund")
var _ = Support. Implement( func( SupportID) *support {
return &support{}
}). Handle(IssueRefund, func(
ctx *golem. Context[support], in RefundIn,
) refund. Refund {
// refund needs a grant to reveal it; each reveal is recorded:
// a binding can let a tool forward handles but reveal none:
return golem. Must(refund.Root. Call( func( a *refund. RootArgs) {
a.Charge, a.Amount = in.Charge, in.Amount
a.APIKey = ctx. Config(Support).StripeKey
}))
}) type AccountID struct{ Team string }
type DescribeIn struct{ Email string }
var Account = golem. DefineAgent[AccountID](
golem. Spec{Name: "AccountAgent"})
var Describe = Account. Method[DescribeIn, string]( "describe",
// over MCP, this method becomes a tool with this description:
golem. Description( "Summarize a customer account by email"))
var _ = Account. Implement( func( id AccountID) *AccountID {
return &id
}). Handle(Describe, golem. Adapt(( *AccountID).describe))
func ( *AccountID) describe( in DescribeIn) string {
// call the CRM's MCP tool through its generated typed client:
call := golem. Must(crm.Root. Call( func( a *crm. RootArgs) {
a.Email = in.Email
}))
out := call. Collect()
golem. Must0(out.Err)
// the MCP server's output schema types this result:
c := out.Result.Structured
return c.Name + " is on the " + c.Plan + " plan"
} 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' }
},
},
}) export const Brief = defineAgent({
name: "BriefAgent",
id: { client: Schema.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: Schema.String }, success: Schema.Void,
}),
},
}). implement({
init: ({ client }) => Effect. succeed({ client }),
methods: ({ client }) => ({
write: ({ notes }) => Effect. gen( function* () {
const res = yield* LanguageModel. generateText({
prompt: `Write an HTML brief for ${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.text)
}). pipe(Effect. provide(OpenAiLanguageModel. model( "gpt-4.1" )),
Effect. provide(openai), Effect.orDie),
}),
}) 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: Schema.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: Schema.String },
success: Schema. Array(Schema.String),
// the page's Add button posts to this endpoint:
http: [Http. post( "/cards")],
}) },
}). implement({
init: () => Effect. sync(() => {
fs. writeFileSync( "/index.html" , boardPage)
return { cards: [] as string[] }
}),
methods: ({ cards }) => ({
add: ({ title }) => Effect. sync(() => {
cards. push(title)
return cards
}),
}),
}) HttpRouter. define( "ExportsRouter", {
mount: Http. mount( "/exports"),
}). implement(Effect. succeed(Effect. gen( function* () {
const request = yield* HttpRouter.request
const id = request.path. match( /^\/exports\/( [\w-]+)\. csv$/)?.[ 1]
if (request.method !== "GET" || !id) {
return HttpServerResponse. empty({ status: 404 })
}
// stream the CSV from the agent that builds this export:
const exp = yield* Export.client. get({ id })
const rows = yield* exp. csv({})
return HttpServerResponse. stream(rows, {
status: 200,
contentType: "text/csv",
headers: {
// browsers save it as a file instead of opening it:
"content-disposition":
`attachment; filename="${id}.csv"` ,
},
})
}))) const Pdf = Unstructured. UnstructuredBinary(
{ restrictions: [{ mimeType: "application/pdf" }] })
export const Payslips = defineAgent({
name: "PayslipAgent",
id: { employee: Schema.String },
http: Http. mount( "/payslips/{employee}", {
// Golem checks the sign-in token before any agent code runs:
auth: true,
}),
methods: {
latest: method({
input: { caller: Principal.PrincipalSchema },
success: Unstructured. Option(Pdf),
http: [Http. get( "/latest.pdf" )],
}),
},
}). implement({
init: ({ employee }) => Effect. succeed({ employee }),
methods: ({ employee }) => ({
latest: ({ caller }) => Effect. sync(() => {
// the agent decides who may open the payslip:
if (caller.tag !== "oidc" || caller.val.sub !== employee) {
return Option. none()
}
const val = fs. readFileSync( "/payslips/latest.pdf" )
return Option. some({ _tag: "inline", val,
mimeType: "application/pdf" } as const)
}),
}),
}) #[agent_definition(mount = "/briefs/{client}",
// Golem serves this agent's own file at /briefs/{client}:
filesystem_bindings = [( "/", "/out/index.html" )])]
pub trait BriefAgent {
fn new(client : String) -> Self;
async fn write( &mut self, notes: String) -> Result<(), AppError>;
}
struct BriefAgentImpl { client: String }
#[agent_implementation]
impl BriefAgent for BriefAgentImpl {
fn new(client : String) -> Self { Self { client } }
async fn write( &mut self, notes: String) -> Result<(), AppError> {
let page = text( OpenAI::send(
OpenAiConfig::from_env() ?,
vec![user( &format!(
"Write an HTML brief for {}:\n{notes}", self. client))],
model( "gpt-4.1" ),
).await )?;
fs::create_dir_all( "/out")?;
// it writes the page into its own files:
// a read waits for this call, so it never gets half a page:
fs::write( "/out/index.html" , page)?;
Ok(())
}
} const BOARD_PAGE: &str = r#"<button id="add">Add</button>
<script>add.onclick = () => fetch('cards', {
method: 'POST', body: JSON.stringify({ title: 'New card' }),
})</script>"#;
#[agent_definition(
// the URL names the agent: one BoardAgent per team:
mount = "/boards/{team}",
filesystem_bindings = [( "/app", "/index.html" )])]
pub trait BoardAgent {
fn new(team : String) -> Self;
// the page's Add button posts to this endpoint:
#[endpoint(post = "/cards")]
fn add( &mut self, title: String) -> Vec<String>;
}
struct BoardAgentImpl { cards: Vec<String> }
#[agent_implementation]
impl BoardAgent for BoardAgentImpl {
fn new(_team : String) -> Self {
fs::write( "/index.html" , BOARD_PAGE)
. expect( "cannot write the page");
Self { cards: vec![] }
}
fn add( &mut self, title: String) -> Vec<String> {
self. cards. push(title);
self. cards. clone()
}
} struct ExportsRouter;
#[http_router(name = "ExportsRouter", mount = "/exports")]
impl HttpRouter for ExportsRouter {
type Config = ();
fn new(_ : Config<()>) -> Self { Self }
async fn handle( &self, request: HttpRequest) -> HttpResponse {
let id = request. path. strip_prefix( "/exports/")
. and_then( |rest| rest. strip_suffix( ".csv" ))
. filter( |id| !id. is_empty() && id. chars() . all(is_word));
let Some(id) = id. filter( |_| request. method == "GET") else {
let body = AgentStream::new() . 1;
return HttpResponse { status: 404, headers: vec![], body };
};
// stream the CSV from the agent that builds this export:
let rows = ExportAgentClient::get(id . to_string()) . csv() .await ;
HttpResponse {
status: 200,
headers: vec![
Header { name: "content-type". into(),
value: b"text/csv". to_vec() },
// browsers save it as a file instead of opening it:
Header { name: "content-disposition". into(), value :
format!( "attachment; filename=\"{id}.csv \"")
. into_bytes() },
],
body: rows,
}
}
} #[derive( AllowedMimeTypes, Clone, Debug)]
pub enum PdfType { #[mime_type( "application/pdf")] Pdf }
type Pdf = UnstructuredBinary<PdfType>;
#[agent_definition(mount = "/payslips/{employee}",
// Golem checks the sign-in token before any agent code runs:
auth = true)]
pub trait PayslipAgent {
fn new(employee : String) -> Self;
#[endpoint(get = "/latest.pdf" )]
fn latest( &self, caller: Principal)
-> Result<Option<Pdf>, AppError>;
}
struct PayslipAgentImpl { employee: String }
#[agent_implementation]
impl PayslipAgent for PayslipAgentImpl {
fn new(employee : String) -> Self { Self { employee } }
fn latest( &self, caller: Principal)
-> Result<Option<Pdf>, AppError> {
// the agent decides who may open the payslip:
let allowed = matches!( &caller,
Principal::Oidc(user) if user. sub == self. employee);
if !allowed { return Ok( None); }
let data = fs::read( "/payslips/latest.pdf" )?;
Ok( Some( Pdf::Inline { data, mime_type: PdfType::Pdf }))
}
} @agentDefinition(
mount = "/briefs/{client}",
// Golem serves this agent's own file at /briefs/{client}:
exposeFiles = Array(( "/", "/out/index.html" )),
)
trait BriefAgent extends BaseAgent:
class Id( val client: String)
def write( notes: String): Future[Unit]
@agentImplementation()
final class BriefAgentImpl( client: String) extends BriefAgent:
def write( notes: String): Future[Unit] =
openai.responses.create( ResponseParams(
model = "gpt-4.1" ,
input = s"Write an HTML brief for $client:\n$notes",
)).toFuture.map : res =>
fs.mkdirSync( "/out", literal(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) val boardPage = """<button id="add">Add</button>
<script>add.onclick = () => fetch('cards', {
method: 'POST', body: JSON.stringify({ title: 'New card' }),
})</script>"""
@agentDefinition(
// the URL names the agent: one BoardAgent per team:
mount = "/boards/{team}",
exposeFiles = Array(( "/app", "/index.html" )),
)
trait BoardAgent extends BaseAgent:
class Id( val team: String)
// the page's Add button posts to this endpoint:
@endpoint( "POST", "/cards")
def add( title: String): List[String]
@agentImplementation()
final class BoardAgentImpl( team: String) extends BoardAgent:
private val cards = ArrayBuffer.empty[ String]
fs.writeFileSync( "/index.html" , boardPage)
def add( title: String): List[String] =
cards += title
cards.toList def bytes( text: String): Array[Byte] = text.getBytes( "UTF-8")
@httpRouter( "ExportsRouter", "/exports")
trait ExportsRouter extends BaseAgent:
@httpHandler def serve( request: HttpRequest): Future[HttpResponse]
@agentImplementation()
final class ExportsRouterImpl() extends ExportsRouter:
private val Csv = """/exports/([\w-]+)\.csv""" .r
def serve( request: HttpRequest): Future[HttpResponse] =
(request.method, request.path) match
case ( "GET", Csv(id)) =>
// stream the CSV from the agent that builds this export:
ExportAgentClient.get(id).csv().map : rows =>
HttpResponse( UShort( 200), List(
HttpHeader( "content-type", bytes( "text/csv")),
// browsers save it as a file instead of opening it:
HttpHeader( "content-disposition", bytes(
s"""attachment; filename="$id.csv"""" )),
), rows)
case _ =>
val body = AgentStream.fromStream( Stream.empty)
Future.successful( HttpResponse( UShort( 404), Nil, body)) type Pdf = UnstructuredBinary["application/pdf"]
@agentDefinition(
mount = "/payslips/{employee}",
// Golem checks the sign-in token before any agent code runs:
auth = true,
)
trait PayslipAgent extends BaseAgent:
class Id( val employee: String)
@endpoint( "GET", "/latest.pdf" )
def latest( caller: Principal): Option[Pdf]
@agentImplementation()
final class PayslipAgentImpl( employee: String) extends PayslipAgent:
def latest( caller: Principal): Option[Pdf] =
// the agent decides who may open the payslip:
caller match
case login: Principal. Oidc if login.sub == employee =>
val pdf = fs.readFileSync( "/payslips/latest.pdf" )
val data = pdf.toArray.map(_.toByte)
Some( UnstructuredBinary.fromInline(data, "application/pdf"))
case _ => None #derive.agent
#derive.mount ( "/briefs/{client}")
// Golem serves this agent's own file at /briefs/{client}:
#derive.expose_files ( "/", "/out/index.html" )
struct BriefAgent {
client : String
}
fn BriefAgent::new(client : String) -> BriefAgent {
{ client, }
}
pub async fn BriefAgent::write(
self : Self, notes : String,
) -> Unit {
let html = @llm. generate_text(
model=@llm. openai( "gpt-4.1" ),
prompt="Write an HTML brief for \{self.client }:\n\{notes}",
)
let root = @fs. get_root_dir(). unwrap()
root. create_directory_at( "out") |> ignore
// it writes the page into its own files:
// a read waits for this call, so it never gets half a page:
@fs. write_string(root, "/out/index.html" , html). unwrap()
} let board_page : String =
#|<button id="add">Add</button>
#|<script>add.onclick = () => fetch('cards', {
#| method: 'POST', body: JSON.stringify({ title: 'New card' }),
#|})</script>
#derive.agent
// the URL names the agent: one BoardAgent per team:
#derive.mount ( "/boards/{team}")
#derive.expose_files ( "/app", "/index.html" )
struct BoardAgent {
team : String
cards : Array[String]
}
async fn BoardAgent::new(team : String) -> BoardAgent {
let root = @fs. get_root_dir(). unwrap()
@fs. write_string(root, "/index.html" , board_page). unwrap()
{ team, cards: [] }
}
// the page's Add button posts to this endpoint:
#derive.endpoint ( post="/cards")
pub fn BoardAgent::add(
self : Self, title : String,
) -> Array[String] {
self.cards. push(title)
self.cards
} fn bytes(s : String) -> Array[Byte] { @utf8. encode(s). to_array() }
#derive.http_router
#derive.mount ( "/exports")
struct ExportsRouter {}
fn ExportsRouter::new() -> ExportsRouter { ExportsRouter::{ } }
#derive.http_handler
pub async fn ExportsRouter::serve(
_self : Self, request : @http. HttpRequest,
) -> @http. HttpResponse {
guard request.method == "GET" &&
request.path is [.. "/exports/", .. id, .. ".csv" ] &&
is_word(id) else {
let body = @schema. AgentStream::produce( async fn(_) { })
return { status: 404, headers: [], body }
}
// stream the CSV from the agent that builds this export:
let rows = ExportAgentClient::get(id. to_owned()). csv()
{
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,
}
} struct Pdf {}
impl @schema. AllowedMimeTypes for Pdf with fn mime_types() {
["application/pdf"]
}
#derive.agent
#derive.mount ( "/payslips/{employee}")
// Golem checks the sign-in token before any agent code runs:
#derive.mount_auth ( true)
struct PayslipAgent {
employee : String
}
fn PayslipAgent::new(employee : String) -> PayslipAgent {
{ employee, }
}
#derive.endpoint ( get="/latest.pdf" )
pub async fn PayslipAgent::latest(
self : Self, caller : @common. Principal,
) -> @schema. UnstructuredBinary[Pdf]? {
// the agent decides who may open the payslip:
guard caller is Oidc({ sub, .. }) && sub == self.employee else {
return None
}
let root = @fs. get_root_dir(). unwrap()
let pdf = @fs. read_bytes(root, "/payslips/latest.pdf" ). unwrap()
let bytes = Bytes::from_fixedarray(pdf)
Some( Inline(bytes~, mime_type =Some( "application/pdf")))
} type BriefID struct{ Client string }
type WriteIn struct{ Notes string }
var Brief = golem. DefineAgent[BriefID]( golem. Spec{
Name: "BriefAgent",
HTTP: &golem. Mount{Path: "/briefs/{client}",
// Golem serves this agent's own file at /briefs/{client}:
ExposeFiles: []golem. FileMapping{
{Route: "/", Path: "/out/index.html" }}},
})
var Write = Brief. Method[WriteIn, golem. Unit]( "write")
var _ = Brief. Implement( func( id BriefID) *BriefID {
return &id
}). Handle(Write, golem. AdaptUnit( func( b *BriefID, in WriteIn) {
res := golem. Must(ai.Responses. New(ctx,
responses. ResponseNewParams{Model: "gpt-4.1" ,
Input: responses. ResponseNewParamsInputUnion{
OfString: openai. String( "Write an HTML brief for " +
b.Client + ":\n" + in.Notes)}}))
golem. Must0(os. MkdirAll( "/out", 0o755))
// it writes the page into its own files:
// a read waits for this call, so it never gets half a page:
golem. Must0(os. WriteFile( "/out/index.html" ,
[]byte(res. OutputText()), 0o644))
})) const boardPage = `<button id="add">Add</button>
<script>add.onclick = () => fetch('cards', {
method: 'POST', body: JSON.stringify({ title: 'New card' }),
})</script>`
type BoardID struct{ Team string }
type AddIn struct{ Title string }
type board struct{ cards []string }
var Board = golem. DefineAgent[BoardID]( golem. Spec{
Name: "BoardAgent",
// the URL names the agent: one BoardAgent per team:
HTTP: &golem. Mount{Path: "/boards/{team}",
ExposeFiles: []golem. FileMapping{
{Route: "/app", Path: "/index.html" }}},
})
var Add = Board. Method[AddIn, []string]( "add",
// the page's Add button posts to this endpoint:
golem. HTTP(golem. POST( "/cards")))
var _ = Board. Implement( func( BoardID) *board {
golem. Must0(os. WriteFile( "/index.html" ,
[]byte(boardPage), 0o644))
return &board{}
}). Handle(Add, golem. Adapt( func( b *board, in AddIn) []string {
b.cards = append(b.cards, in.Title)
return b.cards
})) var Exports = golem. DefineHTTPRouter( golem. RouterSpec{
Name: "ExportsRouter", Mount: "/exports"})
var csvPath = regexp. MustCompile( `^/exports/([\w-]+)\.csv$` )
var _ = Exports. HandleRaw( func( _ context. Context,
req golem. HTTPRequest) golem. HTTPResponse {
match := csvPath. FindStringSubmatch(req.Path)
if req.Method != "GET" || match == nil {
body := golem. StreamOf[[]byte]()
return golem. HTTPResponse{Status: 404,
Headers: []golem. HTTPHeader{}, Body: body}
}
id := match[1]
// stream the CSV from the agent that builds this export:
export := Export. Get( ExportID{ID: id})
rows := CSV. MustCall(export, golem. Unit{})
return golem. HTTPResponse{
Status: 200,
Headers: []golem. HTTPHeader{
{Name: "content-type", Value: []byte( "text/csv")},
// browsers save it as a file instead of opening it:
{Name: "content-disposition", Value: []byte(
`attachment; filename="` + id + `.csv"` )}},
Body: rows,
}
}) type pdfType struct{}
func ( pdfType) MimeTypes() [] string {
return []string{"application/pdf"}
}
type Pdf = golem. UnstructuredBinary[pdfType]
type PayslipID struct{ Employee string }
type LatestIn struct{ Caller golem. Principal }
var mount = &golem. Mount{Path: "/payslips/{employee}",
// Golem checks the sign-in token before any agent code runs:
Auth: true}
var Payslips = golem. DefineAgent[PayslipID](
golem. Spec{Name: "PayslipAgent", HTTP: mount})
var Latest = Payslips. Method[LatestIn, *Pdf]( "latest",
golem. HTTP(golem. GET( "/latest.pdf" )))
var _ = Payslips. Implement( func( id PayslipID) *PayslipID {
return &id
}). Handle(Latest, golem. Adapt(( *PayslipID).latest))
func ( p *PayslipID) latest( in LatestIn) *Pdf {
// the agent decides who may open the payslip:
c, ok := in.Caller.( golem. OidcPrincipal)
if !ok || c.Sub != p.Employee {
return nil
}
data := golem. Must(os. ReadFile( "/payslips/latest.pdf" ))
return &Pdf{Data: data, MimeType: "application/pdf"}
} 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.
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.
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.
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.
Yes. Golem uses the BUSL-1.1 license, which converts to Apache-2.0 after four years. Self-hosting for production workloads is permitted.
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.
Install one binary and run the platform locally.