Go SDK
The Go SDK lets Go programs call Kibotos. This page covers installing it, connecting to ctrl with an Operator identity, the conventions it carries for write operations, and reaching the files, processes and services of a Workspace.
The SDK speaks the native API described in API overview. Read the API pages for what each service does; this page covers what the SDK does for you.
What the SDK takes care of
| Without the SDK you write | With the SDK |
|---|---|
| mTLS with only the server CA, TLS 1.3, HTTP/2, no redirects, forward proxy | kibotos.Dial |
| Renewing the 30-day certificate and moving requests to connections that present the new one | Runs in the background until Close |
| Command IDs, and resending a command whose outcome you did not hear | NewCommandID, DeriveCommandID, Settle |
| Checking capability names before using new fields | Client.Require, Client.Has |
| Polling a Workspace command until it settles | Client.WaitWorkspace |
| Issuing, caching, renewing and replacing data-plane credentials | dataplane.Session |
/v1/files transfers with integrity checks, resumable process streams, service connections | Methods of dataplane.Session |
Each row follows the rules on the native API pages: Authentication for the connection and the certificate, Write operations and errors for command IDs and capabilities, and Data plane for session credentials. Programs in other languages follow those pages directly.
Install
The SDK is the Go module github.com/felinics/kibotos/sdk/go. Every Kibotos release tags it as sdk/go/vX.Y.Z on the same commit, so the SDK of a release has the release's version. The first release that includes the SDK is v0.3.0:
go get github.com/felinics/kibotos/sdk/go@v0.3.0The module needs Go 1.27 or later. It brings only connect-go, protobuf, github.com/coder/websocket and golang.org/x/net into your build.
Note
The Kibotos repository is not public yet. Until it is, go get needs read access to it and must not go through the public module proxy:
-
Ask the Kibotos maintainers for read access to
felinics/kibotos. -
Tell Go to fetch the module directly.
GOPRIVATEalso turns off the public checksum database for it:go env -w GOPRIVATE=github.com/felinics/kibotos -
Give Git credentials for GitHub, in one of two ways:
-
HTTPS with a token, for example through the GitHub CLI:
gh auth setup-git -
SSH, rewriting GitHub URLs to SSH:
git config --global url."git@github.com:".insteadOf "https://github.com/"
-
In CI, set GOPRIVATE the same way and provide a token or deploy key that can read the repository. Without credentials, go get fails with could not read Username for 'https://github.com'.
Connect to ctrl
kibotos.Dial connects with the Operator identity in an identity store. The store holds the client certificate, its private key, the control-client CA and the server CA:
store, err := managedidentity.Open("/var/lib/example/kibotos-identity")
if err != nil {
log.Fatal(err)
}
client, err := kibotos.Dial(ctx, "https://ctrl.example.com:18092", store,
kibotos.WithEnrollmentKey(os.Getenv("KIBOTOS_ENROLLMENT_KEY")))
if err != nil {
log.Fatal(err)
}
defer client.Close()- An empty store is enrolled first when
WithEnrollmentKeyis given. Generate the key withkibo enrollment create --operator, see Identity and enrollment. A store that already holds an identity never uses the key, so it can stay configured after it was consumed. managedidentity.Openuses the layout of a kibo context directory. A directory enrolled withkibo context enrollworks as is. The directory must not grant group or other access. A missing directory is created with the right mode. Give each program its own directory: ctrl accepts only the current and the previous certificate of an identity, so two programs renewing one identity lock each other out.- The certificate is renewed in the background. Inside the renewal window (the last 10 of its 30 days) the client renews over its own connections, writes the new certificate to the store, and then sends new requests on connections that present it. Requests in flight finish where they are.
- Renewal is logged with
slog.Default(), or the logger fromWithLogger. PassWithRenewalObserverto handle the events yourself. - Forward proxies follow
HTTPS_PROXY,ALL_PROXYandNO_PROXY.WithProxynames one explicitly;WithProxy("")dials directly.
Keeping the identity somewhere else
A program that keeps secrets in its own storage implements kibotos.IdentityStore over it: Exists, Load and Publish.
Publish must persist the renewed identity before it returns. A renewal that is lost on restart leaves the program with a certificate that expires within 30 days.
For tests, or to load an identity given as PEM, use managedidentity.ParsePEM and managedidentity.NewMemoryFrom. Set OnPublish on the returned store to persist renewals.
Calling services
The client returns the generated Connect client of each service, for example client.Workspaces(), client.Volumes(), client.Egresses(), and client.Operator().NodeRegistration() for the Operator API. Every call uses the client's mTLS connection.
Write operations
commandID := kibotos.DeriveCommandID("example", "create-workspace", "order-1234")
created, err := client.Workspaces().CreateWorkspace(ctx, connect.NewRequest(&ctrlv1.CreateWorkspaceRequest{
Metadata: &ctrlv1.CommandMetadata{CommandId: commandID},
Spec: &ctrlv1.WorkspaceCreateSpec{
Template: &ctrlv1.TemplateSelector{TemplateId: "coding"},
Resources: &ctrlv1.ResourceRequest{CpuMillis: 1000, MemoryBytes: 512 << 20},
},
Labels: map[string]string{"example.com/order": "1234"},
}))
if err != nil {
log.Fatal(err)
}
workspace, err := client.WaitWorkspace(ctx, created.Msg.GetWorkspace(), commandID)
if err != nil {
log.Fatalf("create failed: %v (reason %s)", err, kibotos.Reason(err))
}| Helper | Use it for |
|---|---|
NewCommandID(prefix) | A random command ID, for example example-3f9c⦠|
DeriveCommandID(prefix, parts...) | A command ID from a key your program already has, such as an order number. After a restart the same key names the same command, so ctrl replays it instead of creating a second Workspace |
Settle(ctx, options, send) | Sending a command again under the same ID while ctrl says it is still executing or did not answer. A settled failure returns at once, because the same ID would only replay it. options.Executing replaces the default judgment StillExecuting |
Client.WaitWorkspace | Waiting for a Workspace command until the Workspace is available. A failed command returns the error ctrl recorded |
Client.Require(ctx, names...) | Failing one operation when ctrl does not announce the capabilities it needs. An older ctrl silently drops fields it does not know |
Detail(err), Reason(err) | The ErrorDetail ctrl attached to a refusal |
For the rules behind command IDs and expected revisions, see Write operations and errors.
The data plane
dataplane.Open prepares a session for one Workspace. It asks ctrl for a credential when the first call needs one:
session, err := dataplane.Open(client, workspace.GetWorkspaceId(), dataplane.WithScopes(
ctrlv1.DataPlaneScope_DATA_PLANE_SCOPE_FILE_READ,
ctrlv1.DataPlaneScope_DATA_PLANE_SCOPE_FILE_WRITE,
ctrlv1.DataPlaneScope_DATA_PLANE_SCOPE_PROCESS,
))
if err != nil {
log.Fatal(err)
}
defer session.Close()
content := "print('hello')\n"
if _, err := session.PutFile(ctx, "hello.py", strings.NewReader(content), int64(len(content)), dataplane.PutOptions{}); err != nil {
log.Fatal(err)
}
stream, err := session.StartProcess(ctx, &ctrlv1.StartRequest{
Argv: []string{"python3", "hello.py"},
Mode: &ctrlv1.StartRequest_Pipe{Pipe: &ctrlv1.ProcessPipeMode{}},
})
if err != nil {
log.Fatal(err)
}
defer stream.Close()
for stream.Receive() {
event := stream.Event()
switch {
case event.Output != nil:
os.Stdout.Write(event.Output.GetData())
case event.Exited != nil:
fmt.Println("exit code", event.Exited.GetExitCode())
}
}
if err := stream.Err(); err != nil {
log.Fatal(err)
}How the credential is managed
- One credential is shared by every call of the session until 70ā80% of its lifetime has passed. Concurrent calls wait for one issuance.
- A refused credential is replaced at once. When the node answers
SESSION_EXPIREDorSESSION_REVOKED, the session asks for a new one.UnaryandGetFilethen send the call once more. TheFilesystemandDesktopclients andPutFiledo not resend; their next call uses the new credential. - Failed issuances back off: the second consecutive failure waits 1 second, doubling up to 30 seconds. Until then calls get the last error without asking ctrl, so a busy program does not turn its traffic into ctrl calls.
WithExecution(generation)binds the session to one execution of the Workspace, thegenerationthatGetWorkspacereports. Once the Workspace runs another execution, calls fail withdataplane.ErrExecutionChanged; open a new session to reach the new execution.
What a session offers
| Method | What it does |
|---|---|
PutFile, GetFile | /v1/files transfers. An upload is at most 1 GiB and checks the node's reported size and SHA-256; a download refuses a partial or unsized answer and fails a body that ends short. A file over 1 GiB is read in parts and fails with ErrFileChanged if it is rewritten in between; Download does the same for callers that manage their own credential. A transfer that moves no data for a minute ends |
StartProcess, AttachProcess | Process streams that resume from the last output offset after a node restart or data-plane upgrade. See Resuming process streams |
DialService | One connection to a service target in the Workspace, as a net.Conn |
ServiceDialer | A dialer for one target that renews its own credential, for an adapter that opens connections for as long as it runs |
Filesystem, Desktop, Unary | Generated Connect clients of the data-plane services, with the session's credential on every call |
Service targets need WithServiceTargets. A node reached through a relay needs WithDialer: it opens the TCP connection, while TLS still verifies the node's own host name.
Complete programs
The Kibotos repository has programs that import only the SDK. Each one enrolls an empty identity directory on its first run:
go run ./examples/go/quickstart -endpoint https://ctrl.example.com:18092 \
-identity "$HOME/.local/state/kibotos-quickstart" -enrollment-key-file quickstart.keyexamples/go/quickstart: connect, create a Workspace, run a command, transfer a file, and delete it.- Scenarios: programs for a coding Agent, untrusted code, batches, and a desktop.
Compatibility
The Go API of the SDK changes only in MINOR releases until v1. PATCH releases do not change it. Each change is listed in the upgrade notes of the release in CHANGELOG.md.
The wire protocol keeps its own commitment, described in Compatibility commitments. A program built with an older SDK keeps working against a newer ctrl within the compatibility window it describes.
Next steps
- Write operations and errors: the rules behind command IDs, expected revisions, and the errors that
DetailandReasonread. - Data plane: session credentials, stream interruptions, and the scope each service needs.
- Identity and enrollment: creating the enrollment key for a program's own identity.
Data plane
Files, processes, desktops, and service connections go directly to the node that runs the Workspace, without forwarding through ctrl. This page shows how to request a session credential from ctrl, keep it valid, and call the node with it.
E2B compatibility
The E2B gateway kibotos-e2b translates the E2B control API and the envd protocol into the Kibotos native API, so programs written with the official E2B SDK run on Kibotos. These pages show which SDK calls and endpoints work, how they behave on Kibotos, and what is not supported.