Skip to content

Updates for Golem 1.6 and naming unifications#1

Draft
noise64 wants to merge 22 commits into
mainfrom
golem-1.6
Draft

Updates for Golem 1.6 and naming unifications#1
noise64 wants to merge 22 commits into
mainfrom
golem-1.6

Conversation

@noise64

@noise64 noise64 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

No description provided.

noise64 added 22 commits June 29, 2026 22:29
… codec

Foundation of the new-schema-model migration (golem 1.5.0 -> 2.0.0 value model):

- Phase 0: vendor current golem main WIT deps (golem:core@2.0.0, golem:agent@2.0.0,
  +golem:secrets/+golem:tool); update the agent-guest world (agent/host + include
  -> 2.0.0, +secrets); regenerate golem-types bindings, keeping the old @1.5.0
  core/agent .d.ts alongside the new @2.0.0 ones so the migration can proceed
  module-by-module; add the new specifiers to rollup external.
- Phase 1: port the vendor-neutral schema-model layer from golem-ts-sdk
  (recursive SchemaType/SchemaValue/SchemaGraph, t.*/v.* ctors, SchemaBuilder,
  GraphEncoder, mergeGraphDefs, schema-value-tree carriers), adjusted for
  effect-golem's NodeNext tsconfig (.js import extensions).
- Phase 2: rewrite WitCodec as the Effect-Schema -> new-model walker producing
  { graph: SchemaGraph, isUnit, codec: Codec<Type, SchemaValue> }, composed with
  the user's schema so refinements/transforms still run.

schema-model + WitCodec compile clean; downstream consumers (agent/method
dispatch, config, durability, rpc, element/multimodal) + tests still reference the
old model and are migrated in later phases.
method.ts, agent.ts, guest.ts now run on the new model:
- MethodCodec carries per-param WitCodecs + a unit|single output (no ElementCodec
  / DataSchema); compileParamCodecs/compileMethodSpec produce them.
- invokeSchemaValue decodes the input record per-param via Schema.decodeEffect +
  schemaValueFromWit, runs the handler, encodes via Schema.encodeEffect +
  schemaValueToWit -> option<schema-value-tree>. Typed-error result<S,E> folding
  and the void-success empty-record stand-in are preserved.
- agent.ts assembles the AgentType via one GraphEncoder per agent (merge graphs ->
  input-schema parameters + output-schema unit|single; schema = encoder.finish());
  dispatch on schema-value-tree.
- guest.ts boundary -> SchemaValueTree, invoke -> option, export object renamed to
  golemAgent200Guest (wasm-rquickjs convention for golem:agent/guest@2.0.0).
- Multimodal/unstructured params rejected pending the Phase 5 redesign.

