Skip to content
Browse the documentation
SDKs

Go SDK

Record agent actions, push facts from systems of record, read verdicts and draft contracts. Standard library only; Go 1.22+.

Last changed Markdown versionllms.txt

Record agent actions, push facts from systems of record, read verdicts and draft contracts. Standard library only; Go 1.22+.

go get github.com/kobikwelu/stateprf-svcs/sdk/go
export COMMITLAYER_API_KEY=spk_...          # the agent runtime's key: a *recorder* key (records actions, nothing else)
export COMMITLAYER_EVIDENCE_KEY=spk_...     # the pipeline's key: an *evidence* key bound to the sources it pushes for
# COMMITLAYER_API_URL defaults to https://api.commitlayer.ai. One credential per side of the line: the key that
# records an action can never also supply the facts that verify it.

Every method takes a context.Context and returns the API payload as map[string]any. The response shapes are documented in docs/API.md and still evolve, so the SDK keeps them untyped; decode into your own struct when you want static fields.

Record an action where it happens

import "github.com/kobikwelu/stateprf-svcs/sdk/go"

cl, err := commitlayer.New(commitlayer.WithAgentID("support-bot"))
if err != nil {
	log.Fatal(err)
}

func refund(ctx context.Context, orderID string, amount float64) (map[string]any, *commitlayer.Recorded, error) {
	return commitlayer.Do(ctx, cl, commitlayer.Action{
		ContractID: "refund_workflow",
		SubjectID:  orderID,
		ActionOptions: commitlayer.ActionOptions{
			Params:      map[string]any{"amount": amount},
			SubjectKind: "order",
			ActionID:    "refund:" + orderID, // retries update one claim instead of recording two
		},
	}, func(ctx context.Context) (map[string]any, error) {
		return payments.Refund(ctx, orderID, amount) // the agent's own work, unchanged
	})
}

result, rec, err := refund(ctx, "order_10231", 180.0)
fmt.Println(rec.Response["verification"].(map[string]any)["summary"].(map[string]any)["headline"])

The action is recorded before the function runs, so its request time precedes every effect. The function's return value is never changed and never consulted: verification reads the systems of record. If the API cannot be reached the function still runs, a warning is logged through log/slog, and rec.Err carries the recording error (fail-open, the default). Set FailOpen to a pointer to false to return the recording error without running the function.

An action that corrects an earlier one names it in Compensates:

commitlayer.ActionOptions{Compensates: "refund:order_10231"}

RecordAction is the same call without a function around it:

resp, err := cl.RecordAction(ctx, "refund_workflow", "order_10231", commitlayer.ActionOptions{
	Params: map[string]any{"amount": 180.0},
})

Push facts, with a watermark

Use an evidence key here, bound to the sources it pushes for (Settings → API keys). The key your agent runtime records actions with is a recorder key and cannot push facts: the credential that records an action never also supplies the facts that verify it.

evidence, err := commitlayer.New(commitlayer.WithAPIKey(os.Getenv("COMMITLAYER_EVIDENCE_KEY"))) // the pipeline's own client
if err != nil {
	log.Fatal(err)
}
_, err = evidence.PushFacts(ctx, []map[string]any{
	{
		"source":      "crm",
		"subject_id":  "order_10231",
		"fact_type":   "note_added",
		"occurred_at": "2026-10-13T10:42:09Z",
	},
}, commitlayer.FactsOptions{
	Watermarks: map[string]string{"crm": "2026-10-13T10:45:00Z"}, // everything up to here has been sent for crm
})

A verdict that is not yet a PASS settles only when its window has closed and every source it reads has a watermark past the end of the window. Declare one when your feed lags; a source that never declares one is taken as current.

Draft a contract from what was recorded

d, err := cl.DraftContract(ctx, "refund_order", commitlayer.DraftOptions{LookbackDays: 30})
fmt.Println(d["yaml"]) // expected effects, window, preconditions, limits, from the sample
fmt.Println(d["stats"].(map[string]any)["effects"])
yaml := d["yaml"].(string)
v, err := cl.ValidateContract(ctx, yaml) // errors, and weakening warnings against the version it would replace
_, err = cl.SaveContract(ctx, yaml)      // the response carries the same warnings

Every value in a draft is what the sample showed, not what the policy says. Review it before saving.

Read a verdict

v, err := cl.GetAction(ctx, "refund:order_10231")
v["status"]                                        // pending | settled
v["settlement"].(map[string]any)["waiting_on"]     // sources still behind the window, if any
v["reversal"]                                      // the later record that undid the action, if any
v["sources"]                                       // trust level and watermark per source the contract read

Errors

API rejections come back as *commitlayer.Error with Status, Code, Message and Details, taken from the API's {"error": {...}} body:

var apiErr *commitlayer.Error
if errors.As(err, &apiErr) && apiErr.Code == "entitlement_limit" {
	// apiErr.Details names the plan to upgrade to
}

Testing without the network

WithTransport replaces the HTTP layer with a func(ctx, method, path, body) (status, payload, err), which is how this package's own tests run offline. With a transport present no API key is needed.