Adding Resource Quotas to an Effect Golem Agent
Golem provides distributed resource quotas through the Quota namespace from
@golemcloud/effect-golem. The manifest defines each resource's policy; agent code acquires a
token for that resource and performs Effect-based reservations against it.
1. Define Resources in the Application Manifest
Add resource definitions under resourceDefaults in golem.yaml, scoped per environment:
resourceDefaults:
prod:
api-calls:
limit:
type: Rate
value: 100
period: minute
max: 1000
enforcementAction: reject
unit: request
units: requests
storage:
limit:
type: Capacity
value: 1073741824 # 1 GB
enforcementAction: reject
unit: byte
units: bytes
connections:
limit:
type: Concurrency
value: 50
enforcementAction: throttle
unit: connection
units: connections
Limit Types
Raterefillsvaluetokens everyperiod(second,minute,hour, orday), capped atmax. Use it for API-call rates.Capacityis a fixed pool ofvaluetokens. Used capacity does not refill. Use it for storage or budget limits.Concurrencyis a pool ofvaluetokens returned when released. Use it to bound parallel connections or jobs.
Enforcement Actions
rejectmakesQuota.reserveorQuota.withReservationfail with a typedQuota.FailedReservationError, whoseestimatedWaitNanosisbigint | undefined.throttlesuspends the agent in the host until capacity is available.terminateterminates the offending agent in the host.
The resource name passed from code must exactly match the manifest key. Do not recreate limit or enforcement policy in the Effect implementation.
2. Acquire and Reuse a Token
Import Effect APIs from effect and quota APIs through the SDK's Quota namespace:
import { Effect, Schema } from "effect";
import { defineAgent, method, Quota } from "@golemcloud/effect-golem";
Quota.acquireQuotaToken is an Effect. Acquire it once in the agent's init Effect
and pass it to methods as state. This is the Effect equivalent of keeping
the token in the constructed agent's state; do not acquire a new token for every invocation or at
module top level.
const QuotaCallResult = Schema.Struct({
input: Schema.String,
success: Schema.Boolean,
});
export const QuotaAgent = defineAgent({
name: "QuotaAgent",
mode: "durable",
id: { instanceName: Schema.String },
methods: {
rateLimitedCall: method({
input: { input: Schema.String },
success: QuotaCallResult,
}),
},
}).implement({
init: () => Quota.acquireQuotaToken("api-calls", 1n),
methods: (token) => ({
rateLimitedCall: ({ input }) =>
Quota.withReservation(token, 1n, () =>
Effect.succeed({
used: 1n,
value: { input, success: true },
}),
).pipe(
Effect.catchTag("FailedReservationError", () =>
Effect.succeed({ input, success: false }),
),
Effect.orDie,
),
}),
});
The second acquisition argument is the expected amount per reservation and is used for fair
scheduling. All quota amounts are bigint values. In the example, Effect.catchTag recovers only
the normal reject outcome; Effect.orDie converts the remaining unexpected QuotaHostError into
an invocation failure instead of incorrectly reporting quota exhaustion. If callers should
recover from host failures, map them to a domain error declared by the method instead.
3. Reserve Fixed or Variable Costs
Quota.withReservation reserves an amount, runs an Effect, commits the reported usage, and returns
the callback's value:
const response = yield* Quota.withReservation(token, 1n, () =>
callSimpleApi().pipe(
Effect.map((value) => ({
used: 1n,
value,
})),
),
);
For variable-cost work, reserve the maximum expected cost and report actual usage:
const response = yield* Quota.withReservation(token, 4_000n, () =>
Effect.gen(function* () {
const result = yield* callLlm(prompt, { maxTokens: 4_000 });
return {
used: BigInt(result.tokensUsed),
value: result,
};
}),
);
The callback must return an Effect whose success value is { used: bigint, value: A }.
Quota.withReservation already manages its own scope; do not add a redundant Effect.scoped
around it. If the callback fails, dies, or is interrupted, the reservation finalizer commits 0n
and preserves the original cause.
4. Reserve and Commit Manually
Use Quota.reserve and Quota.commit for finer control. A manual reservation requires a Scope, so
wrap the complete lifetime in Effect.scoped:
const result = yield* Effect.scoped(
Effect.gen(function* () {
const reservation = yield* Quota.reserve(token, 100n);
const result = yield* doWork();
yield* Quota.commit(reservation, BigInt(result.actualUsage));
return result;
}),
);
Committing less than the reserved amount returns unused capacity. Committing more records debt
against the token. If the scope closes before an explicit commit, the SDK performs a best-effort
commit(0n); a successful explicit commit makes that finalizer a no-op.
5. Split Tokens Across Agent RPC
Use Quota.QuotaToken as the Effect Schema for quota-token method parameters and results. The SDK
codec converts between the live token and its wire record automatically:
const SummaryResult = Schema.Struct({
summary: Schema.String,
token: Quota.QuotaToken,
});
const summarize = method({
input: {
text: Schema.String,
token: Quota.QuotaToken,
},
success: SummaryResult,
});
Split from the parent, send the child token through an existing typed agent client, and merge the returned token:
const childToken = yield* Quota.split(token, 200n);
const summarizer = yield* SummarizerAgent.client.get({ name: "sum-1" });
const { summary, token: returnedToken } = yield* summarizer.summarize({
text,
token: childToken,
});
yield* Quota.merge(token, returnedToken);
return summary;
Quota.split fails with QuotaHostError if the child expected use exceeds the parent's current
expected use. Quota.merge requires tokens from the same resource and environment. A successful
merge consumes the returned child token, so do not use it afterward.
6. Update a Deployed Resource
Resource updates affect running agents without changing their Effect code:
golem resource update api-calls --limit '{"type":"rate","value":200,"period":"minute","max":2000}' --environment prod
Key Constraints
- Import
Quotafrom@golemcloud/effect-golem; there are no flat public quota functions and the host quota client is internal. - Acquire each shared token once in the agent implementation Effect and reuse it.
- Use
bigintamounts such as1n,200n, and4_000n. - Use
Quota.withReservationfor automatic lifetime management andEffect.scopedfor manualQuota.reserve/Quota.commitlifetimes. - Recover a rejected reservation with
Effect.catchTag("FailedReservationError", ...); do not use a broad catch that also hides body failures orQuotaHostError. - Resource names in code must exactly match their
golem.yamlresourceDefaultskeys. - Use
Quota.QuotaToken, notQuota.QuotaTokenRecord, in agent RPC schemas. The record is the inspectable wire representation, not the live token handle.