kosa8 docs

Guides / Go / API reference

Go API reference

Every export of github.com/kosa8ai/kosa8-go v0.1.2, package kosa8. Signatures are copied from the source, and the list of names on this page was checked against go doc -all. Paths are the daemon's native API; … stands for /kosa8/v1/sandboxes/{sandbox}.

Conventions

Client

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

func New(o Options) (*Client, error)
func (c *Client) Endpoint() Endpoint

New resolves the endpoint (see ResolveEndpoint), checks Actor, builds the HTTP client and picks the checkpoint store. It sends nothing. It fails with a KindUsage error for a refused KOSA8_HOST or host, or an actor other than human, agent or tool. A *Client is safe for concurrent use by multiple goroutines. Endpoint reports where requests go.

Options

type Options struct {
	SocketPath string // default ~/.kosa8/run/kosa8.sock
	Host       string // a remote worker; default $KOSA8_HOST
	Token      string // bearer token for Host; default $KOSA8_TOKEN when Host came from the env
	Insecure   bool   // allow plain http to a non-loopback worker
	Actor      string // human | agent | tool (default tool)
	AgentToken string // default $KOSA8_AGENT_TOKEN
	Headers    map[string]string
	HTTPClient *http.Client // overrides the transport entirely
	// Checkpoints is where checkpoint stacks live. Default: the CLI's own
	// ~/.kosa8/checkpoints.json, so `kosa8 rewind` and this SDK share them.
	Checkpoints CheckpointStore
	Getenv      func(string) string // for tests; default os.Getenv
}
FieldDefaultEffect
SocketPathDefaultSocketPath()The daemon's unix socket. Used only when no host is set, either here or in KOSA8_HOST.
Host$KOSA8_HOSTA worker address. Forms and rules under ResolveEndpoint.
Token$KOSA8_TOKEN, only when the host came from KOSA8_HOSTSent as Authorization: Bearer …, to a URL endpoint only, never over a unix socket.
Insecurefalse (or KOSA8_INSECURE=1)Allows plain http:// to a host that is not loopback.
Actor"tool"Sent as X-Kosa8-Actor on every request; recorded in the audit log.
AgentToken$KOSA8_AGENT_TOKENSent as X-Kosa8-Agent-Token when non-empty.
HeadersnoneSet on every request after Accept and X-Kosa8-Actor (so they can replace those) and before Content-Type, X-Kosa8-Approval, X-Kosa8-Agent-Token and Authorization (which win when the SDK sets them).
HTTPClientan http.Client with compression off, dialling the socket for a local endpointReplaces the SDK's client entirely. For a local endpoint, requests go to http://kosa8/… and your client must dial the socket itself.
CheckpointsNewFileCheckpointStore("")The checkpoint store. See Checkpoint stores.
Getenvos.GetenvWhere KOSA8_HOST, KOSA8_TOKEN, KOSA8_INSECURE and KOSA8_AGENT_TOKEN are read from.

Daemon

MethodReturnsHTTP
func (c *Client) Health(ctx context.Context) (Info, error)Keys status, version, commit, go, uptime_seconds, phaseGET /kosa8/v1/health
func (c *Client) Version(ctx context.Context) (string, error)The daemon's version ("" if absent)GET /kosa8/v1/health
func (c *Client) Pull(ctx context.Context, ref string, call ...Call) (Info, error)The daemon's answerPOST /kosa8/v1/pull {"ref"}: into the host's shared engine, not a sandbox

Sandboxes

