Runtime reflection with Effect
SkillAI & modelsLets your agent discover available components at runtime and build matching clients to call them.
Use Runtime reflection with Effect in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Runtime reflection with Effect and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Runtime reflection with Effect skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
About this skill
Composes caller-defined static, discovered, and fully dynamic Golem clients with Effect. Use when schemas are caller-owned or discovered at runtime, or a durable identity must be rebound.
What this skill tells your AI
The instructions your AI receives, as published by golemcloud/golem in golem-skills/skills/effect/golem-agent-reflection-effect/SKILL.md and read by Ahel’s review.
Normal RPC is the non-reflective baseline and uses the ordinary client from a shared source
definition. Reflection adds caller-defined static, discovered, and fully dynamic clients. Import
Reflection from @golemcloud/effect-golem. Reflection operations are Effects and their host
requirements flow through the agent dispatcher; compose them in Effect.gen rather than running
them as promises.
import { Effect } from "effect"
import { Reflection } from "@golemcloud/effect-golem"
const callEcho = Effect.gen(function* () {
const types = yield* Reflection.getAllAgentTypes
const target = yield* Reflection.getAgentType("ReflectionTarget")
if (!target) return yield* Effect.fail("ReflectionTarget is not visible")
const echo = target.method("echo")
if (!echo) return yield* Effect.fail("echo is not registered")
if (target.mode !== "durable") return yield* Effect.fail("ReflectionTarget is not durable")
const client = yield* target.client.get({ name: "target" })
const method = yield* client.method("echo")
const result = yield* method.invoke({ message: "hello" })
return { listed: types.some((type) => type.name === target.name), value: result.value }
}).pipe(Effect.scoped)
getAgentType and getAgentTypeByAgentId return undefined when the type is not visible in the
current environment. Host and malformed-schema discovery failures are typed Effect errors. Agent
identity strings are environment-scoped: parse them with AgentIdentity.parse before
identity-specific discovery or binding. Use constructorInput and method input/output
SchemaRef values to validate, pack, unpack, or render JSON schemas. Normal reflected calls use
JSON; use the *Value variants only for schema-native values. Durable factories expose get and
phantom operations; ephemeral factories expose getPhantom for known IDs and newPhantom for
fresh logical clients, but no ordinary get. Reflected invocation results include host metadata
and a value except for unit-returning methods, which omit it.
Pass optional creation-time overrides as a second argument to reflected factories. Use { path, value } entries containing canonical JSON with get, getPhantom, or newPhantom; use the *Value factory variants for schema-native values. For an existing identity, use target.bindWithJsonConfig(identity, entries) or target.bindWithConfig(identity, nativeEntries). A fully defined client exposes bindWithConfig(identity, { overrides }); a method-only client exposes bindWithEntries(identity, nativeEntries). These operations remain Effects and require a scope. Known declarations reject unknown paths, secret fields, and invalid values before opening RPC. An existing durable worker retains its initial configuration even if overrides are passed while binding.
Use defineAgentClient({ name, id, methods, mode?, config? }) for a caller-defined static full client.
Its agentId(input) creates a parsed identity, and identity.client(clientDefinition) checks the declared
name and constructor schema locally before opening RPC. A caller-defined static method-only
defineAgentClient({ methods }) has no lifecycle factory or discovery; binding assumes durable
result semantics. An unimplemented defineAgent spec is also a full client definition. Durable
reflected types bind through the same function after schema validation. Fully defined ephemeral specs
and reflected ephemeral types reject generic existing-ID binding; use known/fresh phantom
factories. Constructing an ephemeral identity requires a phantom ID.
import { Effect, Schema } from "effect"
import { AgentIdentity, defineAgentClient, method } from "@golemcloud/effect-golem"
import type * as CoreTypes from "golem:core/types@2.0.0"
const Echo = defineAgentClient({
name: "Echo", id: { name: Schema.String },
methods: { echo: method({ input: { message: Schema.String }, success: Schema.String }) },
})
const program = (inputTree: CoreTypes.SchemaValueTree) => Effect.scoped(Effect.gen(function* () {
const identity = yield* Echo.agentId({ name: "main" })
const full = yield* identity.client(Echo)
const one = yield* full.echo({ message: "full" })
const methods = defineAgentClient({ methods: Echo.methods })
const two = yield* (yield* identity.client(methods)).echo({ message: "method only" })
const parsed = yield* AgentIdentity.parse(identity.encoded)
const dynamic = yield* parsed.dynamicClient()
const raw = yield* dynamic.method("echo").invoke(inputTree)
return { one, two, raw }
}))
// inputTree is supplied by the caller as a native SchemaValueTree.
// raw contains invocation metadata and a native output tree.
Validation and optional values
Effect errors preserve the boundary that failed:
- Client definition compilation and
agentIdencode caller-owned schemas. A full client validates the declared name, constructor value, and config overrides before opening RPC. A method-only client owns only method schemas and raw typed config entries. - Reflected
SchemaRefpacking and invocation apply all discovered restrictions and command constraints locally. Discovery returns immutable metadata and schema snapshots. - The host remains authoritative for visibility, authorization, environment-scoped identity resolution, effective configuration, and the deployed input schema.
- Awaited calls verify unit/non-unit cardinality and declared output shape.
RemoteCallError,ToolRuntimeError, custom agent errors, and custom tool payloads remain tagged values in the Effect error channel.
In Normal RPC and caller-defined static inputs, use Schema.optional(...) in a struct and omit the property. Reflected JSON accepts either an omitted option<T> record field or an explicit null as absent; re-encoding may include the field with null. Reflection JSON Schema does not list that field in required:
import { Effect } from "effect"
import { Reflection } from "@golemcloud/effect-golem"
const optionalCall = Effect.scoped(Effect.gen(function* () {
const type = yield* Reflection.getAgentType("SearchAgent")
if (!type || type.mode !== "durable") return yield* Effect.fail("SearchAgent unavailable")
const client = yield* type.client.get({ tenant: "docs" })
const search = yield* client.method("search")
const checked = search.definition.input.validateJson({ query: "golem" })
if (!checked.success) return yield* Effect.fail(checked.issues)
return yield* search.invoke({ query: "golem" })
}))
Canonical JSON represents s64 and u64 as base-10 strings. Duration is { nanoseconds: "..." }; quantity uses a decimal-string mantissa. Smaller integers remain JSON numbers. JSON Schema projections expose the same canonical patterns and exact range metadata.
Capabilities, futures, and streams cannot be packed or unpacked as reflected JSON. Their reflection JSON Schema projection is unsatisfiable; use schema-native value APIs for those leaves.
Cancellation, streams, and cleanup
Agent calls are Effects: fiber interruption cancels result observation, while remote side effects may already have happened. A scheduled call returns a scoped cancel Effect. Keep the scope open until the cancellation token is no longer needed:
import { Effect } from "effect"
import { Reflection } from "@golemcloud/effect-golem"
const scheduled = Effect.scoped(Effect.gen(function* () {
const type = yield* Reflection.getAgentType("CounterAgent")
if (!type || type.mode !== "durable") return yield* Effect.fail("CounterAgent unavailable")
const client = yield* type.client.get({ name: "main" })
const add = yield* client.method("add")
const pending = yield* add.schedule({ seconds: 1n, nanoseconds: 0 }, { by: 2 })
yield* pending.cancel
}))
Streams and opaque capabilities have no canonical JSON form. Use the *Value APIs, transfer an owned handle once, and consume returned streams inside the scope. Reflected tool startJson/startValue exposes independent stdout, stderr, result, collect, and cancel Effects. collect concurrently waits for the result and both outputs and gives the result error precedence. Scope closure releases observers and owned handles; invoke cancel when the remote operation itself must be cancelled.
Discovery to a fully dynamic agent
Retain the discovered method snapshot and apply it explicitly around the
dynamic call. dynamicClient() does not inherit the snapshot's validation
policy:
import { Effect } from "effect"
import { Reflection } from "@golemcloud/effect-golem"
const dynamicSearch = Effect.scoped(Effect.gen(function* () {
const type = yield* Reflection.getAgentType("SearchAgent")
const method = type?.method("search")
if (!type || type.mode !== "durable" || !method)
return yield* Effect.fail("SearchAgent.search is unavailable")
const input = method.input.packJson({ query: "golem", cursor: null })
const identity = yield* type.agentId({ tenant: "docs" })
const dynamic = yield* identity.dynamicClient()
const result = yield* dynamic.method(method.name).invoke(input).pipe(
Effect.catch((error) =>
Effect.logError("dynamic search failed", error).pipe(
Effect.andThen(Effect.fail(error)),
),
),
)
if (!method.output || result.value === undefined)
return yield* Effect.fail("search returned an unexpected unit result")
const outputCheck = method.output.validateValue(result.value)
if (!outputCheck.success) return yield* Effect.fail(outputCheck.issues)
return method.output.unpackJson(result.value)
}))
Effect.scoped releases the dynamic RPC connection. If packed values carry
owned streams, transfer each input once and consume or close every returned
stream before the scope ends.
Signals
- GitHub stars
- 2k
- Forks
- 210
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
golem-agent-reflection-effect- Source
- github.com/golemcloud/golem
Related picks
Skill · mattpocock
The pick for TypeScripttypescript-pro
Skill · jeffallan
The pick for TypeScriptskill-creator
Skill · anthropics
More in AI & modelstriage
Skill · mattpocock
More in AI & modelsalgorithmic-art
Skill · anthropics
More in AI & modelscode-review-and-quality
Skill · addyosmani
More in AI & models