NOTE: WIP — does not fully compile yet. The core dispatch path (method/agent/guest/
WitCodec/schema-model) is type-clean; the rich-feature modules still on the old model
(Config, Client/RPC, durableFunction, Quota, Element/Multimodal/Unstructured) are
migrated in Phase 4-5.
- DurabilityClient / durableFunction: ValueAndType -> typed-schema-value via
  typedSchemaValueToWit / typedSchemaValueFromWit (paired with the codec's graph).
- Config / ConfigClient: get-config-value now takes a schema-graph and returns a
  schema-value-tree; config value-types carry a SchemaGraph and are flattened into
  the agent's shared pool by agent.ts (one GraphEncoder), so each config declaration
  resolves to a type-node-index. Typed-agent-config-value -> typed-schema-value.
- agent.ts assembles config value-types into the same GraphEncoder pool.

Core dispatch + durability + config now type-clean. Remaining (still old model):
Client (RPC), Quota/QuotaClient (golem:quota WIT changed), Element/Multimodal/
Unstructured (element layer removed -> Phase 5 redesign). 31 -> 21 src errors.
- Client (RPC): client-side dispatch on schema-value-tree (encodeParams ->
  v.record -> schemaValueToWit; decodeMethodOutput from option<schema-value-tree>;
  compileParamCodecs; CompiledClient.constructorCodecs).
- RpcClient / ConfigClient: host wrappers -> golem:agent/host@2.0.0
  (invoke-and-await + get-config-value on schema-value-tree / schema-graph).
- Quota / QuotaClient: golem:quota free-function API (newToken/reserve/split/merge);
  quota-token is now an opaque core capability handle (record codec removed; the
  capability-node walker support is deferred to Phase 5).
- Element: element-value layer removed -> componentModelElement is inert pending
  the Phase 5 Multimodal/Unstructured redesign.
- Delete dead witTree.ts.

`npm run build` (tsc -p tsconfig.build.json) is clean and emits dist. Remaining:
test/** (Phase 6) + Multimodal/Unstructured/quota-token Phase 5 redesign; then
rebuild agent_guest.wasm and verify the counter.
generate-agent-template.mjs now swaps the wasm-rquickjs skeleton's upstream
wit-bindgen for Golem's golem-outline-lift fork (the golem:core@2.0.0 schema
model produces giant lift/lower wrappers upstream can't handle). The full
agent-template crate compiles to wasm32-wasip2 and wasm/agent_guest.wasm is
regenerated.
- AgentHostClient: golem:agent/host@1.5.0 -> @2.0.0 (the value import that broke
  module init in the guest); parseAgentId now returns a typed-schema-value, so
  agent.ts uses ctorDataValue.value (the schema-value-tree) on snapshot-load.
- snapshotEnvelope: golem:core/types@1.5.0 -> @2.0.0 (uuidToString/parseUuid).
- Add a guarded strictTextDecoder() helper (try fatal, fall back to lenient) and
  use it in KeyValue/Webhook/Blobstore/snapshotEnvelope — the QuickJS guest is
  compiled without ICU, where `new TextDecoder(_, {fatal:true})` throws at load.
- vitest: alias the @2.0.0 agent/core (and golem:secrets) specifiers to the mocks.
- Migrate the wit-codec test assertions to the new model (graph.root.body / SchemaValue);
  all codec tests pass. Rename guest -> golemAgent200Guest in the agent/snapshot/http tests.

Counter example: golem build now extracts agent types AND module init succeeds on the
new binary (rebuilt from HEAD to match the new value model).
- guest export: revert golemAgent200Guest -> `guest` (effect-golem's wasm-rquickjs
  looks up the short interface name `guest.discoverAgentTypes`, unlike golem-ts-sdk's
  package-qualified convention); keep a golemAgent200Guest alias for callers/tests.
- Config: a Schema.Redacted secret config leaf now declares its value type as
  secret<plaintext> (t.secret wrapping the inner type) — the agent registry rejects
  a bare plaintext type for secret-sourced config (AGENT_SECRET_INVALID_CONFIG_TYPE).

Validated: golem build -> deploy [OK] -> Counter agent type registered ->
invoke value=0, increment=1, value=1 (durable state persists across calls).
A secret config leaf declares its value type as secret<inner>, so getConfigValue
returns an opaque secret handle, not the plaintext. On read, reveal the handle
(capability-gated) against the inner-type graph to obtain the inner value tree,
then decode that with the inner codec and wrap in Redacted. Local leaves are
unchanged. Add minimal vitest mocks for golem:secrets/{reveal,types}.

Validated: Counter.keyTail (reads a Schema.Redacted apiKey, returns its last 4
chars) -> "1234". The Counter now exercises every method end-to-end (value/
increment/add/owner/caller/currentGreeting/keyTail) incl. durable state, local +
secret config, a number param, and the principal.
- host-features-agent: golem:core/types@1.5.0 -> @2.0.0 (uuidToString) — it was the
  last value import of the old core types, which broke module init once the agent
  was loaded alongside the others.
- main.ts / golem.yaml: restore all agents except QuotaTester (its quota-token API
  is pending the Phase-5 capability-node migration).

Validated end-to-end against a local server (all 14 agent types register + deploy [OK]):
- Counter / SqliteCounter: durable state (+ node:sqlite snapshot)
- Caller: wasm-RPC to a remote Counter (bump -> 1)
- KvAgent / BlobAgent: wasi keyvalue + blobstore (put/write -> true)
- Lookup: result<S,E> typed errors (fetch -> { ok: 7 })
- plus config (local + revealed secret), number params, and principal on Counter.

Unit tests: 629/659 pass; the 30 failures are all the deferred Phase-5 features
(quota-token, multimodal, unstructured).
agent/method/snapshot/snapshot-sqlite/config/http/durable-function tests + the
RpcFake helper. Inputs build via schemaValueToWit(v.record([...])); results are
SchemaValueTree | undefined (unit -> undefined); AgentType assertions use the new
input-schema parameters(named-field) / output-schema unit|single shapes; the secret
config tests return v.secret(handle) and drive the golem:secrets/reveal mock.

All migrated files pass individually; full suite 629/659 (remaining failures are the
deferred quota/multimodal/unstructured Phase-5 features).
- Quota.ts: replace the stubbed Schema.declare QuotaToken with a codec marked by
  witQuotaTokenAnnotationKey; the WitCodec walker maps it to the schema-model
  quota-token capability node (graph root t.quotaToken({})). Encode lowers a host
  QuotaToken -> v.quotaToken(GuestQuotaTokenHandle.fromRaw(...)); decode takes the
  handle. Drop dead old-model exports (Uuid/EnvironmentId/Datetime/QuotaTokenRecord).
  Operational API (acquire/reserve/commit/split/merge/withReservation) unchanged.
- WitTypes.ts: add the marker annotation key.
- test mock golem-quota-types: rewrite from the old class API (toRecord/fromRecord)
  to the new free-function interface (newToken/reserve/split/merge/Reservation.commit),
  preserving the event-recording + failure-injection hooks.
- quota.test: drop the 5 obsolete record-model schema cases, add two new-model cases
  (quota-token node + v.quotaToken round-trip), keep all operational tests. Remove the
  obsolete quota-token-property.test.ts and the wit-drift quota-record block.

quota.test: 17 passed. Build 0 errors.
- Unstructured: UnstructuredText/Binary now compile to t.variant([inline->text|binary,
  url->url]) with metadata.role = unstructured-text|binary; restrictions use the new
  languages[]/mimeTypes[] keys; value codecs map to v.variant(inline|url). ElementSpec
  is a thin carrier holding a pre-built WitCodec (preserves isElementSpec + the
  MethodParam/BindableKeys machinery). tryGetter routes thrown decode errors onto the
  schema-error channel.
- Multimodal: multimodal(shape) builds one WitCodec rooted at list<variant> + role
  multimodal, walking each member (ElementSpec reuses its variant; plain Schema via
  toWitCodec). compile() returns a WitCodec.
- method.ts compileParamCodecs: accept ElementSpec/Multimodal params (drop the Phase-5
  rejection); multimodal-must-be-sole-param validation centralized here.
- Element.ts trimmed to ElementValueKindError (dead ElementCodec/componentModelElement removed).
- Rewrite unstructured/multimodal tests to the new model (graph.root variant/list + role,
  inline text/binary restrictions, v.variant/v.list round-trips); behaviors preserved.

unstructured+multimodal: 11 passed. Build 0 errors. Full suite 647 passed; remaining 9
failures are pre-existing (client.test old-model + wit-tree.test for the deleted module).
The new-model quota-token is an opaque capability handle (no toRecord()/expectedUse).
- acquire: mint the token to prove acquisition, echo the known resourceName + expected.
- splitMerge: split + merge complete without error as the observable proof; report the
  deterministic expected-use figures (split moves child out, merge folds it back).
The operational methods (withReservation/manualReserve*/exhaustAndReject) use the intact
Quota.* API unchanged. Re-wire into main.ts + golem.yaml.

