-
Notifications
You must be signed in to change notification settings - Fork 115
Golem Support for Scala.js #554
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| // Internal hooks used by macro expansions | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| def registerPlan[Trait]( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly the term “Plan” here mean conceptually? Just because I didn’t use the term plan anywhere in TS or Rust.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't like the naming we can change it. "Plan" exists because the Scala runtime needs a single explicit value (AgentImplementationPlan) that captures an agent’s metadata + method routing + codecs + handlers so it can register and execute the agent uniformly at runtime.
| import java.util.concurrent.TimeoutException | ||
| import scala.sys.process._ | ||
|
|
||
| object GolemPlugin extends AutoPlugin { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Both this sbt plugin, and the mill one seem to do a lot of work to wrap golem-cli as commands and build steps, so sbt or mill is the outer build tool driven the packaging and deployment of a golem app. We should not do this, the CLI is not intended to be used like this - Golem's app manifest itself contains build steps and language specific profiles for these, and the Scala integration would be a new golem-cli template invoking sbt (or mill) under the hood to compile the Scala code to a single final JS file (just like the TS template is using rollup to do that).
There are many features in golem-cli which all assumes that it is the "driver" and not the other way around.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the JS example I've sent to you I commented this in the golem.yaml: https://github.com/vigoo/js-counter-agent/blob/master/golem.yaml#L5-L12
The examples in this repo should all have a golem.yaml having a scala build template with a build step compiling Scala to JS, and the other steps for wrapping it in a Golem WASM untouched. Once this library is published we will put this build template in golem-cli
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be resolved.
golem/README.md
Outdated
|
|
||
| 1. **`golem-cli`** installed and on your `PATH`: | ||
| ```bash | ||
| curl -sSf https://join.golem.network/as-requestor | bash - |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is golem.network?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Golem Network is not our thing, this must have been generated here by some AI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The real one: https://learn.golem.cloud/cli#installation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, that's my bad. I had originally done some AI generation for the initial boilerplate and this never got removed when I cleaned up. I'll get it cut.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be fixed and I've updated all the docs to properly represent the latest code.
| */ | ||
| val golemExports = | ||
| settingKey[Seq[GolemExport]]( | ||
| "List of exported agents (Scala-only); used to auto-generate a hidden BridgeSpec manifest." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand what the build plugins are doing. They seem to analyse the compiled byte code to have their own concept of exported agents and then generate TypeScript code and embed that generated TypeScript code together with the Scala.js output into a TypeScript Golem app?
This is absolutely not how it should work. The TypeScript SDK was just provided as an example of what to do but in Scala. The compilation should result in a single JS file that have the proper exports (as shown in the hand-written example https://github.com/vigoo/js-counter-agent/blob/master/main.js) - and these exports (invocation, agent metadata etc. should be implemented by the SDK based on the Scala macros that somehow register the annotated classes as agents.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The plugins have been completely removed as they're no longer necessary.
|
Before digging into reviewing the details, I would like to discuss the architecture because I think there are fundamental differences between how we planned Scala.js integration into Golem and what the PR does. I tried to point out a few things regarding this in comments above, but it is hard to understand the whole picture; Could you write me a summary of how the current proposed SDK works (under the hood - the user examples look nice). I would like to understand all the steps involved from annotating a class - what the macros does and why do the build plugins do additional steps, and so on. Having that high level overview would help to figure out how to align this with what we need. |
| * `@agentDefinition("...")` on the trait. | ||
| */ | ||
| def register[Trait](constructor: => Trait): AgentDefinition[Trait] = | ||
| macro AgentImplementationMacroFacade.registerFromAnnotationImpl[Trait] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If registerFromAnnotationImpl is all about registering using a custom agent type name then probably lets rename to registerImplCustomAgentTypeName or something that reflects that idea.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
|
|
||
| import scala.concurrent.Future | ||
|
|
||
| final case class AgentImplementationPlan[Instance]( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So does this inherently represent singleton agent instances? That there are no constructors, or in other words agent input is some unit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is a no-args durable instance if that's what you mean?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FYI: We call these instances that don't take any constructor arguments as "singleton agents".
Why?: With zero constructor arguments, there is no way there can be more than 1 instance of this agent unless they are created through "phantom" functions (note, each instance of this agent has distinct agent-ids, which is composed of constructor argument values along with agent-type name.
|
|
||
| /** | ||
| * Agent implementation plan where the agent instance depends on constructor | ||
| * input. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think, if possible, we should simplify this idea. If the constructors don't exist, it's just empty. Having to separately handle them using different types feels like unnecessary.
In other words, AgentImplementationPlan should never be a separate type. It is already covered in AgentImplementationPlanWithCtor. And thereby rename AgentImplementationPlanWithCtor[Instance, Ctor] to just AgentImplementationPlan[Instance, Ctor]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good thought. Implemented.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @darkfrog26
| ) | ||
|
|
||
| object ClientMethodPlan { | ||
| type Aux[Trait, In, Out] = ClientMethodPlan[Trait, In, Out] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we sure this Aux is required ? Trait, in and Out are already input type parameter and not type members.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good suggestion. Removed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
| * This exists purely for Scala ergonomics: user agent traits can extend | ||
| * `BaseAgent` without importing any packaging/runtime-specific concepts. | ||
| */ | ||
| trait BaseAgent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
May be in scala, this workflow won't work and that's why you may have chose this approach and holding the above functionalities somewhere else. Could you confirm it?
In other SDKs, BaseAgent is quite an important base trait holding mainly three functions along with a few others. Those 3 important functionalities are
- Get the AgentID
- The dynamic method router - given a method name, and arguments as
data-value, delegate it to the real static implementation and return a data value - Get the agent-type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I'm deliberately not modeling those three functions because they live in the guest exports + registry/definition/binding runtime instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for patiently addressing most of the comments. Really appreciate it.
However, with this PR comment, I was personally looking forward to a solution. I kind of get your explanation but not fully.
My point was when an agent trait extends BaseAgent, "registry/definition/binding" is essentially ensuring they implement these functionalities in BaseAgent. Regardless of the language, I would wish BaseAgent has everything other than the actual agent-methods, an Agent can do. Example: getId. This is not something user write in their agent trait, but they get it. When they annotate their agent-implementation, this getId will now have an implementation. otherwise, it won't compile. Now you can see the maintainaibility of this SDK that we developers can easily extend things in BaseAgent and work backwards to satisfy the compiler (when it comes to Rust and Scala, and to some extent TS too)
For example: It was easy for @vigoo to add load_snapshot and save_snapshot (like here) into the BaseAgentand work backwards (atleast for the most part, and then he changed things in macros). I am not saying that's how everyone should do, but it will be really easy for both maintainers and users and see what's going on. In this case, users see they are extending BaseAgent but, they don't see anything in it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've updated the code so agentId, agentType, and agentName are all methods in BaseAgent now that simply invoke BaseAgentPlatform. Does that resolve this concern?
| .collectFirst { | ||
| case p if p.metadata.name == $methodName => | ||
| p.asInstanceOf[_root_.cloud.golem.runtime.plan.ClientMethodPlan.Aux[$traitTpe, $inTpe, $outTpe]] | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you confirm this casting is sound? With traitTpe , Input and Output?
There are no runtime witnesses. scala/jvm erases it. So probably the reasoning here would be somehow just satisfy the compiler, but it also looks odd, that we ended up having to do this. An explanation would suffice, not asking to immediately change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, this is just to satisfy the compiler, but the signature has changed somewhat with the removal of Aux.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see.
| * Registers an agent implementation using the agent type name from | ||
| * `@agentDefinition("...")` on the trait. | ||
| */ | ||
| inline def register[Trait](inline constructor: => Trait): AgentDefinition[Trait] = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's rename to inline build: => Trait instead of inline constructor : => Trait
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
| * `@mode(DurabilityMode.Durable)`) or defaults to Durable when not specified. | ||
| */ | ||
| inline def register[Trait <: AnyRef { type AgentInput }, Ctor](inline build: Ctor => Trait): AgentDefinition[Trait] = | ||
| registerWithCtorInternal[Trait, Ctor](AgentNameMacro.typeName[Trait])(build) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of understand the reasoning behind branches of with constructor and without constructor in the internals. Probably to allow user to avoid writing type AgentInput = ... I have mixed feelings about this, because singleton types are not quite common in golem.
Definitely not a bad idea though.
So I am ok type AgentInput =... is an optional thing at the user level, however, we should try avoiding (if we haven't already) separate separate logic/code-path-ways (such as two different types mention in another PR comment of mine, two different registration function like the ones here) in the internals.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should also be resolved with the previous merging of AgentImplementation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice
|
|
||
| object ShardModule { | ||
| val definition: AgentDefinition[Shard] = | ||
| AgentImplementation.register[Shard, (String, Int)](in => new ShardImpl(in)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I hope user never need to manually register the agent implementation in the code. :)
| * Generates a TypeScript bridge (`src/main.ts`) compatible with the `component new ts` shape, but driven by a | ||
| * user-provided {@link BridgeSpec} (no hard-coded example agents). | ||
| */ | ||
| public final class TypeScriptBridgeGenerator { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not sure about this bridge generator. Trying to understand why you ended up having it :)
I think this goes back to what @vigoo already commented here: #554 (comment)
I think, let's resolve this confusion first before you address rest of the code level reviews. I will continue to review code level afterwards
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is entirely gone. :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice 👍 because this was the most confusing part for me.
…th ByteArrayAccess)
| // | ||
| // golem-cli's inject step provides the real JS bundle via the imported | ||
| // `get-script` function, exposed to QuickJS as a virtual module named | ||
| // `@composition`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we are getting close :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds initial Scala.js support for ZIO-Golem, including a new data model/schema layer, macro-powered agent/client generation (Scala 2 + 3), JVM CLI-backed client utilities for local testing, and repo examples/docs/scripts to exercise the workflow end-to-end.
Changes:
- Introduced core model types for schemas/values plus helper utilities (structured + multimodal payloads).
- Added Scala 2/3 macro implementations for agent type-name derivation, SDK metadata, and RPC helpers.
- Added Scala.js runtime wiring (guest exports, host codecs, snapshot exports) plus examples, scripts, and tests.
Reviewed changes
Copilot reviewed 158 out of 207 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| golem/model/src/main/scala/golem/data/SchemaHelpers.scala | Adds schema/value unwrap helpers for single-element tuple payloads. |
| golem/model/src/main/scala/golem/data/ElementSchema.scala | Defines element schema/value ADTs including unstructured text/binary forms. |
| golem/model/src/main/scala/golem/data/DataModel.scala | Introduces a small portable DataType/DataValue algebra used by codecs. |
| golem/model/src/main/scala/golem/Uuid.scala | Adds a cross-platform UUID value type with Schema derivation. |
| golem/model/src/main/scala/golem/Datetime.scala | Adds a platform-neutral timestamp for scheduled invocations. |
| golem/model/src/main/scala/golem/AgentCompanionBase.scala | Adds a macro-friendly base type for agent companion objects. |
| golem/model/src/main/scala/golem/AgentApi.scala | Adds a macro-derivable “agent API” metadata + reflected type interface. |
| golem/mill/build.sc | Adds a Mill plugin build for source scanning/auto-registration. |
| golem/mill/README.md | Documents Mill plugin purpose/status. |
| golem/macros/src/main/scala-3/golem/runtime/macros/AllowedDerivationMacros.scala | Scala 3 derivation macros for allowed languages/MIME types. |
| golem/macros/src/main/scala-3/golem/runtime/macros/AgentSdkMacro.scala | Scala 3 macro generating AgentApi with derived typeName/constructor. |
| golem/macros/src/main/scala-3/golem/runtime/macros/AgentNameMacro.scala | Scala 3 macro to derive agent typeName from annotation/trait name. |
| golem/macros/src/main/scala-3/golem/runtime/macros/AgentMacros.scala | Scala 3 entrypoint for agent metadata generation macro. |
| golem/macros/src/main/scala-2/golem/runtime/macros/RemoteAgentOpsMacro.scala | Scala 2 macro to generate rpc call/trigger/schedule helpers. |
| golem/macros/src/main/scala-2/golem/runtime/macros/AllowedDerivationMacros.scala | Scala 2 derivation macros for allowed languages/MIME types. |
| golem/macros/src/main/scala-2/golem/runtime/macros/AgentSdkMacro.scala | Scala 2 macro generating AgentApi (typeName + AgentType). |
| golem/macros/src/main/scala-2/golem/runtime/macros/AgentNameMacro.scala | Scala 2 macro to derive agent typeName with Scala 2 annotation caveats. |
| golem/macros/src/main/scala-2/golem/runtime/macros/AgentMacros.scala | Scala 2 agent metadata macro implementation. |
| golem/gettingStarted/scala/src/main/scala/demo/CounterAgentImpl.scala | Adds quickstart Counter agent Scala implementation. |
| golem/gettingStarted/scala/src/main/scala/demo/CounterAgent.scala | Adds quickstart Counter agent trait + companion. |
| golem/gettingStarted/scala/project/plugins.sbt | Adds sbt plugins for Scala.js + zio-golem SBT plugin. |
| golem/gettingStarted/scala/project/build.properties | Adds sbt version pin for getting-started project. |
| golem/gettingStarted/scala/build.sbt | Adds Scala.js build definition for quickstart project. |
| golem/gettingStarted/run.sh | Adds a helper script to build/deploy/invoke the quickstart app. |
| golem/gettingStarted/repl-counter.rib | Adds a RIB script used by getting-started run.sh. |
| golem/gettingStarted/golem.yaml | Adds a golem app manifest for the getting-started app. |
| golem/gettingStarted/components-js/scala-demo/golem.yaml | Adds component manifest for the Scala.js demo component. |
| golem/gettingStarted/common-scala-js/golem.yaml | Adds a shared Scala.js component template pipeline. |
| golem/gettingStarted/build-scalajs.sh | Adds build helper to produce a Scala.js bundle for golem-cli. |
| golem/examples/websearch-summary-local-repl.sh | Adds runnable local repl script for websearch summary example. |
| golem/examples/snapshot-counter-local-repl.sh | Adds runnable local repl script for snapshot counter example. |
| golem/examples/shared/src/main/scala/golem/examples/templates/SnapshotCounterExample.scala | Adds example agent trait for snapshot counter template. |
| golem/examples/shared/src/main/scala/golem/examples/templates/JsonTasksExample.scala | Adds example agents/types for JSON tasks template. |
| golem/examples/shared/src/main/scala/golem/examples/templates/HumanInTheLoopExample.scala | Adds example agents for HITL workflow template. |
| golem/examples/shared/src/main/scala/golem/examples/templates/CounterExample.scala | Adds example agent trait for counter template. |
| golem/examples/shared/src/main/scala/golem/examples/templates/AiExamples.scala | Adds example agents for LLM chat + research (websearch) templates. |
| golem/examples/shared/src/main/scala/golem/examples/minimal/ShardExample.scala | Adds minimal agent + impl demonstrating intended SDK UX. |
| golem/examples/shared/src/main/scala/golem/examples/minimal/MinimalAgentToAgentExample.scala | Adds minimal agent-to-agent calling example traits/types. |
| golem/examples/samples/websearch-summary/repl-websearch-summary.rib | Adds RIB script to drive the websearch summary example. |
| golem/examples/samples/snapshot-counter/repl-snapshot-counter.rib | Adds RIB script to drive the snapshot counter example. |
| golem/examples/samples/simple-rpc/repl-counter.rib | Adds RIB script to drive the simple RPC example. |
| golem/examples/samples/llm-chat/repl-llm-chat.rib | Adds RIB script to drive the LLM chat example. |
| golem/examples/samples/json-tasks/repl-json-tasks.rib | Adds RIB script to drive the JSON tasks example. |
| golem/examples/samples/human-in-the-loop/repl-human-in-the-loop.rib | Adds RIB script to drive the HITL example. |
| golem/examples/samples/agent-to-agent/repl-minimal-agent-to-agent.rib | Adds RIB script to drive the agent-to-agent example. |
| golem/examples/llm-chat-local-repl.sh | Adds runnable local repl script for LLM chat example (guarded by env). |
| golem/examples/json-tasks-local-repl.sh | Adds runnable local repl script for JSON tasks example. |
| golem/examples/js/src/main/scala/golem/examples/templates/Utf8.scala | Adds UTF-8 helper for Scala.js examples (TextEncoder/TextDecoder). |
| golem/examples/js/src/main/scala/golem/examples/templates/TemplateExampleImpls.scala | Adds Scala.js implementations for multiple example agents. |
| golem/examples/js/src/main/scala/golem/examples/templates/AiExampleImpls.scala | Adds Scala.js implementations for AI-backed agents. |
| golem/examples/js/src/main/scala/golem/examples/minimal/MinimalAgentToAgentImpl.scala | Adds Scala.js impls for minimal agent-to-agent example. |
| golem/examples/hitl-local-repl.sh | Adds runnable local repl script for HITL example. |
| golem/examples/golem.yaml | Adds golem app manifest for example suite. |
| golem/examples/counter-local-repl.sh | Adds runnable local repl script for counter example. |
| golem/examples/components-js/scala-examples/golem.yaml | Adds component manifest for Scala.js examples component. |
| golem/examples/common-scala-js/golem.yaml | Adds shared Scala.js template for examples suite. |
| golem/examples/build-scalajs.sh | Adds build helper supporting sbt/mill for examples suite. |
| golem/examples/agent2agent-local.sh | Adds runnable local script for agent-to-agent example. |
| golem/examples/agent2agent-local-repl.sh | Adds runnable local repl script for agent-to-agent example. |
| golem/examples/README.md | Documents available examples + how to run them. |
| golem/docs/supported-versions.md | Adds a compatibility matrix for Scala/Scala.js/sbt/mill/golem-cli. |
| golem/docs/snapshot.md | Adds documentation for SnapshotExports helpers and usage patterns. |
| golem/docs/result.md | Adds documentation for WitResult/Result helpers and WIT bridging. |
| golem/docs/multimodal.md | Adds documentation for multimodal payloads and constraints derivation. |
| golem/core/shared/src/main/scala/golem/GolemPackageBase.scala | Adds cross-platform package-level aliases to keep API surface stable. |
| golem/core/shared/src/main/scala/golem/BaseAgent.scala | Adds BaseAgent interface with host-provided identity accessors. |
| golem/core/jvm/src/main/scala/golem/runtime/rpc/jvm/internal/WaveTextCodec.scala | Adds CLI-oriented argument encoding/decoding helpers for testing client. |
| golem/core/jvm/src/main/scala/golem/runtime/rpc/jvm/internal/GolemCliProcess.scala | Adds simple process runner for golem-cli invocations. |
| golem/core/jvm/src/main/scala/golem/runtime/rpc/jvm/JvmAgentClientConfig.scala | Adds env-configurable JVM golem-cli client settings. |
| golem/core/jvm/src/main/scala/golem/runtime/rpc/jvm/JvmAgentClient.scala | Adds a JVM dynamic-proxy client for invoking agents via golem-cli. |
| golem/core/jvm/src/main/scala/golem/package.scala | Exposes golem package aliases on the JVM side. |
| golem/core/jvm/src/main/scala/golem/BaseAgentPlatform.scala | Adds JVM stub throwing unsupported for host-only BaseAgent accessors. |
| golem/core/jvm/src/main/scala-3/golem/AgentCompanion.scala | Adds Scala 3 JVM AgentCompanion backed by JvmAgentClient. |
| golem/core/jvm/src/main/scala-2/golem/AgentCompanion.scala | Adds Scala 2 JVM stub AgentCompanion for cross-compilation. |
| golem/core/js/src/test/scala/golem/runtime/wit/WitResultSpec.scala | Adds tests for WitResult semantics, mapping, and unwrap behavior. |
| golem/core/js/src/test/scala/golem/runtime/schema/GolemSchemaRoundtripSpec.scala | Adds schema encode/decode roundtrip tests across multiple types. |
| golem/core/js/src/test/scala/golem/runtime/rpc/ValueMappingFuzzSpec.scala | Adds fuzz tests for RPC value encode/decode mappings. |
| golem/core/js/src/test/scala/golem/runtime/rpc/RpcValueCodecSpec.scala | Adds deterministic tests for RPC codec roundtrips. |
| golem/core/js/src/test/scala/golem/runtime/rpc/RpcValueCodecFixtures.scala | Adds fixtures used by RPC codec tests. |
| golem/core/js/src/test/scala/golem/runtime/rpc/BindListDoubleWorkflow.scala | Adds regression agent trait for Scala.js bind of List[Double]. |
| golem/core/js/src/test/scala/golem/runtime/rpc/AgentClientTypeEndToEndSpec.scala | Adds end-to-end test verifying client type loopback via resolve/bind. |
| golem/core/js/src/test/scala/golem/runtime/rpc/AgentClientBindListDoubleSpec.scala | Adds regression test for bind method name shape for List[Double]. |
| golem/core/js/src/test/scala/golem/runtime/autowire/JsPlainSchemaCodecSpec.scala | Adds tests for JS plain-object codec encode/decode. |
| golem/core/js/src/test/scala/golem/runtime/autowire/HostValueCodecSpec.scala | Adds tests for host value encode/decode (tuple + multimodal). |
| golem/core/js/src/test/scala/golem/runtime/AgentEndToEndSpec.scala | Adds end-to-end tests for binding invocation through HostPayload. |
| golem/core/js/src/test/scala/golem/runtime/AgentEndToEndFixtures.scala | Adds fixtures for AgentEndToEndSpec. |
| golem/core/js/src/main/scala/golem/wasi/KeyValue.scala | Adds Scala.js facades for WASI keyvalue packages. |
| golem/core/js/src/main/scala/golem/wasi/Environment.scala | Adds Scala.js facade for WASI environment package and helper conversion. |
| golem/core/js/src/main/scala/golem/wasi/Config.scala | Adds Scala.js facade for WASI config store package. |
| golem/core/js/src/main/scala/golem/wasi/Blobstore.scala | Adds Scala.js facades for WASI blobstore packages. |
| golem/core/js/src/main/scala/golem/runtime/util/FutureInterop.scala | Adds internal Future<->Promise utilities used across JS runtime. |
| golem/core/js/src/main/scala/golem/runtime/snapshot/SnapshotExports.scala | Adds host-exported snapshot entrypoints plus configurable hooks. |
| golem/core/js/src/main/scala/golem/runtime/rpc/host/WasmRpcApi.scala | Adds a thin wrapper around golem:rpc WasmRpc for invoke/trigger/schedule. |
| golem/core/js/src/main/scala/golem/runtime/rpc/RpcInvoker.scala | Adds an internal abstraction for invoke/trigger/schedule backends. |
| golem/core/js/src/main/scala/golem/runtime/rpc/RemoteAgentClient.scala | Adds agent client resolver wired to host APIs + WasmRpc. |
| golem/core/js/src/main/scala/golem/runtime/rpc/AgentClientRuntime.scala | Adds runtime for resolving agents and executing await/trigger/schedule. |
| golem/core/js/src/main/scala/golem/runtime/guest/Guest.scala | Adds Scala.js guest exports expected by Golem runtime. |
| golem/core/js/src/main/scala/golem/runtime/autowire/package.scala | Adds legacy naming alias for durability mode. |
| golem/core/js/src/main/scala/golem/runtime/autowire/WitValueCodec.scala | Adds decoding of WIT values into DataValue using DataType schema. |
| golem/core/js/src/main/scala/golem/runtime/autowire/WitValueBuilder.scala | Adds encoding of DataValue into WIT value node graphs. |
| golem/core/js/src/main/scala/golem/runtime/autowire/WitTypeBuilder.scala | Adds encoding of DataType into WIT type node graphs. |
| golem/core/js/src/main/scala/golem/runtime/autowire/MethodBinding.scala | Adds method binding abstraction (sync/async) + HostPayload integration. |
| golem/core/js/src/main/scala/golem/runtime/autowire/HostValueEncoder.scala | Adds host DataValue encoder for tuples + multimodal payloads. |
| golem/core/js/src/main/scala/golem/runtime/autowire/HostValueDecoder.scala | Adds host DataValue decoder for tuples + multimodal payloads. |
| golem/core/js/src/main/scala/golem/runtime/autowire/HostSchemaEncoder.scala | Adds host schema encoder for structured + multimodal schemas. |
| golem/core/js/src/main/scala/golem/runtime/autowire/HostPayload.scala | Adds top-level schema/encode/decode helpers bridging GolemSchema and host payloads. |
| golem/core/js/src/main/scala/golem/runtime/autowire/AgentTypeEncoder.scala | Adds host-facing agent-type encoder (constructor + methods + mode). |
| golem/core/js/src/main/scala/golem/runtime/autowire/AgentRegistry.scala | Adds synchronized registry for typeName -> AgentDefinition. |
| golem/core/js/src/main/scala/golem/runtime/autowire/AgentMode.scala | Adds durable/ephemeral mode model + parser. |
| golem/core/js/src/main/scala/golem/runtime/autowire/AgentImplementationRuntime.scala | Adds runtime wiring from macro-generated impl types into AgentDefinitions. |
| golem/core/js/src/main/scala/golem/runtime/autowire/AgentDefinition.scala | Adds runtime agent definition type with initialize + invoke by method name. |
| golem/core/js/src/main/scala/golem/runtime/autowire/AgentConstructor.scala | Adds constructor binding primitive + schema encoding for host. |
| golem/core/js/src/main/scala/golem/package.scala | Exposes Scala.js package aliases and core runtime entrypoints. |
| golem/core/js/src/main/scala/golem/host/Rdbms.scala | Adds Scala.js facades for built-in rdbms host modules. |
| golem/core/js/src/main/scala/golem/host/OplogApi.scala | Adds Scala.js facade for oplog host module (raw exposure). |
| golem/core/js/src/main/scala/golem/host/DurabilityApi.scala | Adds Scala.js facade for durability host package with minimal wrappers. |
| golem/core/js/src/main/scala/golem/host/ContextApi.scala | Adds Scala.js facade for context host package with minimal wrappers. |
| golem/core/js/src/main/scala/golem/Result.scala | Adds Result wrapper/alias over WitResult. |
| golem/core/js/src/main/scala/golem/RemoteAgent.scala | Adds remote handle wrapper giving await/trigger/schedule access. |
| golem/core/js/src/main/scala/golem/Guards.scala | Adds scoped host configuration guards (retry, persistence, atomic ops). |
| golem/core/js/src/main/scala/golem/FutureInterop.scala | Adds public facade over internal FutureInterop helper. |
| golem/core/js/src/main/scala/golem/DatetimeJs.scala | Adds Scala.js helper constructors for Datetime from host shapes. |
| golem/core/js/src/main/scala/golem/BaseAgentPlatform.scala | Adds Scala.js BaseAgentPlatform reading self metadata from host API. |
| golem/core/js/src/main/scala-3/golem/runtime/autowire/AgentImplementation.scala | Adds Scala 3 AgentImplementation API backed by macros + runtime registry. |
| golem/core/js/src/main/scala-2/golem/RemoteAgentOps.scala | Adds Scala 2 RemoteAgentOps macro-based RPC surface. |
| golem/core/js/src/main/scala-2/golem/AgentCompanion.scala | Adds Scala 2 cross-version AgentCompanion macro surface. |
| golem/.gitignore | Adds ignore rules for IDE/build artifacts and local golem state. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| runFireAndForget(method, input).map(_ => | ||
| throw new IllegalStateException("Fire-and-forget methods return Unit") | ||
| ) |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The FireAndForget branch always fails the returned Future by throwing, even when the method’s declared return type is Unit. If the intent is “call returns Future[Out]”, this should complete successfully for Out = Unit (or be prevented at compile time). Consider returning runFireAndForget(...).asInstanceOf[Future[Out]] (assuming Out is Unit for all fire-and-forget methods), or changing the API so call is only available for Awaitable and trigger/schedule are the supported entrypoints for fire-and-forget methods.
| runFireAndForget(method, input).map(_ => | |
| throw new IllegalStateException("Fire-and-forget methods return Unit") | |
| ) | |
| runFireAndForget(method, input).asInstanceOf[Future[Out]] |
| decodeTextValue(innerTag, payload.selectDynamic("val")).map(ElementValue.UnstructuredText.apply) | ||
| case ElementSchema.UnstructuredBinary(_) => | ||
| val payload = value.selectDynamic("val") | ||
| val innerTag = | ||
| if (tag == "unstructured-binary") payload.selectDynamic("tag").asInstanceOf[String] | ||
| else tag | ||
| decodeBinaryValue(innerTag, payload.selectDynamic("val")).map(ElementValue.UnstructuredBinary.apply) |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This decoder attempts to support both wrapped (tag == "unstructured-text"|"unstructured-binary") and unwrapped (tag == "inline"|"url") shapes, but the “unwrapped” path is currently incorrect: it still reads payload.selectDynamic("val"), even though payload is already the inner value for unwrapped shapes. A safer approach is to branch on tag: if wrapped, pass payload.selectDynamic("val"); otherwise pass payload directly to decodeTextValue / decodeBinaryValue.
| decodeTextValue(innerTag, payload.selectDynamic("val")).map(ElementValue.UnstructuredText.apply) | |
| case ElementSchema.UnstructuredBinary(_) => | |
| val payload = value.selectDynamic("val") | |
| val innerTag = | |
| if (tag == "unstructured-binary") payload.selectDynamic("tag").asInstanceOf[String] | |
| else tag | |
| decodeBinaryValue(innerTag, payload.selectDynamic("val")).map(ElementValue.UnstructuredBinary.apply) | |
| val innerPayload = | |
| if (tag == "unstructured-text") payload.selectDynamic("val") | |
| else payload | |
| decodeTextValue(innerTag, innerPayload).map(ElementValue.UnstructuredText.apply) | |
| case ElementSchema.UnstructuredBinary(_) => | |
| val payload = value.selectDynamic("val") | |
| val innerTag = | |
| if (tag == "unstructured-binary") payload.selectDynamic("tag").asInstanceOf[String] | |
| else tag | |
| val innerPayload = | |
| if (tag == "unstructured-binary") payload.selectDynamic("val") | |
| else payload | |
| decodeBinaryValue(innerTag, innerPayload).map(ElementValue.UnstructuredBinary.apply) |
| Right(IntValue(raw.toInt)) | ||
| case (LongType, "prim-s64") => | ||
| val raw = node.selectDynamic("val").asInstanceOf[Double] | ||
| Right(LongValue(raw.toLong)) | ||
| case (LongType, "prim-float64") => | ||
| val raw = node.selectDynamic("val").asInstanceOf[Double] | ||
| Right(LongValue(raw.toLong)) |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Converting prim-float64 to Int/Long via toInt/toLong will silently truncate fractional values (and may overflow without an error). If the host ever sends non-integral JS numbers, this will round toward zero and corrupt data. Consider rejecting non-whole numbers (e.g., check raw.isWhole) and validating bounds before converting.
| Right(IntValue(raw.toInt)) | |
| case (LongType, "prim-s64") => | |
| val raw = node.selectDynamic("val").asInstanceOf[Double] | |
| Right(LongValue(raw.toLong)) | |
| case (LongType, "prim-float64") => | |
| val raw = node.selectDynamic("val").asInstanceOf[Double] | |
| Right(LongValue(raw.toLong)) | |
| if (!raw.isValidInt) | |
| Left(s"Value $raw is out of Int range for IntType") | |
| else if (!raw.isWhole()) | |
| Left(s"Non-integral numeric value $raw cannot be decoded as IntType") | |
| else | |
| Right(IntValue(raw.toInt)) | |
| case (LongType, "prim-s64") => | |
| val raw = node.selectDynamic("val").asInstanceOf[Double] | |
| if (!raw.isValidLong) | |
| Left(s"Value $raw is out of Long range for LongType (prim-s64)") | |
| else if (!raw.isWhole()) | |
| Left(s"Non-integral numeric value $raw cannot be decoded as LongType (prim-s64)") | |
| else | |
| Right(LongValue(raw.toLong)) | |
| case (LongType, "prim-float64") => | |
| val raw = node.selectDynamic("val").asInstanceOf[Double] | |
| if (!raw.isValidLong) | |
| Left(s"Value $raw is out of Long range for LongType (prim-float64)") | |
| else if (!raw.isWhole()) | |
| Left(s"Non-integral numeric value $raw cannot be decoded as LongType (prim-float64)") | |
| else | |
| Right(LongValue(raw.toLong)) |
golem/examples/counter-local-repl.sh
Outdated
| GOLEM_CLI_FLAGS="${GOLEM_CLI_FLAGS:---local}" | ||
| read -r -a flags <<<"$GOLEM_CLI_FLAGS" | ||
|
|
||
| is_cloud=0 | ||
| for f in "${flags[@]}"; do | ||
| [[ "$f" == "--cloud" ]] && is_cloud=1 | ||
| done | ||
|
|
||
| if [[ "$is_cloud" -eq 0 ]]; then | ||
| host="${GOLEM_ROUTER_HOST:-127.0.0.1}" | ||
| port="${GOLEM_ROUTER_PORT:-9881}" | ||
| if ! timeout 1 bash -lc "cat < /dev/null > /dev/tcp/$host/$port" 2>/dev/null; then | ||
| echo "[counter-local-repl] Local router not reachable at $host:$port." >&2 | ||
| echo "[counter-local-repl] Start it in another terminal, then rerun:" >&2 | ||
| echo " golem server run --clean --data-dir .golem-local --router-port $port" >&2 | ||
| exit 1 | ||
| fi | ||
| fi |
Copilot
AI
Jan 24, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The “flags parsing + cloud/local router reachability check + build + deploy + repl + output grep” flow is duplicated across multiple scripts in golem/examples/*.sh. This makes it easy for behavior to drift (e.g., timeouts, error patterns, router port guidance). Consider extracting shared logic into a single examples/lib.sh (or similar) and sourcing it from each script, keeping only the script-specific script_file and expected output checks.

This pull request introduces the initial implementation of the ZIO-Golem Scala SDK, providing a type-safe, macro-powered toolkit for building Golem agents. The changes include a comprehensive project README, new macro-based agent registration and client APIs for Scala.js, and supporting configuration files.
Documentation and project setup:
README.mddescribing ZIO-Golem's features, usage examples, project structure, build instructions, and developer tooling for sbt and Mill. This serves as the main entry point for new users and contributors..gitignorefile to exclude IDE, build, and local configuration artifacts from version control.Core agent and client APIs (Scala.js):
AgentImplementation.scala, providing macro-based registration of agent implementations with automatic derivation of RPC bindings, schemas, and metadata. Supports trait-based agent definitions and compile-time wiring.AgentClient.scala, enabling macro-based client generation for agent traits, with support for Scala.js and explicit plan resolution/binding.