workersdk

package
v0.25.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 9, 2026 License: AGPL-3.0 Imports: 16 Imported by: 0

Documentation

Overview

Package workersdk is the client half of the Worker protocol: leasing jobs of named types from a running atlas, reporting how each ended, answering the form lookups a Worker Type serves, and asking before acting where the server governs the Worker (ADR-0007, ADR-0168, ADR-0473, ADR-0475).

It is the seed of the Worker SDK of ADR-0535: what a Worker Type built outside this repository depends on. That is why it imports nothing of the engine — not job, not state, not compiler — and a test holds it to that (sdk_deps_test.go). The built-in Worker Types in package worker are written against it; package worker keeps its old names as aliases, so nothing that worked against worker.New stops working.

The contract for a model-authored job type's code is the one the script worker established: the job arrives as JSON on stdin, and whatever JSON object the command writes to stdout becomes the variables the job completes with (CmdExec). A non-zero exit fails the job, carrying the command's stderr as the message an operator reads on the incident.

Index

Constants

View Source
const (
	// EnvServer is the base URL of the atlas the instance works for.
	EnvServer = "ATLAS_SERVER_URL"
	// EnvWorkerID is the instance's id in the Workers view.
	EnvWorkerID = "ATLAS_WORKER_ID"
	// EnvToken is the bearer credential the instance authenticates with.
	EnvToken = "ATLAS_TOKEN"
	// EnvTypeID, EnvTypeVersion and EnvTypeDigest are the installed release the
	// supervisor verified before it started this process: the handshake's identity.
	EnvTypeID      = "ATLAS_WORKER_TYPE_ID"
	EnvTypeVersion = "ATLAS_WORKER_TYPE_VERSION"
	EnvTypeDigest  = "ATLAS_WORKER_TYPE_DIGEST"
	// EnvConfig is the instance's configured Workers as JSON,
	// {"workers":[{"name":"…","config":{…}}]}: each configuration with its secrets
	// resolved and checked against the Worker Type's schema (ADR-0535 §4). Absent when
	// nothing is configured.
	EnvConfig = "ATLAS_WORKER_CONFIG"
)

The launch contract: what atlas hands a Worker Type executable it launches from an installed artifact (ADR-0535 §2). The executable is started with no arguments and this environment, and nothing else of the server's; it reads its own configuration from what it is given here and from the Worker protocol.

View Source
const (
	DefaultLease   = 5 * time.Minute
	DefaultWait    = 30 * time.Second
	DefaultMaxJobs = 1
	DefaultRetry   = 5 * time.Second
	// DefaultMaxQueries is higher than DefaultMaxJobs on purpose: a query is one short
	// read that a person is waiting on, and a second person searching should not wait
	// for the first.
	DefaultMaxQueries = 4
)

Defaults, chosen so an unconfigured worker is safe rather than surprising: it takes one job at a time, holds it for five minutes, and parks for thirty seconds per poll.

View Source
const EventPollFailed = "worker.poll_failed"

EventPollFailed is the event name the worker's own warnings carry under the key "event": a poll that failed and will be retried, or a query answer the server did not take. It is atlas's registered event worker.poll_failed (ADR-0142); a test in package worker holds the two equal, because this package may not import atlas's catalogue.

View Source
const ProtocolVersion = 1

ProtocolVersion is the major version of the Worker protocol this client speaks (docs/contracts/worker-protocol). It is sent on every poll; a server that does not accept it refuses the poll with 400, naming the versions it does accept, and the worker logs that and keeps retrying, so an upgrade of either side is visible rather than silent.

Variables

View Source
var ErrNoDocuments = errors.New("workersdk: no document access here: documents are read and stored for a job a worker runs")

ErrNoDocuments is what ReadDocument and UploadDocument answer outside a job a worker runs — a handler called directly in a test, say — where there is no lease to read under and no server to store to.

Functions

func DefaultRetryBackoff

func DefaultRetryBackoff(retriesLeft int32) int64

DefaultRetryBackoff is the delay the server's in-process runner waits before it retries a failed job (ADR-0111, amendment 2026-11-04): a job with one retry left waits a minute, one with more left waits fifteen seconds, one with none raises its incident at once. It is the server's policy restated here, because this package may not import the runner that owns it; a test in package worker holds the two equal.

func DocumentID

func DocumentID(v any) string

DocumentID returns the id a value names when it is a document reference, and "" for anything else: a decoded JSON object or its JSON text.

func Refused

func Refused() error

Refused is the error a handler returns for a refused action.

func UploadDocument

func UploadDocument(ctx context.Context, name, contentType string, data []byte) (map[string]any, error)

UploadDocument stores data as a new document and answers the reference a variable holds. The document is the worker's until the job completes with the reference.

Types

type Authorization

type Authorization struct {
	// Enforcement is "off" for a Worker without an identity policy, else "shadow" or
	// "enforce".
	Enforcement string `json:"enforcement"`
	Permitted   bool   `json:"permitted"`
	Enforced    bool   `json:"enforced"`
	ReasonCode  string `json:"reasonCode"`
	DecisionKey uint64 `json:"decisionKey"`
	// Job is the parameters the decision covers. A handler acts on these and not on
	// what it was handed at lease time.
	Job        map[string]any `json:"job"`
	Credential *struct {
		Type        string `json:"type"`
		AccessToken string `json:"accessToken"`
		ExpiresAt   int64  `json:"expiresAt"`
	} `json:"credential"`
}

Authorization is the server's answer.

func Authorize

func Authorize(ctx context.Context) (*Authorization, error)

Authorize asks the server whether the job in ctx may act. It returns nil, nil when there is nobody to ask — a handler run outside a worker, or against a server built before this step existed — which a handler treats as the legacy answer.

func (*Authorization) Governed

func (a *Authorization) Governed() bool

Governed reports whether the answer came from a decision at all.

type BPMNError

type BPMNError struct {
	Code    string
	Message string
}

BPMNError is how a handler ends a job in a BPMN error instead of a result (ADR-0089): return one — wrapped or not — and the worker throws Code rather than failing the job. An error boundary or error event subprocess matching Code catches it and the instance takes the error path, with no retry spent. With none in reach the job waits on an incident naming Code and Message until an operator resolves it.

It is for an answer the process has a path for — a card declined, a recipient that does not exist — not for a fault: anything else a handler returns fails the job and is retried as before.

The engine's in-process runner uses this same type under its own name (job.BPMNError is an alias of it), so a handler shared by both paths — a script task's run — throws the same error on either. It is defined here rather than there because this package may depend on nothing of the engine.

func (*BPMNError) Error

func (e *BPMNError) Error() string

type CmdExec

type CmdExec struct {
	Name string
	Args []string
	// Env is added to the process's own environment.
	Env []string
}

CmdExec runs an operating-system command per job: the job arrives as JSON on stdin, and the JSON object the command writes to stdout becomes the variables the job completes with (no output completes with none). A non-zero exit fails the job and carries the command's stderr, which is what an operator reads on the incident.

func (CmdExec) Run

func (c CmdExec) Run(ctx context.Context, j Job) (map[string]any, error)

Run implements Exec.

type CompletingExec

type CompletingExec interface {
	RunCompleting(ctx context.Context, j Job) (Outcome, error)
}

CompletingExec is the widest exec shape: work that completes with more than variables. Exec is the decision-less special case, and the ladder is deliberately the one the job package already has (Handler / OutputHandler / CompletingHandler) so the two sides of the protocol are described the same way.

A handler implementing both is used as a CompletingExec.

type CompletingExecFunc

type CompletingExecFunc func(ctx context.Context, j Job) (Outcome, error)

CompletingExecFunc adapts a function to CompletingExec.

func (CompletingExecFunc) Run

func (f CompletingExecFunc) Run(ctx context.Context, j Job) (map[string]any, error)

Run implements Exec by discarding the decision half, so a CompletingExecFunc can stand wherever an Exec is required.

func (CompletingExecFunc) RunCompleting

func (f CompletingExecFunc) RunCompleting(ctx context.Context, j Job) (Outcome, error)