Validated end-to-end (all 15 agents deploy [OK]): acquire 5 -> {resourceName, expectedUse:"5"};
withReservationOk 3/2 -> "committed:2"; splitMerge 8/3 -> {parent:"5", child:"3", merge:"8"}.
Mirror of the dispatch-test migration for the client side: inputs built via
schemaValueToWit(v.record([...])); encoded form is a SchemaValue; constructor/method
input assertions decode via schemaValueFromWit (.tag==="record", positional .fields);
fake responder outputs are bare schema-value-trees (unit -> undefined, single ->
schemaValueToWit(sv)) per Client.ts decodeMethodOutput; the void+typed-error
wire-violation case now checks the missing-value path; custom-error payload uses
typed-schema-value shape.

client.test: 33 passed.
- Re-vendor src/internal/schema-model/{model,wit}.ts from golem-ts-sdk main (numeric
  type nodes now carry option<numeric-restrictions>); re-add .js import extensions.
  Update wit/deps/golem-core-v2 (+golem-tool) and regenerate the bindings; refresh the
  curated golem-types core/tool d.ts.
- Adopt restrictions: WitTypes adds `restrict({min,max,unit})` + witNumericRestrictionsKey;
  WitCodec numericNode lowers them to t.<kind>(restrictions) with the right NumericBound tag
  (unsigned/signed/float-bits), canonicalizing -0.0 float bits.

Tests: 659 pass (656 + 3 numeric-restriction round-trip cases). Build 0 errors.
…d, snapshot→snapshotting)

Aligns effect-golem's public defineAgent option keys with the canonical fluent
SDK names, and sweeps the adjacent internal naming to fluent's "id field" / `Id`
vocabulary:

