@serverless-agents/harness
v0.1.0
Published
TypeScript contracts for durable Serverless Agents harnesses
Readme
TypeScript harness SDK
@serverless-agents/harness is the public author API for guest API v1. The SDK
provides types; the platform-owned runner supplies web globals and ctx bindings.
It has no platform credentials, HTTP client, or bootstrap side effects.
import type { HarnessDefinition } from '@serverless-agents/harness';
export default {
async create(construction) {
const restored = await construction.state.get();
return {
async run(ctx) {
const current = await ctx.state.get();
return {
final_changes: {
state: {
expected_revision: current.revision,
body: { ...current.body, started: true },
},
events: [],
inputs: [],
},
};
},
};
},
} satisfies HarnessDefinition;Construction and hooks
The default export has an asynchronous create(ctx) factory returning an object
with required asynchronous run(ctx) and optional initialize(ctx)/cleanup(ctx).
Construction can read state/secrets and identity/config/limits. Invocation contexts
also expose event/inbox reads, atomic commits and managed helpers. Initialize and
cleanup cannot resolve inputs; Run must explicitly include input dispositions.
Returning void means empty final changes. Returning typed final changes requests
an atomic lifecycle completion through the authoritative platform transaction.
One isolate and harness instance belongs to one leased session activation. Run continuations keep ordinary JavaScript memory. Release disposes it; replacement and abort cleanup construct a fresh isolate and restore persisted state.
Platform bindings
ctx.state.get()returns the persisted object and revision.ctx.events.get(id)/.list(query?)read session events.ctx.inbox.get(id)/.list(query?)read durable inputs..wait()awaits a coalesced availability hint. It does not consume inputs; list again after waking. The runner retains a watch cursor across list/wait so an intervening notice is not lost. Lease observations and host reconciliation recover missed transport hints.ctx.secrets.get(name, { refresh: true })explicitly bypasses the provider's short cache. Names come from private worker bindings; provider references and worker credentials are never installed in JavaScript.ctx.prepare(changes, operationId?)validates and freezes semantic retry data.ctx.commit(changes)prepares one operation, then commits it.ctx.commitPrepared(operation)reuses an operation identity. On failure the thrownGuestError.operationretains the exact submitted snapshot. A failed transport or cancelled commit can have an unknown durable outcome; reuse that identity when retrying. Never assume rollback or generate a replacement ID.ctx.spawn(async signal => ...)returns{ join(), cancel() }. Helpers share the isolate's JavaScript thread, and platform/fetch operations inherit the helper's cancellation. Awaitjoin()for its result. Outstanding helpers stop with their invocation; detached work never prolongs durable lifecycle authority.
GuestError.code is cancelled, scope_closed, validation, platform, or
resource_limit. platform_code preserves the platform error code (for example,
revision_conflict) when present. Web APIs retain standard web errors such as AbortError.
Revision/sequence/limit values use their generated decimal-string wire types;
application JSON uses ordinary JavaScript number semantics.
Web environment and imports
The runtime provides HTTP/HTTPS fetch and standard request/response/header/body
objects; web streams; abort controllers/signals; numeric timers and microtasks;
encoding, URL/search parameters, Blob/File/FormData, base64, performance and console.
Crypto provides secure randomness/UUIDs and the pinned Deno Web Crypto implementation.
The qualified standard algorithms are recorded in the
runtime environment guide.
There is no cookie jar, automatic application-request retry policy, filesystem,
process access, Deno/Node namespace, browser DOM, guest workers or shared-memory
threading. Timer callbacks are functions. AbortSignal.timeout validates its
millisecond duration using Web IDL and its timer belongs to the current scope.
Build application dependencies into one JavaScript ESM file. Type-only SDK imports
are erased. Runtime npm/remote/dynamic imports and application ext: imports are
rejected. Runner bootstrap modules are private. CLI scaffolding, strict checks, bundling,
source maps, deployment and lightweight local execution are implemented.
Scope ownership
Construction and each hook get separate scopes and abort signals. The hook's
Promise determines completion. Timers, helpers, outstanding requests and native
stream/body handles close before another scope opens. Materialized plain data can
remain in the instance; unread response bodies and old contexts cannot retain
access. Saving an old ctx or continuing an old Promise does not grant the next
hook's authority. Use ctx.signal for custom cancellation-aware work.
Guest CPU/allocation/operation budgets are enforced by the runner; limits are exposed as immutable activation snapshots. Console records carry session/worker/invocation identity and timestamps through bounded runner logging.
Package checks
pnpm --filter @serverless-agents/harness generate
pnpm --filter @serverless-agents/harness testThe package builds ESM and declarations in dist/. Generated DTOs come from
schemas/javascript-contracts.openapi.json; handwritten interfaces describe the
runner's callable guest API. The echo example shows explicit
atomic state/event/input changes and fresh-instance cleanup.
Read-only bundled assets are available in every scope through await ctx.assets.read("assets/prompt.txt"), returning a Uint8Array. Only manifest-listed paths are accessible. Reads share the pending-operation, buffered-body and external-memory budgets and stop at scope teardown; streaming asset access is not implemented.
Standalone tooling
Install the approved CLI + runner archive and Node/pnpm, then:
serverless-agents init my-agent
cd my-agent
pnpm install
pnpm check
serverless-agents run --input '{"message":"hello"}'
pnpm dev
serverless-agents login
serverless-agents deployThe SDK package includes pinned TypeScript/esbuild tooling and ./globals types
for the supported runtime surface. The scaffold extends ./tsconfig, excluding
shared-memory globals and browser ambient libraries from its editor profile. ./tooling executes in Node; it is
never imported by guest code. Successful builds are content-addressed under
.serverless-agents/builds. Declare {path,file} assets in harness.json. Local
values live in a 0600, Git-ignored .dev.secrets.json; tooling rejects it as
an import or asset. Declare required names in harness.json's secrets array.
serverless-agents secrets put NAME prompts for a remote value, stores it encrypted,
and adds only the name to configuration. secrets list returns metadata and
secrets delete NAME removes/revokes the managed reference. Deployment checks
required remote bindings without uploading local values. Failed builds leave the prior
worker alive; successful reload starts a fresh session without replay or implicit
abort cleanup. :abort explicitly runs cleanup, :reset starts another session.
No platform source or credentials are needed for local execution. Full distributed
recovery and Linux host limits require private integration/remote project testing.
Set the deployment name in harness.json (the scaffold does this). First deploy creates
the remote harness and binds the folder to its organization. Later deploys publish
new releases and promote for new sessions. Interrupted deploys resume matching bytes;
--resume uses the original immutable package and --new supersedes it. Remote sessions
retain their existing release pins. org use NAME switches the active organization;
org use NAME --local explicitly changes the folder binding.
The new SDK/toolchain must be supplied or published before registry installation.