RunCompleting implements CompletingExec.

type ConfiguredWorker

type ConfiguredWorker struct {
	Name   string         `json:"name"`
	Config map[string]any `json:"config"`
}

ConfiguredWorker is one configured Worker an instance serves.

func LaunchConfig

func LaunchConfig(getenv func(string) string) ([]ConfiguredWorker, error)

LaunchConfig reads the configured Workers of the launch contract from getenv. None configured is an empty list, not an error; a value that is not the contract's shape is an error, because serving on a misread configuration is worse than not starting.

type ConnectorPayload

type ConnectorPayload struct {
	Kind   string         `json:"kind"`
	Fields map[string]any `json:"fields"`
}

ConnectorPayload is a resolved task: what to do, with no engine concepts in it.

type DecisionReport

type DecisionReport struct {
	DecisionID string         `json:"decisionId"`
	Inputs     map[string]any `json:"inputs,omitempty"`
	Outputs    map[string]any `json:"outputs,omitempty"`
	Trace      string         `json:"trace,omitempty"`
}

DecisionReport is a worker's account of a decision it evaluated: which decision, the inputs it was handed, the outputs it got and the service's trace.

It is an account, not a record. The engine stamps which element instance it belongs to from the job's own lease, so a report cannot attach itself to a task other than the one it holds.

type Document

type Document struct {
	ID          string
	Name        string
	ContentType string
}

Document describes a document read with ReadDocument.

func ReadDocument

func ReadDocument(ctx context.Context, id string) ([]byte, Document, error)

ReadDocument reads the bytes of the document id for the job running in ctx.

type Exec

type Exec interface {
	Run(ctx context.Context, j Job) (map[string]any, error)
}

Exec does one job's work and returns the variables it completed with, or an error that fails the job.

type ExecFunc

type ExecFunc func(ctx context.Context, j Job) (map[string]any, error)

ExecFunc adapts a function to Exec.

func (ExecFunc) Run

func (f ExecFunc) Run(ctx context.Context, j Job) (map[string]any, error)

Run implements Exec.

type Identity

type Identity struct {
	ID      string `json:"id"`
	Version string `json:"version"`
	Digest  string `json:"digest,omitempty"`
}

Identity names a Worker Type the way its manifest does: a stable reverse-DNS id, a semantic version, and the sha256 digest of the artifact when the instance knows it ("sha256:" and 64 hex digits).

type Job

type Job struct {
	JobKey             uint64 `json:"jobKey"`
	Type               string `json:"type"`
	ProcessInstanceKey uint64 `json:"processInstanceKey"`
	ElementInstanceKey uint64 `json:"elementInstanceKey"`
	ProcessDefKey      uint64 `json:"processDefKey"`
	ElementID          string `json:"elementId"`
	Retries            int32  `json:"retries"`
	LeaseToken         uint64 `json:"leaseToken"`
	// IdempotencyKey is the same for every delivery of this task execution and
	// unique to it: hand it to a target that filters duplicates, or check by it before
	// acting, so a job delivered twice does its effect once.
	IdempotencyKey string         `json:"idempotencyKey"`
	Variables      map[string]any `json:"variables"`
	// Worker is a task the engine resolved into plain values, present
	// only for a kind the server no longer serves itself (ADR-0168). Nil for a plain
	// job-worker task, whose work is the customer's own command.
	Connector *ConnectorPayload `json:"connector,omitempty"`
}

Job is one leased job as the engine hands it over.

type Options

