File I/O in Scala Golem Agents

SkillFiles & storage

Reading and writing files from a Scala Golem agent. Use when the user asks to read files, write files, or do filesystem operations from agent code in Scala.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the File I/O in Scala Golem Agents skill

What this skill tells your AI

The instructions your AI receives, as published by golemcloud/golem in golem-skills/skills/scala/golem-file-io-scala/SKILL.md and read by ahel’s review.

Overview

Golem Scala agents are compiled to JavaScript via Scala.js and run in a QuickJS-based WASM runtime. The runtime provides node:fs for filesystem operations, accessible via Scala.js JavaScript interop. Standard JVM file I/O (java.io.File, java.nio.file.*) is not available.

To provision files into an agent's filesystem, load the golem-add-initial-files skill. To understand the full runtime environment, load the golem-js-runtime skill.

Setting Up the node:fs Facade

Define a Scala.js facade object for the node:fs module:

import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport

@js.native
@JSImport("node:fs", JSImport.Namespace)
private object Fs extends js.Object {
  def readFileSync(path: String, encoding: String): String = js.native
  def readFileSync(path: String): js.typedarray.Uint8Array = js.native
  def writeFileSync(path: String, data: String): Unit = js.native
  def existsSync(path: String): Boolean = js.native
  def readdirSync(path: String): js.Array[String] = js.native
  def appendFileSync(path: String, data: String): Unit = js.native
  def mkdirSync(path: String, options: js.Object): Unit = js.native
}

Important: WASI modules like node:fs are not available during the build-time pre-initialization (wizer) phase — they are only available at runtime. Use lazy val to defer initialization:

// ✅ CORRECT — lazy val defers import to first runtime use
private lazy val fs: Fs.type = Fs

// ❌ WRONG — top-level val triggers import during pre-initialization and fails
private val fs: Fs.type = Fs

Reading Files

Text Files

val content: String = Fs.readFileSync("/data/config.json", "utf-8")

Binary Files

val bytes: js.typedarray.Uint8Array = Fs.readFileSync("/data/image.png")

Writing Files

Only files provisioned with read-write permission (or files in non-provisioned paths) can be written to.

Fs.writeFileSync("/tmp/output.txt", "Hello, world!")

Checking File Existence

if (Fs.existsSync("/data/config.json")) {
  val content = Fs.readFileSync("/data/config.json", "utf-8")
}

Listing Directories

val files: js.Array[String] = Fs.readdirSync("/data")
files.foreach(println)

Complete Agent Example

import golem.runtime.annotations.{agentDefinition, agentImplementation}
import golem.BaseAgent
import scala.scalajs.js
import scala.scalajs.js.annotation.JSImport
import scala.concurrent.Future

@js.native
@JSImport("node:fs", JSImport.Namespace)
private object Fs extends js.Object {
  def readFileSync(path: String, encoding: String): String = js.native
  def appendFileSync(path: String, data: String): Unit = js.native
}

@agentDefinition()
trait FileReaderAgent extends BaseAgent {
  class Id(val name: String)
  def readGreeting(): Future[String]
  def writeLog(message: String): Future[Unit]
}

@agentImplementation()
final class FileReaderAgentImpl(private val name: String) extends FileReaderAgent {

  override def readGreeting(): Future[String] = Future.successful {
    Fs.readFileSync("/data/greeting.txt", "utf-8").trim
  }

  override def writeLog(message: String): Future[Unit] = Future.successful {
    Fs.appendFileSync("/tmp/agent.log", message + "\n")
  }
}

Key Constraints

  • Use node:fs via @JSImport — standard JVM file I/O (java.io.File, java.nio.file.*) does not work in Scala.js
  • Use lazy val or defer node:fs access to method bodies to avoid pre-initialization failures
  • Files provisioned via golem-add-initial-files with read-only permission cannot be written to
  • The filesystem is per-agent-instance — each agent has its own isolated filesystem
  • File changes within an agent are persistent across invocations (durable state)

Signals

GitHub stars
2k
Forks
210
Last commit
Sep 2026
Advanced
Item type
skill
Key
golem-file-io-scala
Source
github.com/golemcloud/golem