MethodReturnsHTTP
func (c *Client) ListSandboxes(ctx context.Context) ([]Info, error)Every sandboxGET /kosa8/v1/sandboxes
func (c *Client) GetSandbox(ctx context.Context, idOrName string) (Info, error)The first sandbox whose id, then name, matches; nil, nil when none doesGET /kosa8/v1/sandboxes, filtered in the client
func (c *Client) CreateSandbox(ctx context.Context, o CreateOptions) (Info, error)Keys sandbox (an object), boot_ms, and broker when one was asked forPOST /kosa8/v1/sandboxes. KindUsage before sending if EgressAllow is set with an Egress other than "" or "deny", a Mounts entry is malformed, or Capture has no Path
func (c *Client) StartSandbox(ctx context.Context, sandbox string, call ...Call) (Info, error)Keys sandbox, boot_msPOST …/up
func (c *Client) StopSandbox(ctx context.Context, sandbox string, call ...Call) (Info, error)Key ok, and othersPOST …/down
func (c *Client) DeleteSandbox(ctx context.Context, sandbox string, o DeleteOptions) (Info, error)The daemon's answer (ok, …) plus checkpoints_dropped (an int)DELETE … (?wipe=1); then, unless KeepCheckpoints, removes the sandbox's records from the store and sends DELETE /kosa8/v1/snapshots/{id} for each, ignoring their errors
func (c *Client) PullIntoSandbox(ctx context.Context, sandbox, ref string, call ...Call) (Info, error)Keys ok, imagePOST …/pull {"ref"}
func (c *Client) SandboxImages(ctx context.Context, sandbox string) ([]string, error)Image referencesGET …/images
func (c *Client) SandboxContainers(ctx context.Context, sandbox string) ([]Info, error)Containers: id, image, status, …GET …/containers
func (c *Client) SandboxEgress(ctx context.Context, sandbox string) (Info, error)policy, stats, and moreGET …/egress

Running code

MethodReturnsHTTP
func (c *Client) Run(ctx context.Context, sandbox, image string, args []string, opts *RunOptions) (ExecResult, error)The collected result. On error, the output so farPOST …/run {"image","args","env"?}, NDJSON frames. The image must already be in the sandbox
func (c *Client) StartContainer(ctx context.Context, sandbox, image string, args []string, call ...Call) (string, error)The container idPOST …/start {"image","args"}
func (c *Client) Exec(ctx context.Context, sandbox, container string, args []string, opts *RunOptions) (ExecResult, error)The collected result. A non-empty opts.Env is a KindUsage error before sending: the daemon's exec takes no environmentPOST …/exec {"container","args"}, NDJSON frames
func (c *Client) ExecFrames(ctx context.Context, sandbox, container string, args []string, fn func(Frame) error, call ...Call) errornil when the stream ends cleanly; fn's error if it returned one; a KindStream error when the final frame carries an error (after fn has seen that frame)POST …/exec; calls fn per frame, including the final Done frame
func (c *Client) StartInteractive(ctx context.Context, sandbox, image string, args []string, o InteractiveOptions) (string, error)The container idPOST …/start-interactive {"image","args","env","tty"}
func (c *Client) WriteStdin(ctx context.Context, sandbox, container string, r io.Reader, call ...Call) (Info, error){"ok": true}. A second call for the same sandbox and container on the same Client is a KindUsage error, sent nothingPOST …/containers/{cid}/stdin, body streamed from r as application/octet-stream; stdin closes when r returns EOF, so r is the whole of stdin (use an io.Pipe to feed it over time)
func (c *Client) Attach(ctx context.Context, sandbox, container string, opts *RunOptions) (ExecResult, error)The collected result when the container exitsPOST …/containers/{cid}/attach, NDJSON frames

Run, Exec and Attach fail with KindStream when the stream ends with an error frame, with a frame that cannot be decoded, or with no final frame; with KindTimeout when RunOptions.Timeout or the context's deadline passes. In both cases the error's Stdout/Stderr and the returned ExecResult hold the output so far. A non-zero exit code is not an error. opts may be nil.

Snapshots, fork, explore

MethodReturnsHTTP
func (c *Client) Snapshot(ctx context.Context, sandbox string, o SnapshotOptions) (Info, error)Keys snapshot (an object), snapshot_ms, …POST …/snapshot {"id"?,"encrypt"?}
func (c *Client) ListSnapshots(ctx context.Context) ([]Info, error)Every snapshotGET /kosa8/v1/snapshots
func (c *Client) RestoreSnapshot(ctx context.Context, snapshotID string, o RestoreOptions) (Info, error)Keys sandbox, restore_msPOST /kosa8/v1/snapshots/{id}/restore {"name"?,"label"?,"allow_tampered"?}
func (c *Client) DeleteSnapshot(ctx context.Context, snapshotID string, o SnapshotDeleteOptions) (Info, error)The daemon's answerDELETE /kosa8/v1/snapshots/{id} (?wipe=1, &force=1)
func (c *Client) Fork(ctx context.Context, sandbox string, n int, o ForkOptions) (ForkResult, error)ForkResultPOST …/fork {"n","prefix"?,"label"?}; n below 1 is sent as 1
func (c *Client) Explore(ctx context.Context, sandbox string, variants []string, o ExploreOptions) (ExploreResult, error)ExploreResultPOST …/pull (unless NoPull), POST …/fork with n = len(variants), one POST /kosa8/v1/sandboxes/{branch}/run of sh -c VARIANT per branch, concurrently, then DELETE /kosa8/v1/sandboxes/{branch} for each branch not kept

Explore fails with KindUsage for fewer than two variants, when the fork returned fewer branches than variants, and when Keep names no branch (then it returns the result with every branch kept). A branch that fails to run is recorded in its Error field rather than failing the call; connection-level failures are retried up to four times, 0.5 s, 1 s and 1.5 s apart.

Checkpoints

MethodReturnsHow
func (c *Client) Checkpoint(ctx context.Context, sandbox, label string, call ...Call) (CheckpointResult, error)CheckpointResultPicks <sandbox>-ckpt-<n>, POST …/snapshot with that id (on a 409 already exists, from a concurrent checkpoint, it moves to the next number, up to 10 tries; any other 409 is returned), DELETE /kosa8/v1/snapshots/{id} for each evicted checkpoint (past MaxCheckpointsPerSandbox), then records it in the store
func (c *Client) ListCheckpoints(sandbox string) ([]Checkpoint, error)Oldest first, by CreatedThe store only; no HTTP
func (c *Client) Rewind(ctx context.Context, sandbox, to string, call ...Call) (RewindResult, error)RewindResultResolves to (an id, or a label without regard to case, newest first; "" = the latest), DELETE …, POST /kosa8/v1/snapshots/{id}/restore {"name": sandbox}, removes later checkpoints from the store and sends DELETE /kosa8/v1/snapshots/{id} for each

Checkpoint errors: KindCheckpoint for an invalid sandbox name (empty, ., .., containing / or \, or starting with a dot), no checkpoints to rewind to, or a to that matches nothing. If the restore step of Rewind fails, the error is KindCheckpoint wrapping the daemon's error (Err), with the message restore failed and NAME no longer exists (it was deleted to free its name for the restore): … — the checkpoint is intact; recover with RestoreSnapshot("ID", RestoreOptions{Name: "NAME"}); by then the sandbox has been deleted and the snapshot is intact. If the delete step fails, its error is returned unchanged and nothing has changed.

Handle

func (c *Client) Sandbox(id string) *Sandbox

Returns a handle bound to one sandbox. It sends nothing and does not check that the sandbox exists.

Sandbox

type Sandbox struct {
	ID string
	// contains filtered or unexported fields
}
MethodCalls
func (s *Sandbox) Info(ctx context.Context) (Info, error)GetSandbox
func (s *Sandbox) Start(ctx context.Context, call ...Call) (Info, error)StartSandbox
func (s *Sandbox) Stop(ctx context.Context, call ...Call) (Info, error)StopSandbox
func (s *Sandbox) Delete(ctx context.Context, o DeleteOptions) (Info, error)DeleteSandbox
func (s *Sandbox) Pull(ctx context.Context, ref string, call ...Call) (Info, error)PullIntoSandbox
func (s *Sandbox) Run(ctx context.Context, image string, args []string, o *RunOptions) (ExecResult, error)Run
func (s *Sandbox) StartContainer(ctx context.Context, image string, args []string, call ...Call) (string, error)StartContainer
func (s *Sandbox) Exec(ctx context.Context, container string, args []string, o *RunOptions) (ExecResult, error)Exec
func (s *Sandbox) Containers(ctx context.Context) ([]Info, error)SandboxContainers
func (s *Sandbox) Images(ctx context.Context) ([]string, error)SandboxImages
func (s *Sandbox) Egress(ctx context.Context) (Info, error)SandboxEgress
func (s *Sandbox) Snapshot(ctx context.Context, o SnapshotOptions) (Info, error)Snapshot
func (s *Sandbox) Fork(ctx context.Context, n int, o ForkOptions) (ForkResult, error)Fork
func (s *Sandbox) Explore(ctx context.Context, variants []string, o ExploreOptions) (ExploreResult, error)Explore
func (s *Sandbox) Checkpoint(ctx context.Context, label string, call ...Call) (CheckpointResult, error)Checkpoint
func (s *Sandbox) Checkpoints() ([]Checkpoint, error)ListCheckpoints
func (s *Sandbox) Rewind(ctx context.Context, to string, call ...Call) (RewindResult, error)Rewind

There is no ExecFrames, StartInteractive, WriteStdin, Attach, RestoreSnapshot or DeleteSnapshot on the handle; call them on the client with s.ID.

Option types

Call

type Call struct{ Approval string }

Per-request options: a signed approval id, sent as X-Kosa8-Approval (trimmed; omitted when empty).

CreateOptions

type CreateOptions struct {
	Name   string
	CPUs   int
	MemMB  int
	DiskGB int
	Mounts []string // host:guest[:ro]; host must be absolute
	Egress string   // deny | any | fakenet | allow:host,host
	// EgressAllow limits the network to these hosts (sets Egress to allow:…).
	EgressAllow []string
	// Capture records the sandbox's network traffic as pcap segments under
	// Capture.Path, a file on the host (pcapng). nil records nothing.
	Capture  *CaptureOptions
	Labels   map[string]string
	Broker   any // the credential-broker config, passed through
	Approval string
}

// CaptureOptions turn on packet capture for a sandbox (daemon 0.9.36 writes one pcapng file at Path).
type CaptureOptions struct {
	Path     string // host file the capture is written to (required)
	MaxBytes int64  // per segment; 0 is the daemon's default
	MaxFiles int    // segments kept; 0 is the daemon's default
}
FieldSent asNotes
NamenameOmitted when empty: the daemon picks one.
CPUs, MemMB, DiskGBcpus, mem_mb, disk_gbOmitted when 0 or less: the daemon's defaults.
Mountsmounts, each "host:guest[:ro|:rw]" as {"host_path","guest_path","read_only"}Anything that is not two non-empty parts (plus an optional ro/rw) is a KindUsage error, and so (from v0.1.2) is a host path that is not absolute (filepath.IsAbs) or a guest path not starting with /. Shared with virtiofs. v0.1.0 sent strings and every mount failed. See the guide.
Egressegressdeny, any, fakenet or allow:h1,h2. Empty: the daemon's default (the org policy's, if it sets one).
EgressAllowegress: "allow:h1,h2"Each host trimmed. Combined with an Egress other than ""/"deny" it is a KindUsage error.
Capturecapture: {"path", "max_bytes"?, "max_files"?}nil: no capture. Empty Path is a KindUsage error. Path is a pcapng file path (like kosa8 sandbox create --pcap FILE); a directory is refused with a 500 capture: open …: is a directory. MaxBytes, MaxFiles are sent only when positive. v0.1.0 had a bool here that never worked.
LabelslabelsOmitted when empty.
BrokerbrokerMarshalled with encoding/json as given.
ApprovalheaderX-Kosa8-Approval.

Other option types

type DeleteOptions struct {
	Wipe            bool // overwrite the disks, not just unlink them
	KeepCheckpoints bool // keep this sandbox's checkpoint snapshots
	Approval        string
}

type RunOptions struct {
	Env      []string // KEY=VALUE
	OnStdout func([]byte)
	OnStderr func([]byte)
	OnStatus func([]byte) // pull/progress lines
	Timeout  time.Duration
	Approval string
}

type InteractiveOptions struct {
	Env      []string
	TTY      bool
	Approval string
}

type SnapshotOptions struct {
	ID       string // default: the daemon picks one
	Encrypt  bool
	Approval string
}

type RestoreOptions struct {
	Name          string
	Label         string
	AllowTampered bool
	Approval      string
}

type SnapshotDeleteOptions struct {
	Wipe, Force bool
	Approval    string
}

type ForkOptions struct {
	Prefix   string
	Label    string
	Approval string
}

type ExploreOptions struct {
	Image    string        // default alpine:latest
	KeepAll  bool          // keep every branch
	Keep     string        // keep this branch instead of the fastest success
	Timeout  time.Duration // per branch; default 120s
	Prefix   string
	NoPull   bool // the image is already in the sandbox
	Approval string
}
FieldBehaviour
DeleteOptions.Wipe?wipe=1 on the sandbox delete and on each checkpoint snapshot delete.
DeleteOptions.KeepCheckpointsLeaves the store and the snapshots alone; checkpoints_dropped is 0.
RunOptions.EnvSent by Run (when non-nil). Exec refuses a non-empty one with KindUsage; Attach ignores it.
RunOptions.OnStdout, OnStderrCalled with each chunk as it arrives, on the calling goroutine. The chunk is also collected in the result.
RunOptions.OnStatusCalled with status frames (progress lines); these are not collected.
RunOptions.TimeoutWhen positive, wraps the context with context.WithTimeout for this call.
InteractiveOptions.Env, TTYSent as env (null when nil) and tty.
SnapshotOptions.ID, EncryptSent as id and encrypt: true when set.
RestoreOptions.Name, Label, AllowTamperedSent as name, label, allow_tampered: true when set. Name empty: the daemon picks one.
SnapshotDeleteOptions.Wipe, Force?wipe=1, ?force=1.
ForkOptions.Prefix, LabelSent when set. The daemon names forks <prefix>1, <prefix>2, …; its default prefix is <sandbox>-fork.
ExploreOptions.PrefixDefault <sandbox>-explore-<unix seconds mod 100000>.

Result types

type Info = map[string]any

type ExecResult struct {
	ExitCode    int
	StdoutBytes []byte
	StderrBytes []byte
}
func (r ExecResult) Stdout() string
func (r ExecResult) Stderr() string
func (r ExecResult) OK() bool // ExitCode == 0

type ForkResult struct {
	Forks    []Info `json:"forks"`
	Snapshot any    `json:"snapshot"`
	ForkMS   int64  `json:"fork_ms"`
	Count    int    `json:"count"`
}

type ExploreBranch struct {
	Name       string
	Variant    string
	Exit       int
	Output     string
	Error      string // set when the branch could not run
	DurationMS int64
}

type ExploreResult struct {
	Parent   string
	Branches []ExploreBranch
	Winner   string // "" when no branch succeeded
	Removed  int
}

type Checkpoint struct {
	ID      string    `json:"id"`
	Sandbox string    `json:"sandbox"`
	Label   string    `json:"label"`
	Auto    bool      `json:"auto,omitempty"`
	Created time.Time `json:"created"`
}

type CheckpointResult struct {
	Checkpoint Checkpoint
	Evicted    []string
	Snapshot   Info
}

type RewindResult struct {
	Sandbox    any
	Checkpoint Checkpoint
	Dropped    []string
	RestoreMS  any
}
FieldMeaning
ExploreBranch.Outputstdout followed by stderr, trailing newlines trimmed.
ExploreBranch.DurationMSWall time for the branch, retries included.
ExploreResult.WinnerThe successful branch (no Error, exit 0) with the smallest DurationMS, or Keep when set.
ExploreResult.RemovedBranches deleted successfully. With KeepAll, 0. With no winner and no KeepAll, every branch is deleted.
CheckpointResult.EvictedIds of checkpoints removed to stay within 10.
CheckpointResult.SnapshotThe daemon's snapshot answer (snapshot, snapshot_ms, …).
RewindResult.Sandbox, RestoreMSThe restore answer's sandbox (a map[string]any) and restore_ms (a float64), untyped.
RewindResult.DroppedIds of checkpoints taken after the target, now removed.

Checkpoint stores

const MaxCheckpointsPerSandbox = 10

type CheckpointStore interface {
	Load() (map[string][]Checkpoint, error)
	// Update runs fn on the stacks and saves what it leaves, serialised against other updates.
	Update(fn func(map[string][]Checkpoint) error) error
}

type FileCheckpointStore struct {
	Path string
	// contains filtered or unexported fields
}
func NewFileCheckpointStore(path string) *FileCheckpointStore
func (f *FileCheckpointStore) Load() (map[string][]Checkpoint, error)
func (f *FileCheckpointStore) Update(fn func(map[string][]Checkpoint) error) error

type MemoryCheckpointStore struct {
	// contains filtered or unexported fields
}
func (m *MemoryCheckpointStore) Load() (map[string][]Checkpoint, error)
func (m *MemoryCheckpointStore) Update(fn func(map[string][]Checkpoint) error) error
NameBehaviour
NewFileCheckpointStore"" means ~/.kosa8/checkpoints.json, the CLI's file.
FileCheckpointStore.LoadA missing file is an empty store; so is a file that is not valid JSON (a corrupt store must not make the sandbox unusable).
FileCheckpointStore.UpdateCreates the directory (0700), takes an exclusive flock on <Path>.lock (the CLI's lock; no lock on systems without flock, only an in-process mutex), loads, runs fn, writes <Path>.tmp (0600) and renames it over the file. If fn fails nothing is written.
MemoryCheckpointStoreThe zero value is ready to use; use it by pointer. Load returns a copy; Update works on a copy and keeps it only if fn succeeds.

Endpoint, frames and constants

const Version = "0.1.2"

const (
	ApprovalHeader   = "X-Kosa8-Approval"
	ActorHeader      = "X-Kosa8-Actor"
	AgentTokenHeader = "X-Kosa8-Agent-Token"
)

func DefaultSocketPath() string // $HOME/.kosa8/run/kosa8.sock

type Endpoint struct {
	Unix  string   // socket path, when local
	URL   *url.URL // remote worker, when not
	Token string
}
func (e Endpoint) Target() string // "unix://PATH" or "SCHEME://HOST"

func ResolveEndpoint(host, socketPath, token string, insecure bool, getenv func(string) string) (Endpoint, error)

type Frame struct {
	Stream string `json:"stream,omitempty"` // stdout | stderr | status
	Data   []byte `json:"data,omitempty"`
	Done   bool   `json:"done,omitempty"`
	OK     bool   `json:"ok,omitempty"`
	Error  string `json:"error,omitempty"`
	Exit   *int   `json:"exit,omitempty"`
	ID     string `json:"id,omitempty"`
}

ResolveEndpoint follows the CLI's KOSA8_HOST rules. It sends nothing.

Frame is one NDJSON frame of a run, exec or attach stream, with Data decoded from base64. Output frames have Stream and Data; the last frame has Done, and either OK and Exit, or Error.

Errors

type Error struct {
	Kind          Kind
	Message       string
	Status        int    // HTTP status, when the daemon answered
	DaemonMessage string // the daemon's "error" text, verbatim
	Remediation   string // the daemon's "remediation": what to do about it
	Method, Path  string
	Body          any            // the decoded response body
	Approval      map[string]any // set when Kind == KindApprovalRequired: the daemon's refusal
	Stdout        string         // KindStream: output produced before the error
	Stderr        string
	Err           error // the underlying cause, if any
}
func (e *Error) Error() string        // Message
func (e *Error) Unwrap() error        // Err
func (e *Error) Is(target error) bool

func AsError(err error) (*Error, bool)

type Kind string

const (
	KindConnection       Kind = "connection"        // daemon unreachable
	KindAPI              Kind = "api"               // any other HTTP error
	KindBadRequest       Kind = "bad_request"       // 400
	KindAuth             Kind = "auth"              // 401
	KindPolicy           Kind = "policy"            // 403
	KindApprovalRequired Kind = "approval_required" // 403 needing a signed approval
	KindNotFound         Kind = "not_found"         // 404
	KindConflict         Kind = "conflict"          // 409
	KindUnsupported      Kind = "unsupported"       // 501
	KindStream           Kind = "stream"            // a stream ended with an error frame
	KindCheckpoint       Kind = "checkpoint"        // client-side checkpoint bookkeeping
	KindTimeout          Kind = "timeout"
	KindUsage            Kind = "usage" // bad arguments
)

var (
	ErrConnection       = &Error{Kind: KindConnection}
	ErrAPI              = &Error{Kind: KindAPI}
	ErrBadRequest       = &Error{Kind: KindBadRequest}
	ErrAuth             = &Error{Kind: KindAuth}
	ErrPolicy           = &Error{Kind: KindPolicy}
	ErrApprovalRequired = &Error{Kind: KindApprovalRequired}
	ErrNotFound         = &Error{Kind: KindNotFound}
	ErrConflict         = &Error{Kind: KindConflict}
	ErrUnsupported      = &Error{Kind: KindUnsupported}
	ErrStream           = &Error{Kind: KindStream}
	ErrCheckpoint       = &Error{Kind: KindCheckpoint}
	ErrTimeout          = &Error{Kind: KindTimeout}
	ErrUsage            = &Error{Kind: KindUsage}
)

Is matches a target *Error with an empty Message (a sentinel, or &kosa8.Error{Kind: k}) by Kind. Two extra rules: ErrPolicy also matches KindApprovalRequired, and ErrAPI matches every HTTP kind (api, bad_request, auth, policy, approval_required, not_found, conflict, unsupported). ErrUsage (new in v0.1.1) matches KindUsage. AsError is errors.As into a *Error. When each kind occurs, with real messages: Go errors and troubleshooting.