- constructorParams → id; snapshot → snapshotting (agent-definition keys)
- canonicalConstructorParams → canonicalId; constructorParamNames → idFieldNames;
  string/nonStringBindableConstructorParams → string/nonStringBindableIdFields
- generic C → Id (agent.ts / httpTypes.ts / Client.ts)
- "constructor param(eter)" → "id field" in comments + error/branded messages

Pure source-API rename — no wire/behavior change. The Snapshot module, the
bound-instance AgentInstance.snapshot, and the shared MethodParams/MethodInput
types are unchanged. Tests updated in lockstep; build + 659 tests green.
…ess→returns)

Aligns effect-golem's public method() option keys with the canonical fluent SDK
names, and sweeps the adjacent internal naming to fluent's `Input` generic:

- params → input; success → returns (method-definition keys)
- generic Params → Input (method.ts / Client.ts / Http.ts / httpTypes.ts)
- stringBindableParams → stringBindableInputs; nonStringBindableParams →
  nonStringBindableInputs

Pure source-API rename — no wire/behavior change. Durability.wrap({success}),
SQL bind `params`, and successVoid/MethodParams/MethodInput are unchanged. Tests
updated in lockstep; build + 659 tests green.
…emas

effect's numeric pins were bare `Schema.Number.pipe(annotate)` — no integer/range
check (decode of 999 or 3.7 passed) — and `restrict({min,max})` was a generic
`Schema.annotate` applicable to any schema, never locally enforced. Both fluent
(validating marker) and the base/decorator SDK (checkIntRange) already reject
out-of-range at runtime; this brings effect in line.

- Integer pins (Uint8..Int32) -> Schema.Int.check(isBetween({minimum,maximum}))
  via intPin(); bigint pins (Int64/Uint64) -> bigPin() with
  isGreaterThanOrEqualToBigInt/isLessThanOrEqualToBigInt. Rejects non-integer /
  out-of-range on Schema.decode/encode (the invocation boundary). Float32/64
  unchanged.
- restrict(opts) is now typed <S extends Schema.Schema<number> |
  Schema.Schema<bigint>>(self: S) => S (compile error on String/Boolean/record),
  adds real bound checks, and re-carries the pin's witType tag onto the final
  annotation layer (the codec's resolveAt reads only the last check, so appending
  bound checks would otherwise drop the width). Bonus: preserves the static
  number/bigint type (was erased to unknown).

Wire-safe: same emitted WIT type, just adds runtime rejection + compile-time
safety. Tests: decode/encode rejection cases in wit-codec-restrictions.test.ts +
new wit-numeric-restrict.test-d.ts (restrict is numeric-only). 663 vitest +
typecheck green.
Tier-A unification had renamed method({ success }) -> method({ returns }) to
match fluent. Reconsidered: an effect handler returns Effect<A, E, R>, so
`success`/`error` name its two channels and pair with the sibling `error:` key
(canonical Effect/Result vocabulary); `returns` reads oddly next to `error:`.

- method: returns -> success (field, option key, spec.success, JSDoc/prose).
- `input` stays unified with fluent (params -> input kept).
- fluent keeps `returns` (handler returns a value directly; no `error:` sibling,
  typed errors fold into the return via s.result).

Wire-safe: pure source-API rename, no emitted schema/WIT change.
Same gap the SDK rename left: the in-repo integration-test app still used the old
option keys (it's e2e, not part of `npm test`). Align it to the renamed API:
constructorParams -> id, params -> input, snapshot -> snapshotting. `success` kept.
Add a `readOnly?: boolean | ReadOnlyOption` option to `method(...)` /
`MethodSpec` (plus a `withReadOnly` combinator), reaching parity with the
base/fluent SDKs. `true` maps to the base default `until-write` cache policy;
the object form selects `no-cache` / `until-write` / `ttl` and per-principal
caching. `resolveReadOnly` in agent.ts lowers it into the emitted
`AgentMethod.readOnly` (`read-only-config`), replacing the hardcoded
`undefined`.
Add `Principal.PrincipalSchema`, an Effect Schema whose WitCodec lowers to the
WIT `golem:agent/common` `principal` variant (oidc/agent/golem-user/anonymous),
letting a Principal travel as ordinary data in a method param/return (not just
the Principal capability service). The schema is annotated with a new
`witPrincipalAnnotationKey`; the WitCodec Declaration walker recognises it and
emits the variant graph + a host-Principal round-trip value pair.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant