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.