type Options struct {
	// Server is the base URL of the Atlas to work for, e.g. http://localhost:8080.
	Server string
	// ID names this worker. It rides on every lease and every report, and it is what
	// the Workers view shows — so give each deployment its own.
	ID string
	// Token, when set, is sent as a bearer credential on every request.
	Token string
	// BrokerKey, when set, is presented when this worker asks whether it may act
	// (Authorize). A server supervising this worker hands it over (ATLAS_BROKER_KEY)
	// only to the process serving a Worker whose credential it brokers per job; without
	// it, the server releases no person's token to this process.
	BrokerKey string
	// Handlers is what this worker serves, keyed by job type. A worker with none is a
	// misconfiguration rather than a process that polls forever over nothing.
	Handlers map[string]Exec
	// Lease is how long the engine holds a job for this worker. It must comfortably
	// exceed how long the work takes: an elapsed lease puts the job back on offer, and
	// the report that finally arrives is fenced out.
	Lease time.Duration
	// Wait is how long a poll may block waiting for work before coming back empty.
	Wait time.Duration
	// Retry is how long to wait after a failed poll before trying again. A worker
	// must not exit because the server was restarting, or because the process using
	// its job type is not deployed yet — both are ordinary and both resolve.
	Retry time.Duration
	// MaxJobs is how many jobs of one type this worker runs at once. A poll leases
	// only as many as there are free places for, so every leased job starts the
	// moment it arrives: leased work nobody is running is work nobody else can take
	// either, and its lease runs down while it waits (ADR-0440).
	MaxJobs int
	// Workers are the worker names this worker holds credentials for, reported
	// to the engine on every poll. Only the worker knows them — once a kind is
	// offloaded the engine holds no credential for it — and the Workers view
	// subtracts them from what deployed models reference to show which names are
	// configured nowhere (ADR-0168). Empty for a worker that serves only plain job
	// types, which need no credential of their own.
	Connectors []string
	// Endpoints are where those workers connect, reduced to scheme, host and port and
	// keyed by worker name — what lets an architecture sketch draw the system a worker
	// reaches when the engine holds no configuration for it (ADR-0471). Reported beside
	// Connectors on every poll; a name with no entry is drawn as holding its endpoint
	// itself.
	Endpoints map[string]string
	// Queries are the form lookups this worker answers, keyed by the job type of the
	// Worker Type answering them (ADR-0473). A query is not a
	// job: nothing about it is durable, and it is polled on a path of its own beside the
	// job poll, under the same identity and for the same Workers.
	Queries map[string]QueryFunc
	// MaxQueries is how many queries of one type this worker answers at once.
	MaxQueries int
	// RetryBackoff is how long a job this worker failed waits before it is offered
	// again, given the attempts it has left after the failure, in nanoseconds; nil means
	// [DefaultRetryBackoff], the delay the server's in-process runner waits too
	// (ADR-0111, amendment 2026-11-04). A Worker Type that runs out here meets the same
	// replication delays as one that runs inside the server — a group Microsoft Graph
	// created a moment earlier is not yet found — so it waits as long before it raises
	// an incident. NoRetryBackoff retries at once.
	RetryBackoff func(retriesLeft int32) int64
	// NoRetryBackoff offers a failed job again at once, as before the amendment; tests
	// that drive a failing job to its incident use it.
	NoRetryBackoff bool
	// HTTP is the client to use; nil means a default one bounded by Lease + Wait.
	HTTP *http.Client
	// Identity is the Worker Type this instance runs, reported on every poll as part of
	// the protocol handshake (ADR-0535 §3) and shown in the server's Workers view. Nil
	// reports none, which a server accepts: an instance an operator runs by hand may
	// not be a packaged Worker Type at all.
	Identity *Identity
	// Logger receives the few lines the worker writes itself — a poll that failed and
	// will be retried, a query answer the server did not take. Each carries the event
	// name under the key "event" ([EventPollFailed]), so an operator can alert on it
	// whatever logger a Worker Type uses. Nil means [slog.Default]; atlas's own worker
	// leaves it nil, so these lines go through the handler `atlas worker` installs.
	Logger *slog.Logger
}

Options configures a Worker.

func LaunchOptions

func LaunchOptions(getenv func(string) string) Options

LaunchOptions reads the launch contract from getenv (os.Getenv in a real process): the server, the instance id, the token and the identity. A Worker Type adds its Handlers and Queries and passes the result to New.

type Outcome

