Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
b5193f8
WIP: add generic agent reflection host support
noise64 Aug 26, 2026
34b0c04
Regenerate MoonBit reflection bindings
noise64 Aug 26, 2026
8122639
WIP: add TypeScript agent reflection vertical slice
noise64 Aug 26, 2026
496bc67
Add separate RPC creation paths for reflected clients
noise64 Aug 27, 2026
4ba00ad
Refine reflection API naming and RPC behavior
noise64 Aug 27, 2026
19ccca9
Keep static RPC call sites fail-fast
noise64 Aug 27, 2026
5628544
Add canonical reflected schema rendering
noise64 Aug 27, 2026
5367d05
Add aligned TypeScript reflection client levels
noise64 Aug 27, 2026
71e30b0
Keep schema values compatible across SDK entries
noise64 Aug 27, 2026
58988cb
Delay strict UTF-8 decoder initialization
noise64 Aug 27, 2026
4584f20
Adopt definition-owned clients and agent identities in TS
noise64 Aug 27, 2026
d505f91
Migrate TypeScript RPC guidance to definition clients
noise64 Aug 27, 2026
23e2be3
Inline definition clients in TypeScript examples
noise64 Aug 27, 2026
63e9c26
Map agent RPC errors in TypeScript
noise64 Aug 27, 2026
141d246
Remove lifecycle mode from AgentId-bound clients
noise64 Aug 28, 2026
977eff8
Close ephemeral lifecycle contract gaps
noise64 Aug 31, 2026
0ded4e5
Format merged TypeScript SDK files
noise64 Aug 31, 2026
e13dfa0
Forward fallible RPC creation in debug context
noise64 Aug 31, 2026
83a71fc
Document and test TypeScript agent reflection
noise64 Aug 31, 2026
2e387e5
Raise TypeScript CI declaration heap
noise64 Aug 31, 2026
870b700
Regenerate Scala agent host declarations
noise64 Aug 31, 2026
aaa9091
Document AgentId schema construction paths
noise64 Aug 31, 2026
797d14f
Move schema rendering into golem-schema
noise64 Sep 2, 2026
dd29d9d
Preserve persisted reflection payload tags
noise64 Sep 5, 2026
a58bee0
Finish TypeScript reflection contracts
noise64 Sep 7, 2026
b13e610
Restore strict UTF-8 decoding
noise64 Sep 7, 2026
f2c8177
Move generic schema renderers to golem-schema
noise64 Sep 7, 2026
3537e4b
Harden reflected agent lifecycle failures
noise64 Sep 7, 2026
43d7e5c
Add common agent reflection skill
noise64 Sep 7, 2026
b6ee223
Sync generated agent WIT artifacts
noise64 Sep 8, 2026
c50f7b7
Align typed worker failure expectation
noise64 Sep 8, 2026
945f153
Merge remote-tracking branch 'origin/main' into reflection-base-and-ts
noise64 Sep 9, 2026
7cf48bf
Remove completed reflection planning notes
noise64 Sep 9, 2026
08caf9d
Merge remote-tracking branch 'origin/main' into reflection-base-and-ts
noise64 Sep 9, 2026
0cc54a5
Merge remote-tracking branch 'origin/main' into reflection-base-and-ts
noise64 Sep 10, 2026
059b278
Use environment-scoped IDs for agent reflection
noise64 Sep 10, 2026
1e9b9b7
Regenerate Scala agent host declaration
noise64 Sep 10, 2026
e650153
Fix reflected Rust method test name
noise64 Sep 10, 2026
26dfbc4
Align agent ID wire bindings
noise64 Sep 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .agents/skills/migrate-ts-decorator-sdk/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { BaseAgent, agent, prompt, description, endpoint, readonly, Config, Secr

// NEW — import what you use
import { z } from 'zod'; // or valibot / arktype
import { defineAgent, method, s, http, clientFor, Result } from '@golemcloud/golem-ts-sdk';
import { defineAgent, method, s, http, Result } from '@golemcloud/golem-ts-sdk';
```

`Result` still exists (host `Result.ok` / `Result.err`). `Config` and `Secret` as **constructor parameter types** are gone — config is now a `config` record on `defineAgent` and secrets are `s.secret(...)` markers surfaced as `Secret<T>` handles on `this.config`.
Expand Down Expand Up @@ -195,8 +195,9 @@ removed decorator surface.
**Current forms:**
- **`readOnly` cache policies.** `@readonly({ cache: 'no-cache' | 'until-write' | { ttl } })` → `method({ readOnly: { cache: 'no-cache' | 'until-write' | { ttlNanos: <bigint> }, usesPrincipal?: boolean } })`. Bare `readOnly: true` uses the `until-write` policy (the base default); principal-dependent caching → `usesPrincipal: true`.
- **Config-on-RPC (`getWithConfig`).** `Agent.getWithConfig(id, overrides)` →
`clientFor(Def)(id, undefined, overrides)`. For a fresh phantom agent, use
`clientFor(Def).newPhantom(id, overrides)`. Non-secret override leaves are encoded and applied at
`Def.client.get(id, overrides)`. For an existing phantom agent, use
`Def.client.getPhantom(id, phantomId, overrides)`; for a fresh phantom agent, use
`Def.client.newPhantom(id, overrides)`. Non-secret override leaves are encoded and applied at
call time; secret overrides are rejected because secrets remain host-provisioned.
- **Cancelable / abortable RPC.** Pass `{ signal }` to an awaited client method, for example
`await client.run(input, { signal })`. `client.run.schedule(at, input)` returns a
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,8 @@ jobs:

build-golem-ts:
runs-on: blacksmith
env:
NODE_OPTIONS: --max-old-space-size=4096
steps:
- uses: actions/checkout@v5
with:
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/skill-harness.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ jobs:

build-golem-ts:
runs-on: blacksmith
env:
NODE_OPTIONS: --max-old-space-size=4096
steps:
- uses: actions/checkout@v5
with:
Expand Down
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/golem-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ harness = false
golem-api-grpc = { workspace = true }
golem-client = { workspace = true }
golem-common = { workspace = true, default-features = true }
golem-schema = { workspace = true }

# External deps
anyhow = { workspace = true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2412,7 +2412,7 @@ fn schema_value_to_json(
value: &SchemaValue,
) -> anyhow::Result<serde_json::Value> {
if !schema_value_contains_stream(value) {
return golem_common::schema::render::to_json_value(graph, ty, value).map_err(Into::into);
return golem_schema::schema::render::to_json_value(graph, ty, value).map_err(Into::into);
}

let ty = graph
Expand Down
2 changes: 1 addition & 1 deletion cli/golem-cli/src/model/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1195,7 +1195,7 @@ fn format_typed_config(config: &[TypedAgentConfigEntry]) -> String {
.iter()
.map(|entry| {
let key = entry.path.join(".");
let value = golem_common::schema::render::to_json_value(
let value = golem_schema::schema::render::to_json_value(
entry.value.graph(),
entry.value.root_type(),
entry.value.value(),
Expand Down
2 changes: 1 addition & 1 deletion cli/golem-cli/src/model/masking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ mod tests {
mask_typed_agent_config_entries(MaskingConfig::hide_secrets(), &entries, &secret_paths);

assert_eq!(
golem_common::schema::render::to_json_value(
golem_schema::schema::render::to_json_value(
masked[0].value.graph(),
masked[0].value.root_type(),
masked[0].value.value(),
Expand Down
12 changes: 6 additions & 6 deletions cli/golem-cli/templates/ts/common/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ This project includes coding-agent skills in `.agents/skills/`. Load a skill whe
| `golem-mark-read-only-ts` | Marking methods `readOnly` for a side-effect-free guarantee and result caching |
| `golem-add-config-ts` | Adding typed configuration to a TypeScript agent |
| `golem-add-secret-ts` | Adding secrets (`s.secret`, `Secret<T>`) to TypeScript agents |
| `golem-call-another-agent-ts` | Calling another agent and awaiting the result (RPC) with `clientFor` |
| `golem-call-another-agent-ts` | Calling another agent and awaiting the result over RPC through a definition client |
| `golem-agent-reflection` | Choosing reflection levels and identity lookup behavior |
| `golem-agent-reflection-ts` | Discovering and calling agents through runtime reflection |
| `golem-call-from-external-ts` | Calling agents from external Node.js apps using generated bridge SDKs |
| `golem-fire-and-forget-ts` | Triggering an agent invocation without waiting for the result (`.trigger`) |
| `golem-parallel-workers-ts` | Fan out work to multiple parallel agents and collect results |
Expand Down Expand Up @@ -254,15 +256,13 @@ Config values are provisioned via `golem.yaml` (`env`/`envDefaults`/`secretDefau

## Calling Other Agents (RPC)

`clientFor(Def)` returns a factory; call it with an id record to get a typed proxy, or use `factory.newPhantom(id)` to create a phantom and return `{ client, phantomId }`. `await client.m(input, { signal })` invokes with optional cancellation; `client.m.trigger(input)` is fire-and-forget; `client.m.schedule(at, input)` enqueues for later and returns a `CancellationToken`.
Every agent definition exposes a `.client` factory. Use `.get(id)` for a durable agent, `.getPhantom(id, phantomId)` for a known phantom, or `.newPhantom(id)` to create a phantom and return `{ client, agentId, phantomId }`. `await client.m(input, { signal })` invokes with optional cancellation; `client.m.trigger(input)` is fire-and-forget; `client.m.schedule(at, input)` enqueues for later and returns a `CancellationToken`.

```typescript
import { clientFor } from '@golemcloud/golem-ts-sdk';
import { Counter } from './counter-agent.js';

const counter = clientFor(Counter);
const next = await counter({ name: 'c1' }).add({ by: 5 });
counter({ name: 'c1' }).add.trigger({ by: 1 }); // fire-and-forget
const next = await Counter.client.get({ name: 'c1' }).add({ by: 5 });
Counter.client.get({ name: 'c1' }).add.trigger({ by: 1 }); // fire-and-forget
```

## Snapshotting
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import { z } from 'zod';
import { defineAgent, method, http, clientFor, createPromise, awaitPromise } from '@golemcloud/golem-ts-sdk';
import { defineAgent, method, http, createPromise, awaitPromise } from '@golemcloud/golem-ts-sdk';
import { HumanAgent, encodePromiseId } from './human-agent.js';

// A typed RPC client factory for the remote HumanAgent (wasm-RPC under the hood).
const humanClient = clientFor(HumanAgent);

// The workflow side of the loop: it creates a promise, hands it to a human for
// approval, then PAUSES until the promise is completed — the classic
// human-in-the-loop pattern. Each workflow instance gets its own generated id.
Expand All @@ -24,9 +21,9 @@ export const WorkflowAgentImpl = WorkflowAgent.implement({
// 1. Create a promise that represents waiting for human input.
const approvalPromiseId = createPromise();

// 2. Register the pending approval with the human (remote agent call).
// 2. Register the pending approval with the human over agent RPC.
// Normally you would surface this in a UI, email, etc.
await humanClient({ username: approver }).requestApproval({
await HumanAgent.client.get({ username: approver }).requestApproval({
workflowId: this.workflowId,
promiseId: encodePromiseId(approvalPromiseId),
});
Expand Down
9 changes: 3 additions & 6 deletions cli/golem-cli/test-data/ts-code-first-snippets/main.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { z } from 'zod';
import { defineAgent, method, s, clientFor } from '@golemcloud/golem-ts-sdk';
import { defineAgent, method, s } from '@golemcloud/golem-ts-sdk';

import {
ObjectType,
Expand Down Expand Up @@ -327,9 +327,6 @@ export const BarAgentImpl = BarAgent.implement({
},
});

// A typed RPC client factory for the remote BarAgent (mirrors `Client<BarAgent>`).
const barAgentClient = clientFor(BarAgent);

// ---------------------------------------------------------------------------
// FooAgent — forwards every call to its BarAgent client and returns the result.
// ---------------------------------------------------------------------------
Expand All @@ -346,11 +343,11 @@ export const FooAgent = defineAgent({
});

export const FooAgentImpl = FooAgent.implement({
// Build the phantom BarAgent client mirroring the old `BarAgent.get("foooo", 1)`
// Build the typed RPC client for BarAgent, mirroring `BarAgent.get("foooo", 1)`
// (constructor params optionalStringType = "foooo", optionalUnionType = 1).
init: ({ id }) => ({
input: id.input,
barAgent: barAgentClient({ optionalStringType: 'foooo', optionalUnionType: 1 }),
barAgent: BarAgent.client.get({ optionalStringType: 'foooo', optionalUnionType: 1 }),
}),
methods: {
funAll(input) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { z } from 'zod';
import { defineAgent, method, clientFor } from '@golemcloud/golem-ts-sdk';
import { defineAgent, method } from '@golemcloud/golem-ts-sdk';

export const StringAgent = defineAgent({
name: 'StringAgent',
Expand Down Expand Up @@ -33,18 +33,15 @@ export const StructAgentImpl = StructAgent.implement({
},
});

const stringClient = clientFor(StringAgent);
const structClient = clientFor(StructAgent);

async function runStringTest(): Promise<void> {
for (let i = 445; i < 450; i++) {
await stringClient({ name: ' '.repeat(i) }).test();
await StringAgent.client.get({ name: ' '.repeat(i) }).test();
}
}

async function runStructTest(): Promise<void> {
for (let i = 100; i < 105; i++) {
await structClient({
await StructAgent.client.get({
args: { x: ' '.repeat(i), y: ' '.repeat(i), z: '/'.repeat(i) },
}).test();
}
Expand Down
6 changes: 2 additions & 4 deletions cli/golem-cli/tests/app/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3463,7 +3463,7 @@ async fn test_long_agent_id_rejected_in_invoke_repl_and_rpc() {
&component_source_code_main_file,
indoc! { r#"
import { z } from 'zod';
import { defineAgent, method, clientFor } from '@golemcloud/golem-ts-sdk';
import { defineAgent, method } from '@golemcloud/golem-ts-sdk';

export const TargetAgent = defineAgent({
name: 'TargetAgent',
Expand All @@ -3482,8 +3482,6 @@ async fn test_long_agent_id_rejected_in_invoke_repl_and_rpc() {
},
});

const targetClient = clientFor(TargetAgent);

export const CallerAgent = defineAgent({
name: 'CallerAgent',
id: { id: z.string() },
Expand All @@ -3496,7 +3494,7 @@ async fn test_long_agent_id_rejected_in_invoke_repl_and_rpc() {
init: ({ id }) => ({ id: id.id }),
methods: {
async callTarget({ targetId }) {
return await targetClient({ id: targetId }).ping();
return await TargetAgent.client.get({ id: targetId }).ping();
},
},
});
Expand Down
27 changes: 21 additions & 6 deletions cli/golem-cli/wit/deps/golem-agent/host.wit
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package golem:agent@2.0.0;

interface host {
use golem:core/types@2.0.0.{component-id, uuid, promise-id, schema-graph, schema-value-tree, typed-schema-value, permission-card};
use golem:core/types@2.0.0.{uuid, promise-id, schema-graph, schema-value-tree, typed-schema-value, permission-card};
use wasi:clocks/system-clock@0.3.0.{instant as datetime};
use common.{agent-error, agent-type, registered-agent-type, typed-agent-config-value};

Expand All @@ -11,19 +11,25 @@ interface host {
/// Get a specific registered agent type by name
get-agent-type: func(agent-type-name: string) -> option<registered-agent-type>;

/// An environment-scoped agent identity created by `make-agent-id`.
type agent-id = string;

/// Gets the registered agent type used by an existing agent, identified by its agent ID.
get-agent-type-by-agent-id: func(agent-id: agent-id) -> option<registered-agent-type>;

/// Constructs a string agent-id from the agent type and its constructor parameters
/// and an optional phantom ID.
///
/// `input` is a value tree whose root encodes the constructor's parameter list.
make-agent-id: func(agent-type-name: string, input: schema-value-tree, phantom-id: option<uuid>) -> result<string, agent-error>;
make-agent-id: func(agent-type-name: string, input: schema-value-tree, phantom-id: option<uuid>) -> result<agent-id, agent-error>;

/// Parses an agent-id (created by `make-agent-id`) into an agent type name and its constructor parameters
/// and an optional phantom ID.
///
/// The constructor parameters are returned as a self-contained typed value
/// (graph + value tree) so the receiver can interpret them without an
/// external schema registry.
parse-agent-id: func(agent-id: string) -> result<tuple<string, typed-schema-value, option<uuid>>, agent-error>;
parse-agent-id: func(agent-id: agent-id) -> result<tuple<string, typed-schema-value, option<uuid>>, agent-error>;

/// Creates a webhook that can be used to integrate with webhook driven apis.
/// When the created url is called with a post request, the provided promise-id is completed with the body of the post request.
Expand Down Expand Up @@ -56,7 +62,7 @@ interface host {
/// Final identity allocated for one remote invocation. For an ephemeral
/// target, `agent-id` contains the generated one-shot phantom ID.
record invocation-metadata {
agent-id: string,
agent-id: agent-id,
idempotency-key: string,
}

Expand Down Expand Up @@ -85,12 +91,21 @@ interface host {

/// An RPC client for invoking remote agents
resource wasm-rpc {
/// Constructs the RPC client connecting to the given target agent.
/// Creates an RPC client connecting to the given target agent.
///
/// `constructor` is a value tree whose root encodes the target agent
/// constructor's parameter list.
/// constructor's parameter list. This fail-fast form traps if the client
/// cannot be created and is intended for statically generated clients.
constructor(agent-type-name: string, %constructor: schema-value-tree, phantom-id: option<uuid>, agent-config: list<typed-agent-config-value>);

/// Creates an RPC client connecting to the given target agent.
///
/// `constructor` is a value tree whose root encodes the target agent
/// constructor's parameter list. This fallible form returns an RPC error
/// if the client cannot be created and is intended for reflective and
/// other dynamic clients.
create: static func(agent-type-name: string, %constructor: schema-value-tree, phantom-id: option<uuid>, agent-config: list<typed-agent-config-value>) -> result<wasm-rpc, rpc-error>;

/// Invokes a remote method with the given parameters, and awaits the result.
///
/// `input` encodes the method's parameter list. The returned result is
Expand Down
Loading
Loading