kosa8 docs

Guides / Python / API reference

Python API reference

Every name the kosa8 package exports in version 0.1.1 (its __all__), and the public members of each. Signatures are copied from the source of the published package. Keys in returned dicts are the daemon's own JSON names, unchanged.

Package

from kosa8 import (
    Kosa8Client, Sandbox, ExecResult, ExploreBranch, ExploreResult,
    Transport, default_socket_path, resolve_endpoint,
    CheckpointBook, FileCheckpointStore, MemoryCheckpointStore, MAX_CHECKPOINTS_PER_SANDBOX,
    Kosa8Error, Kosa8ConnectionError, Kosa8ApiError, Kosa8BadRequestError, Kosa8AuthError,
    Kosa8PolicyError, Kosa8ApprovalRequiredError, Kosa8NotFoundError, Kosa8ConflictError,
    Kosa8UnsupportedError, Kosa8StreamError, Kosa8CheckpointError, Kosa8TimeoutError,
    Kosa8ProtocolError, error_from_response, __version__,
)
NameValue
__version__"0.1.1". Changes from 0.1.0 are marked "0.1.1" below and listed in the package's CHANGELOG.md.
MAX_CHECKPOINTS_PER_SANDBOX10. Checkpoints kept per sandbox; the oldest is evicted past it.

Python 3.9 or later. No dependencies: only the standard library. The package ships py.typed, so type checkers read its annotations. Type aliases used below, from kosa8.client and kosa8.transport:

Env          = Union[Mapping[str, str], Sequence[str], None]       # {"A": "1"} or ["A=1"]
Chunk        = Callable[[bytes], None]                             # output callbacks
EgressPolicy = Union[str, Mapping[str, Sequence[str]], None]       # "deny" | "any" | "fakenet" | "allow:a,b" | {"allow": [...]}
Body         = Union[bytes, str, Iterable[Union[bytes, str]]]      # write_stdin data
Query        = Mapping[str, Union[str, int, bool, None]]

Kosa8Client

Kosa8Client(
    *,
    socket_path: Optional[str] = None,
    host: Optional[str] = None,
    token: Optional[str] = None,
    insecure: bool = False,
    actor: str = "tool",
    agent_token: Optional[str] = None,
    headers: Optional[Mapping[str, str]] = None,
    timeout: Optional[float] = None,
    checkpoint_store: Any = None,
    env: Optional[Mapping[str, str]] = None,
)

All arguments are keyword-only. Constructing the client sends nothing; it resolves the endpoint (and can raise Kosa8Error for a refused address or a bad actor).

ParameterTypeDefaultMeaning
socket_pathstr~/.kosa8/run/kosa8.sockThe daemon's unix socket. Ignored when a host is set.
hoststrKOSA8_HOSTA remote worker: https://…, http://…, tcp://h:p, h:p, or unix:///path. See resolve_endpoint.
tokenstrKOSA8_TOKEN, only when the host came from KOSA8_HOSTBearer token, sent as Authorization: Bearer … to a TCP host only.
insecureboolFalse (or KOSA8_INSECURE=1)Allow plain http to a non-loopback host.
actorstr"tool""human", "agent" or "tool"; sent as X-Kosa8-Actor and recorded in the audit log. Anything else raises Kosa8Error.
agent_tokenstrKOSA8_AGENT_TOKENAgent identity, sent as X-Kosa8-Agent-Token.
headersMapping[str, str]noneExtra headers on every request. The actor, approval, agent token and authorization headers are set after these and win.
timeoutfloatNone (wait forever)Socket timeout in seconds, applied to connect and to each read. Per-call timeout= overrides it.
checkpoint_storestoreFileCheckpointStore()Where checkpoint stacks are kept. Any object with load() and update(fn).
envMapping[str, str]os.environWhere KOSA8_* variables are read from. Pass {} to ignore the environment.
AttributeTypeMeaning
transportTransportThe HTTP layer; transport.target says where requests go.
checkpointsCheckpointBookThe checkpoint stack logic over checkpoint_store; checkpoints.store is the store.

Every method that takes approval: Optional[str] = None sends it, stripped, as the X-Kosa8-Approval header. Methods without it in their signature send none. Every method can raise Kosa8ConnectionError (daemon unreachable), Kosa8TimeoutError (a timeout was set and passed), and the HTTP error class matching the daemon's status; only the extra errors are listed per method. Paths below are shortened: … is /kosa8/v1, and every path segment is percent-encoded.

Status and the shared engine

SignatureReturnsHTTP
health(*, approval=None) -> Dict[str, Any]{status, version, commit, go, uptime_seconds, phase}GET …/health
version() -> strhealth()["version"], such as "0.9.36"GET …/health
pull(ref: str, *, approval=None) -> Dict[str, Any]The daemon's answer. Pulls into the host's shared engine, not a sandbox.POST …/pull, body {"ref"}

Sandboxes

SignatureReturnsHTTP
list_sandboxes() -> List[Dict[str, Any]]Sandbox records; [] when the daemon answers null.GET …/sandboxes
get_sandbox(id_or_name: str) -> Optional[Dict[str, Any]]The record whose id matches, else whose name matches, else None. Never raises for a missing sandbox.GET …/sandboxes, filtered in the client
create_sandbox(*, name=None, cpus=None, mem_mb=None, disk_gb=None, mounts=None, egress=None, egress_allow=None, capture=None, labels=None, broker=None, approval=None) -> Dict[str, Any]{sandbox, boot_ms}, plus broker when one was asked forPOST …/sandboxes
start_sandbox(sandbox: str, *, approval=None) -> Dict[str, Any]{sandbox, boot_ms} (and warning when the daemon has one)POST …/sandboxes/{id}/up
stop_sandbox(sandbox: str, *, approval=None) -> Dict[str, Any]{ok, sandbox} (and warning, already_stopped when they apply)POST …/sandboxes/{id}/down
delete_sandbox(sandbox: str, *, wipe: bool = False, keep_checkpoints: bool = False, approval=None) -> Dict[str, Any]The daemon's answer plus checkpoints_dropped: intDELETE …/sandboxes/{id} (?wipe=1 when wipe), then DELETE …/snapshots/{ckpt} per checkpoint
pull_into_sandbox(sandbox: str, ref: str, *, approval=None) -> Dict[str, Any]{ok, image}POST …/sandboxes/{id}/pull, body {"ref"}
sandbox_images(sandbox: str) -> List[str]Image references in the sandbox's engineGET …/sandboxes/{id}/images
sandbox_containers(sandbox: str) -> List[Dict[str, Any]][{id, image, status, exit?, ip?}]GET …/sandboxes/{id}/containers
sandbox_egress(sandbox: str) -> Dict[str, Any]{policy, stats, …}: the rule the host enforces and what it decidedGET …/sandboxes/{id}/egress

create_sandbox parameters

A parameter that is None, empty or zero is left out of the request body, and the daemon applies its default.

ParameterTypeSent asMeaning
namestrnameThe sandbox name. Omit it and the daemon picks one.
cpusintcpusVirtual CPUs.
mem_mbintmem_mbMemory in MiB.
disk_gbintdisk_gbDisk size in GB.
mountsList[Dict]mountsHost folders: {"host_path", "guest_path", "read_only"}.
egressEgressPolicyegress"deny", "any", "fakenet", "allow:a.com,b.com", or {"allow": [...]} (sent as "allow:…"). None: the org policy's default.
egress_allowSequence[str]egress: "allow:…"Shorthand for an allow-list. Combining it with an egress other than None or "deny" raises Kosa8Error.
captureDictcapturePacket capture: {"path", "max_bytes"?, "max_files"?}; path absolute, on the host.
labelsMapping[str, str]labelsFree-form labels kept on the record.
brokerDictbrokerEgress broker spec, passed through: {"allow"?: [{"host", "ports"?, "methods"?}], "routes"?: [{"name", "upstream", "header", "secret", "methods"?, "paths"?, "scheme"?}]}.
approvalstrheaderA signed approval id.

Extra errors: Kosa8Error (client side) for egress={"allow": []} ("an egress allow-list needs at least one host…") and for egress_allow with another egress.

Running code

SignatureReturnsHTTP
run(sandbox: str, image: str, args: Sequence[str] = (), *, env: Env = None, on_stdout: Optional[Chunk] = None, on_stderr: Optional[Chunk] = None, on_status: Optional[Chunk] = None, timeout: Optional[float] = None, approval=None) -> ExecResultExecResultPOST …/sandboxes/{id}/run, body {"image", "args", "env"?}, NDJSON answer
start_container(sandbox: str, image: str, args: Sequence[str] = (), *, approval=None) -> strThe container idPOST …/sandboxes/{id}/start, body {"image", "args"}
exec(sandbox: str, container: str, args: Sequence[str], *, on_stdout=None, on_stderr=None, timeout=None, approval=None) -> ExecResultExecResultPOST …/sandboxes/{id}/exec, body {"container", "args"}
exec_frames(sandbox: str, container: str, args: Sequence[str], *, timeout=None, approval=None) -> Iterator[Dict[str, Any]]A generator of frames (below). Nothing is sent until you start iterating.POST …/sandboxes/{id}/exec
start_interactive(sandbox: str, image: str, args: Sequence[str] = (), *, env: Env = None, tty: bool = False, approval=None) -> strThe container idPOST …/sandboxes/{id}/start-interactive, body {"image", "args", "env", "tty"}
write_stdin(sandbox: str, container: str, data: Body, *, approval=None) -> Dict[str, Any]{ok}. The body is the container's whole stdin: the daemon closes stdin when it ends. 0.1.1: a second call for the same (sandbox, container) strings on the same client raises Kosa8Error (stdin of container … was already sent and is closed …) without sending; 0.1.0 sent it and answered {ok} for input never readPOST …/sandboxes/{id}/containers/{cid}/stdin, application/octet-stream; an iterable is sent chunked
attach(sandbox: str, container: str, *, on_stdout=None, on_stderr=None, timeout=None, approval=None) -> ExecResultExecResult when the container exitsPOST …/sandboxes/{id}/containers/{cid}/attach

A frame is a dict as the daemon sends it, with data base64-decoded to bytes: output frames {"stream": "stdout" | "stderr" | "status", "data": b"…"}, then a final {"done": True, "ok": …, "exit": int}, or {"done": True, "error": "…"}. exec_frames yields them unchanged and raises nothing for an error frame; an HTTP error before streaming starts is raised as its typed error.

Snapshots, fork, explore

SignatureReturnsHTTP
snapshot(sandbox: str, *, id: Optional[str] = None, encrypt: bool = False, approval=None) -> Dict[str, Any]{snapshot, snapshot_ms} (and encrypted_by_policy, encrypt_ms, encryption when encrypted)POST …/sandboxes/{id}/snapshot, body {"id"?, "encrypt"?: true}
list_snapshots() -> List[Dict[str, Any]]Snapshot recordsGET …/snapshots
restore_snapshot(snapshot_id: str, *, name: Optional[str] = None, label: Optional[str] = None, allow_tampered: bool = False, approval=None) -> Dict[str, Any]{sandbox, restore_ms} (and seal for a sealed snapshot)POST …/snapshots/{id}/restore, body {"name"?, "label"?, "allow_tampered"?: true}
delete_snapshot(snapshot_id: str, *, wipe: bool = False, force: bool = False, approval=None) -> Dict[str, Any]{ok, removed, encrypted, …}DELETE …/snapshots/{id}, ?wipe=1, ?force=1 when set
fork(sandbox: str, n: int = 1, *, prefix: Optional[str] = None, label: Optional[str] = None, approval=None) -> Dict[str, Any]{forks: [sandbox records], snapshot, fork_ms, count}POST …/sandboxes/{id}/fork, body {"n", "prefix"?, "label"?}
explore(sandbox: str, variants: Sequence[str], *, image: str = "alpine:latest", keep_all: bool = False, keep: Optional[str] = None, timeout: float = 120.0, prefix: Optional[str] = None, pull: bool = True, approval=None) -> ExploreResultExploreResultpull_into_sandbox (if pull), fork, one run per branch in parallel, then DELETE …/sandboxes/{branch} for the losers

explore parameters

ParameterDefaultMeaning
variants(required)Shell commands, one per branch, each run as ["sh", "-c", variant]. At least two, or Kosa8Error.
image"alpine:latest"The image each variant runs in.
keep_allFalseKeep every branch instead of deleting the losers.
keepNoneKeep this branch name as the winner. Not one of the branches: Kosa8Error, and every branch is left in place.
timeout120.0Seconds, per branch, passed to run as its socket timeout.
prefixf"{sandbox}-explore-{int(time.time()) % 100000}"Branch name prefix.
pullTruePull image into the parent first.

Branches run on a ThreadPoolExecutor with one worker per variant. A branch whose error message looks transient (connection refused, EOF, reset, broken pipe, "no such file or directory", "stream ended", unreachable) is retried up to four attempts, waiting 0.5, 1 and 1.5 seconds between them (0.1.0 also waited 2 seconds after the last); a timeout is never retried. The winner is the fastest branch with no error and exit 0; "" when none succeeded. A failed delete of a loser is not raised; removed counts the ones that went. Extra errors: Kosa8Error when the fork returned fewer copies than variants.

Checkpoints

Client-side, over snapshots. The daemon has no checkpoint endpoint.

SignatureReturnsHTTP
checkpoint(sandbox: str, *, label: str = "", approval=None) -> Dict[str, Any]{checkpoint, evicted: List[str], snapshot}: the stored record, the ids evicted, and the snapshot() answerPOST …/sandboxes/{id}/snapshot with id <sandbox>-ckpt-<n>, then DELETE …/snapshots/{old} per eviction
list_checkpoints(sandbox: str) -> List[Dict[str, Any]]Records {id, sandbox, label, created}, oldest firstnone: the store
rewind(sandbox: str, *, to: Optional[str] = None, approval=None) -> Dict[str, Any]{sandbox, checkpoint, dropped: List[str], restore_ms}DELETE …/sandboxes/{id}, POST …/snapshots/{ckpt}/restore with {"name": sandbox}, then DELETE …/snapshots/{later} per dropped checkpoint

to is a checkpoint id or a label (case-insensitive; the newest match wins); without it, the latest. Extra errors: Kosa8CheckpointError for no checkpoints, no match, or an invalid sandbox name (empty, ., .., containing / or \, or starting with a dot). If the restore fails after the delete, rewind raises Kosa8CheckpointError (0.1.1; a plain Kosa8Error saying "is now stopped" in 0.1.0) 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 restore_snapshot('ID', name='NAME'), chained to the daemon's error. checkpoint (0.1.1): when the snapshot is refused with a 409 whose message contains "already exists" (a concurrent checkpoint took the id), it tries the next number, up to ten attempts in all; any other 409, or the tenth, is raised. Failures deleting evicted or dropped snapshots are not raised.

Helper

SignatureReturns
sandbox(sandbox_id: str) -> SandboxA handle bound to one sandbox. Sends nothing.

Sandbox

Sandbox(client: Kosa8Client, sandbox_id: str)
client: Kosa8Client
id: str
repr(sb)  # "Sandbox('doc-py-guide')"

Each method calls the client method with the sandbox filled in, passing other keyword arguments through (**kw), so the options and errors are the client's.

MethodCalls
info() -> Optional[Dict]get_sandbox(id)
start(**kw) -> Dictstart_sandbox(id, **kw)
stop(**kw) -> Dictstop_sandbox(id, **kw)
delete(**kw) -> Dictdelete_sandbox(id, **kw)
pull(ref, **kw) -> Dictpull_into_sandbox(id, ref, **kw)
run(image, args=(), **kw) -> ExecResultrun(id, image, args, **kw)
start_container(image, args=(), **kw) -> strstart_container(id, image, args, **kw)
exec(container, args, **kw) -> ExecResultexec(id, container, args, **kw)
containers() -> List[Dict]sandbox_containers(id)
images() -> List[str]sandbox_images(id)
egress() -> Dictsandbox_egress(id)
snapshot(**kw) -> Dictsnapshot(id, **kw)
fork(n=1, **kw) -> Dictfork(id, n, **kw)
explore(variants, **kw) -> ExploreResultexplore(id, variants, **kw)
checkpoint(**kw) -> Dictcheckpoint(id, **kw)
checkpoints() -> List[Dict]list_checkpoints(id)
rewind(**kw) -> Dictrewind(id, **kw)
exec_frames(container, args, **kw) -> Iterator[Dict] (0.1.1)exec_frames(id, container, args, **kw)
start_interactive(image, args=(), **kw) -> str (0.1.1)start_interactive(id, image, args, **kw)
write_stdin(container, data, **kw) -> Dict (0.1.1)write_stdin(id, container, data, **kw)
attach(container, **kw) -> ExecResult (0.1.1)attach(id, container, **kw)

Not on the handle: restore_snapshot, delete_snapshot, list_snapshots, and the host-engine pull. Call them on the client. (In 0.1.0, exec_frames, start_interactive, write_stdin and attach were missing from the handle too.)

Dataclasses

ExecResult

@dataclass
class ExecResult:
    exit_code: int
    stdout_bytes: bytes = b""
    stderr_bytes: bytes = b""

    stdout: str     # property: stdout_bytes decoded as UTF-8, errors replaced
    stderr: str     # property: the same for stderr_bytes
    ok: bool        # property: exit_code == 0

When the final frame carries no integer exit, exit_code is 0.

ExploreBranch

@dataclass
class ExploreBranch:
    name: str                    # the branch sandbox's id
    variant: str                 # the shell command it ran
    exit: int = 0
    output: str = ""             # stdout + stderr, trailing newlines stripped
    duration_ms: int = 0         # including retries
    error: Optional[str] = None  # the last error, if the run never finished

ExploreResult

@dataclass
class ExploreResult:
    parent: str
    branches: List[ExploreBranch] = field(default_factory=list)
    winner: str = ""             # "" when no branch succeeded
    removed: int = 0             # branches deleted

Result shapes

Methods other than the three above return the daemon's JSON as plain dicts and lists. The keys you will meet most, as kosa8 0.9.36 sends them:

ShapeKeys
sandbox recordid, name, created, state (running, paused, stopped), cpus, mem_mb, and when set disk_gb, parent, mounts, egress, capture, ip, labels, broker
snapshot recordid, sandbox, created, cpus, mem_mb, cmdline, size_bytes, state_path, and optional fields such as disk_gb, mounts, egress, sealed, labels, encryption
checkpoint recordid, sandbox, label, created (RFC 3339, UTC). Records written by the Go tools may also carry auto.
containerid, image, status, and exit, ip when known
egress statuspolicy, stats: {passed, denied, dropped, dns_answered, dns_refused, …}, and capture, wildcards, fakenet when they apply

Transport and endpoints

Transport

Transport(
    *,
    socket_path: Optional[str] = None,
    host: Optional[str] = None,
    token: Optional[str] = None,
    insecure: bool = False,
    actor: str = "tool",
    agent_token: Optional[str] = None,
    headers: Optional[Mapping[str, str]] = None,
    timeout: Optional[float] = None,
    env: Optional[Mapping[str, str]] = None,
)

The same connection parameters as Kosa8Client, without checkpoint_store. It opens a new connection per request.

MemberMeaning
endpointThe resolved Endpoint.
actor, agent_token, extra_headers, timeoutAs given (agent_token falls back to KOSA8_AGENT_TOKEN).
target -> strProperty: unix:///path or scheme://host[:port].
raw(method, path, *, query=None, body=None, raw_body=None, approval=None, headers=None, timeout=None) -> ResponseSends a request and returns once the headers arrive. body is JSON-encoded; raw_body (bytes, str or an iterable of them) is sent as application/octet-stream, chunked for an iterable. In query, None and False are dropped and True becomes "1". Does not raise for HTTP errors. Raises Kosa8TimeoutError or Kosa8ConnectionError.
json(method, path, **kw) -> Anyraw, then the body decoded as JSON (None for an empty body, the text if it is not JSON). A status of 400 or more raises error_from_response(…).
frames(method, path, **kw) -> Iterator[Dict]raw, then one decoded frame per NDJSON line, data base64-decoded. An HTTP error before streaming raises the typed error; a read timeout mid-stream raises Kosa8TimeoutError; a line that is not valid JSON, or whose data is not valid base64, raises Kosa8ProtocolError (0.1.1; 0.1.0 let the decoding error escape).

Response (returned by raw, in kosa8.transport) has status, headers, read() -> bytes, lines() -> Iterator[bytes] and close(), and is a context manager; read and lines close it when done.

resolve_endpoint

resolve_endpoint(
    host: Optional[str] = None,
    socket_path: Optional[str] = None,
    token: Optional[str] = None,
    insecure: bool = False,
    env: Optional[Mapping[str, str]] = None,
) -> Endpoint

Where to connect, by the kosa8 CLI's rules:

  1. The value is host if given (even ""), else KOSA8_HOST, stripped of spaces and trailing slashes. Empty: the unix socket at socket_path or default_socket_path().
  2. The scheme is lower-cased. unix:///path is a socket. tcp:// is read as http://. No scheme (host:port) is read as http://. Any other scheme raises Kosa8Error (KOSA8_HOST scheme "ftp" is not supported; use http://, https://, tcp:// or unix://). No host name raises Kosa8Error.
  3. Plain http to a host other than localhost, ::1 or 127.* raises Kosa8Error (refusing to talk to … over plain http …) unless insecure or KOSA8_INSECURE=1.
  4. The token is token if given; otherwise KOSA8_TOKEN, but only when the host came from KOSA8_HOST (never with an explicit host). A path in the URL is kept as a prefix for every request.

Endpoint (in kosa8.transport) is a frozen dataclass: kind ("unix" or "tcp"), socket_path, scheme, host, port, base_path, token, and the property target.

Other transport names

NameMeaning
default_socket_path() -> stros.path.join(os.path.expanduser("~"), ".kosa8", "run", "kosa8.sock")
kosa8.transport.APPROVAL_HEADER"X-Kosa8-Approval"
kosa8.transport.ACTOR_HEADER"X-Kosa8-Actor"
kosa8.transport.AGENT_TOKEN_HEADER"X-Kosa8-Agent-Token"
kosa8.transport.decode_frame(line) -> DictOne NDJSON frame with data base64-decoded.
kosa8.transport.enc(segment) -> strPercent-encodes one path segment (nothing is safe, so / becomes %2F).

These six live in kosa8.transport and are not re-exported from kosa8.

Checkpoint stores

NameSignature and members
FileCheckpointStoreFileCheckpointStore(path: Optional[str] = None, *, lock_timeout: float = 10.0) (lock_timeout is 0.1.1). Attributes path, lock_timeout. path defaults to ~/.kosa8/checkpoints.json, the file the CLI, MCP server and desk use. load() -> Dict[str, List[Dict]] ({} if missing or corrupt). update(fn) -> T: takes an in-process lock and an exclusive fcntl.flock on <path>.lock (the same lock the Go tools take; in-process only where fcntl is missing). 0.1.1: the flock is tried without blocking every 50 ms, and after lock_timeout seconds raises Kosa8CheckpointError (timed out after 10s waiting for PATH.lock; another kosa8 process is holding it); 0.1.0 blocked indefinitely, loads, calls fn(data) to change it in place, writes <path>.tmp with mode 0600 and renames it over the file. Creates the directory with mode 0700.
MemoryCheckpointStoreMemoryCheckpointStore(). load() returns a deep copy; update(fn) runs fn on a copy under a lock and keeps it. Nothing is shared with the CLI.
CheckpointBookCheckpointBook(store), available as client.checkpoints. store; next_id(sandbox) -> Tuple[str, List[str]] (the next <sandbox>-ckpt-<n>, with n one past the larger of the stack size and the highest number used, and the ids to evict); record(cid, sandbox, label="", evicted=None) -> Dict; list(sandbox) -> List[Dict] (sorted by created); latest(sandbox) -> Dict; resolve(sandbox, ref) -> Dict; pop(sandbox, cid) -> List[str] (keeps cid on top, returns the later ids); forget(sandbox) -> List[str] (drops the stack, returns its ids). Raises Kosa8CheckpointError.

The store data is {sandbox_name: [checkpoint record, …]}. Any object with load() and update(fn) of these shapes can be passed as checkpoint_store. kosa8.checkpoints.validate_sandbox_name(name) is the name check used by next_id and record.

Errors

ClassBaseRaised for
Kosa8ErrorExceptionBase class; client-side checks.
Kosa8ConnectionErrorKosa8ErrorDaemon unreachable (any OSError while sending).
Kosa8TimeoutErrorKosa8ErrorA socket timeout passed.
Kosa8ProtocolError (0.1.1)Kosa8ErrorA streamed frame that is not valid NDJSON (bad JSON or base64). method, path set; the decoding error is __cause__.
Kosa8StreamErrorKosa8ErrorError frame, or no final frame. stdout, stderr.
Kosa8CheckpointErrorKosa8ErrorCheckpoint bookkeeping; from 0.1.1 also a checkpoint lock timeout and a rewind whose restore failed.
Kosa8ApiErrorKosa8ErrorAny HTTP error status without a more specific class (500, 503, …).
Kosa8BadRequestErrorKosa8ApiError400
Kosa8AuthErrorKosa8ApiError401
Kosa8PolicyErrorKosa8ApiError403
Kosa8ApprovalRequiredErrorKosa8PolicyError403 whose body's approval is a dict with a string why. approval: dict.
Kosa8NotFoundErrorKosa8ApiError404
Kosa8ConflictErrorKosa8ApiError409
Kosa8UnsupportedErrorKosa8ApiError501

Every class has the attributes message, status, daemon_message, remediation, method, path and body (each None when not known). The constructor is Kosa8Error(message, *, status=None, daemon_message=None, remediation=None, method=None, path=None, body=None); Kosa8ApprovalRequiredError(message, approval, **kw); Kosa8StreamError(message, stdout="", stderr="", **kw).

error_from_response(status: int, body: Any, *, method: Optional[str] = None, path: Optional[str] = None) -> Kosa8ApiError

Builds the typed error for an HTTP error answer, as the client does. daemon_message is the body's error, or else its message, or a non-empty text body; remediation is the body's remediation. The message is "<daemon_message> (<remediation>)", or the daemon message alone, or "request failed with HTTP <status>". Details and real messages: Python errors and troubleshooting.