type Outcome struct {
	Variables map[string]any
	Decision  *DecisionReport
	// ToolCalls is a worker's account of what an agent chose to run next
	// (ADR-0253/ADR-0254). It is the second thing in the sentence above: a round's
	// answer is usually not variables at all, it is a choice of activities, and a
	// worker deciding the round is the only one who knows what the model said.
	//
	// Empty is an answer, not an omission — the agent saying the run is finished —
	// so a round that ends completes with variables like any other job.
	ToolCalls []ToolCallReport
}

Outcome is everything a job's work produced: the variables it completes with, and — for a central business rule task — the decision evaluation to retain.

It mirrors the in-process runner's job.Completion, and for the same reason: a decision's record is durable (ADR-0066), so once the evaluation happens on a worker the worker has to be able to report it. Every other kind completes with variables alone, which is why Exec stays the ordinary shape.

func RunHandler

func RunHandler(ctx context.Context, h Exec, j Job) (Outcome, error)

RunHandler runs one job through whichever shape its handler implements. A handler that only knows variables is the ordinary case and stays untouched; one that can report a decision is asked for the wider outcome. The worker calls it for every job; it is exported so a Worker Type can test its handler through the same door.

type QueryFunc

type QueryFunc func(ctx context.Context, connector string, q lookup.Query) (lookup.Page, error)

QueryFunc answers one form lookup for the Worker named connector: a page of records or the *lookup.Error it failed with (ADR-0473). Any other error is reported as the provider being unavailable — and only as that, because an error's text is the one place a search term could leave this process.

type ToolCallReport

type ToolCallReport struct {
	Tool      string         `json:"tool"`
	CallId    string         `json:"callId,omitempty"`
	Arguments map[string]any `json:"arguments,omitempty"`
}

ToolCallReport is a worker's account of one tool an agent chose for the next round: which of the container's tools to run, the model's own id for the call, and the arguments it supplied for that tool's declared parameters.

Like a DecisionReport it is an account, not a record — and the difference matters more here, because a tool call does not describe work that happened, it *causes* work to happen. So the engine reads which container this is from the leased job, and resolves Tool against that container's compiled tool index before anything is activated. A worker cannot name an activity the model does not carry and have it run; naming one it does not carry raises the incident ADR-0253 already defines.

type Worker

type Worker struct {
	// contains filtered or unexported fields
}

Worker leases jobs of the types it handles and reports what happened. Build one with New.

func New

func New(opts Options) *Worker

New builds a worker, filling in defaults.

func (*Worker) AnswerQueriesOnce

func (w *Worker) AnswerQueriesOnce(ctx context.Context, wait time.Duration) error

AnswerQueriesOnce polls every query type once, waiting up to wait for a query, and answers whatever it is given before it returns. It is what a test drives; a zero wait is an idle poll, which makes this worker present for its Workers.

func (*Worker) AuthorizingContext

func (w *Worker) AuthorizingContext(ctx context.Context, j Job) context.Context

AuthorizingContext puts the question for j within reach of its handler: Authorize called with the context it returns asks the server about j. The worker does this for every job it runs; it is exported so a handler run outside the poll loop — in a Worker Type's own test — can be asked the same way.

func (*Worker) DocumentContext

func (w *Worker) DocumentContext(ctx context.Context, j Job) context.Context

DocumentContext puts the document store within reach of j's handler. The worker does this for every job it runs; it is exported so a handler run outside the poll loop can be given the same access.

func (*Worker) Run

func (w *Worker) Run(ctx context.Context) error

Run works until ctx is cancelled. Each type is polled by its own goroutine, so a slow queue never starves another, and each type runs up to MaxJobs jobs at once. Run returns only once every job it started has returned.

func (*Worker) RunOnce

func (w *Worker) RunOnce(ctx context.Context) error

RunOnce polls every handled type once and works whatever it is given. It is what a test drives, and what a one-shot invocation ("drain the queue and exit") wants.

Directories

Path Synopsis
Package lookup is the wire shape of a form lookup: what the server asks a Worker Type when a person searches a remote data source from a task form, and what the Worker Type answers (ADR-0473).
Package lookup is the wire shape of a form lookup: what the server asks a Worker Type when a person searches a remote data source from a task form, and what the Worker Type answers (ADR-0473).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL