kosa8 docs

Guides / Java / API reference

Java API reference

Every public type of com.kosa8:kosa8 0.1.2, package com.kosa8. Signatures are copied from the source, and the list of members on this page was checked against javap -public run on the jar from Maven Central. Paths are the daemon's native API; … stands for /kosa8/v1/sandboxes/{sandbox}.

How this page was tested

The member list was produced with javap -public (OpenJDK 21.0.12.1), first on kosa8-0.1.0.jar from Maven Central and then on the classes built from the v0.1.1 tag (commit 6d3f129) and the v0.1.2 tag (commit 14e5fc6) for the members those releases added; 0.1.2 was built from the v0.1.2 tag (commit 14e5fc6) with mvn install, because Maven Central was still answering 404 for 0.1.2 while it was being published. Every public member it printed is on this page. Members and behaviour new or changed since 0.1.0 are marked with the release that brought them, such as 0.1.1. Behaviour notes come from reading the source of that version and, where marked "seen", from runs against kosa8 daemon 0.9.36 on macOS 26.0.1 (the programs are on the developer guide and errors pages).

What this is, in plain words

This page is the dictionary: every class and method the Java SDK offers, with exactly what it takes, what it gives back and how it can fail. If you are new to kosa8, start with the quickstart; come here when you need the precise detail of one call.

Conventions

Kosa8Client

public final class Kosa8Client {
  public static final String VERSION = "0.1.2";
  public static Builder builder();
  public Endpoint endpoint();
  public Sandbox sandbox(String id);
  // … the methods in the tables below
}

There is no public constructor; use builder(). endpoint() reports where requests go. sandbox(id) returns a Sandbox handle and sends nothing. VERSION is the SDK's own version. The class does not implement AutoCloseable: it holds no connection.

Kosa8Client.Builder

public static final class Builder {
  public Builder();
  public Builder socketPath(String v);
  public Builder host(String v);
  public Builder token(String v);
  public Builder insecure(boolean v);
  public Builder actor(String v);
  public Builder agentToken(String v);
  public Builder headers(Map<String, String> v);
  public Builder timeout(Duration v);
  public Builder checkpoints(CheckpointStore v);
  public Builder getenv(Function<String, String> v);
  public Kosa8Client build();
}
MethodDefaultEffect
socketPathEndpoint.defaultSocketPath()The daemon's unix socket. Used only when no host is set here or in KOSA8_HOST.
host$KOSA8_HOSTA worker. Forms and rules under Endpoint.resolve. An explicit value wins over the variable.
token$KOSA8_TOKEN, only when the host came from KOSA8_HOSTSent as Authorization: Bearer … to a network endpoint only, never over a unix socket. An empty string means none.
insecurefalse (or KOSA8_INSECURE=1)Allows plain http:// to a non-loopback host.
actor"tool"human, agent or tool, sent as X-Kosa8-Actor. Anything else: build() throws Kosa8Exception.
agentToken$KOSA8_AGENT_TOKENSent as X-Kosa8-Agent-Token when non-empty.
headersnoneExtra header lines on every request, written after X-Kosa8-Actor and before X-Kosa8-Approval, X-Kosa8-Agent-Token, Authorization and Content-Type. Added, never substituted.
timeoutnoneLimit for every request without its own. Zero means none. Fires as TimeoutException by closing the connection.
checkpointsnew FileCheckpointStore() (a new instance per build())The checkpoint store. See Checkpoint stores.
getenvSystem::getenvWhere KOSA8_HOST, KOSA8_TOKEN, KOSA8_INSECURE and KOSA8_AGENT_TOKEN are read.
buildResolves the endpoint, checks the actor, picks the store. Sends nothing. Throws Kosa8Exception for a refused host or actor.

Daemon

MethodReturnsHTTP
Map<String, Object> health()Keys status, version, commit, go, uptime_seconds, phase (seen)GET /kosa8/v1/health
String version()String.valueOf(health().get("version")); "null" if absentGET /kosa8/v1/health
Map<String, Object> pull(String ref)
Map<String, Object> pull(String ref, Options.Approval o)
The daemon's answerPOST /kosa8/v1/pull {"ref"}: into the host's shared engine, not a sandbox

Sandboxes

MethodReturnsHTTP and notes
List<Map<String, Object>> listSandboxes()Every sandbox: id, name, created, state, cpus, mem_mb, disk_gb, egress, ip, and parent, labels when set (seen)GET /kosa8/v1/sandboxes
Map<String, Object> getSandbox(String idOrName)The first sandbox whose id, then name, equals the argument; null when none doesGET /kosa8/v1/sandboxes, filtered in the client
Map<String, Object> createSandbox(Options.Create o)Keys sandbox (an object), boot_ms (seen)POST /kosa8/v1/sandboxes with the body. Blocks until booted. Throws Kosa8Exception before sending if egressAllow is combined with an egress other than deny; ConflictException for a name in use
Map<String, Object> startSandbox(String sandbox)
… startSandbox(String sandbox, Options.Approval o)
Keys sandbox, boot_ms (seen)POST …/up. A missing sandbox is a 500 ApiException on 0.9.36
Map<String, Object> stopSandbox(String sandbox)
… stopSandbox(String sandbox, Options.Approval o)
Keys ok, sandbox, warning (seen)POST …/down. The disk is kept
Map<String, Object> deleteSandbox(String sandbox)
… deleteSandbox(String sandbox, Options.Delete o)
The daemon's answer (ok) plus checkpoints_dropped (an Integer)DELETE … (?wipe=1 when wipe). If that succeeds and not keepCheckpoints: removes the sandbox's stack from the store and sends DELETE /kosa8/v1/snapshots/{id} for each record (with the same wipe and approval), ignoring Kosa8Exceptions from either step
Map<String, Object> pullIntoSandbox(String sandbox, String ref)
… pullIntoSandbox(String sandbox, String ref, Options.Approval o)
Keys ok, image (the full reference, seen)POST …/pull {"ref"}. The host fetches the image and copies it in, so it works with egress denied
List<String> sandboxImages(String sandbox)Image references, as docker.io/library/alpine:latestGET …/images
List<Map<String, Object>> sandboxContainers(String sandbox)id, image, status, ip (seen)GET …/containers
Map<String, Object> sandboxEgress(String sandbox)policy and stats (passed, denied, dropped, dns_answered, dns_refused; seen)GET …/egress

Running code

MethodReturnsHTTP and notes
Results.Exec run(String sandbox, String image, List<String> args)
Results.Exec run(String sandbox, String image, List<String> args, Options.Run o)
The collected result when the container exitsPOST …/run {"image","args","env"?}; NDJSON frames. A fresh container from an image already in the sandbox. args null is sent as []
String startContainer(String sandbox, String image, List<String> args)
… startContainer(…, Options.Approval o)
The container id (String.valueOf of the answer's id)POST …/start {"image","args"}. Returns once started
Results.Exec exec(String sandbox, String container, List<String> args)
Results.Exec exec(…, Options.Run o)
The collected resultPOST …/exec {"container","args"}; NDJSON. A non-empty o.env throws Kosa8Exception("exec does not take env: …") before sending 0.1.1 (0.1.0 dropped it silently)
void execFrames(String sandbox, String container, List<String> args, Consumer<Frame> fn, Options.Approval o)Nothing; fn is called for every frame, including the final done framePOST …/exec. No timeout; the client-wide timeout applies. An error frame is passed to fn, not thrown. StreamException only if the stream breaks or a line is not a frame
String startInteractive(String sandbox, String image, List<String> args, Options.Interactive o)The container idPOST …/start-interactive {"image","args","env","tty"} (env is sent as null when unset)
Map<String, Object> writeStdin(String sandbox, String container, byte[] data, Options.Approval o){ok=true} (seen)POST …/containers/{cid}/stdin, application/octet-stream with Content-Length. The container's stdin is closed when the body ends (seen). Once per container per client: a second call for the same sandbox and container throws Kosa8Exception("stdin of container … was already sent and is closed: …") before sending (seen) 0.1.1
Map<String, Object> writeStdin(String sandbox, String container, InputStream data, Options.Approval o)The daemon's answerThe same, sent with Transfer-Encoding: chunked in reads of up to 16 KiB, flushed after each, until read returns 0 or less. The stream is not closed by the SDK. The same once-per-container check applies; to feed input over time, pass a stream that yields as you go (a PipedInputStream)
Results.Exec attach(String sandbox, String container, Options.Run o)The collected result when the container exitsPOST …/containers/{cid}/attach; NDJSON

How run, exec and attach collect. Frames with stream stderr go to stderrBytes and onStderr; status frames go only to onStatus; any other stream, or none, goes to stdoutBytes and onStdout. Frames with no data are skipped. The first done frame ends collection: with error set it throws StreamException (with the output so far), otherwise it yields Exec(exit, stdout, stderr), with exit 0 if the frame had none. A stream that ends with no done frame throws StreamException("stream ended unexpectedly (no final frame)"); a broken stream throws StreamException("stream broke: …") with the output so far; a time limit throws TimeoutException, without output. A non-zero exit is not an exception. The daemon also reports an unpulled image or an unknown container this way (seen).

Snapshots, fork and explore

MethodReturnsHTTP and notes
Map<String, Object> snapshot(String sandbox, Options.Snapshot o)Keys snapshot (an object: id, sandbox, created, size_bytes, paths, …) and snapshot_ms (seen)POST …/snapshot {"id"?, "encrypt"?: true}
List<Map<String, Object>> listSnapshots()Every snapshot, checkpoints includedGET /kosa8/v1/snapshots
Map<String, Object> restoreSnapshot(String snapshotId, Options.Restore o)Keys sandbox, restore_ms (seen)POST /kosa8/v1/snapshots/{id}/restore {"name"?, "label"?, "allow_tampered"?: true}. A missing snapshot is a 500 ApiException on 0.9.36
Map<String, Object> deleteSnapshot(String snapshotId, Options.SnapshotDelete o)Keys ok, removed, encrypted (seen)DELETE /kosa8/v1/snapshots/{id} (?wipe=1, &force=1)
Results.Fork fork(String sandbox, int n, Options.Fork o)Results.ForkPOST …/fork {"n", "prefix"?, "label"?}; n below 1 is sent as 1
Results.Explore explore(String sandbox, List<String> variants, Options.Explore o)Results.ExploreClient-side: optional pull, one fork per variant, run(branch, image, ["sh","-c",variant]) on each in parallel, keep the fastest exit-0 branch, DELETE the rest. Throws Kosa8Exception for fewer than two variants, fewer forks than variants, or a keep name that is not a branch (all branches kept). Full algorithm in the developer guide

Checkpoints

MethodReturnsWhat it does
Results.CheckpointSaved checkpoint(String sandbox, String label)
… checkpoint(String sandbox, String label, Options.Approval o)
CheckpointSavedValidates the name; picks <sandbox>-ckpt-<n>; POST …/snapshot {"id"}, and on a 409 whose message contains already exists (another checkpoint took that id at the same moment) steps to n+1 and retries, up to 10 attempts; any other 409 is thrown 0.1.1 (seen: two clients at once got ckpt-1 and ckpt-2); if the stack already had 10, deletes the oldest snapshots (best effort); records the checkpoint (label null is stored as ""). Throws CheckpointException for an invalid name, before any request
List<Checkpoint> listCheckpoints(String sandbox)The stack, oldest first (sorted by created); empty if noneReads the store only; no request
Results.Rewind rewind(String sandbox, String to)
… rewind(String sandbox, String to, Options.Approval o)
RewindResolves to (an id, or a label without regard to case, newest first; null = the latest); DELETE …; POST /kosa8/v1/snapshots/{id}/restore {"name": sandbox}; pops later checkpoints from the store and deletes their snapshots (best effort). Throws CheckpointException when there is no such checkpoint, and when the restore fails: the message says the sandbox no longer exists and the checkpoint is intact, and names the recovery call 0.1.1 (seen); the cause is the restore's exception. The store is not changed. A refused DELETE is thrown as is, with nothing changed

Sandbox

A handle bound to one sandbox id, from client.sandbox(id). No public constructor. Every method delegates to the client method in the right-hand column with the handle's id. An approval travels in the options object where a method takes one; start, stop, pull, startContainer, checkpoint and rewind take none, so use the client's overloads for gated calls of those.

MethodSame as
String id()the id given to sandbox(id)
Map<String, Object> info()getSandbox(id); null when it does not exist
Map<String, Object> start()startSandbox(id)
Map<String, Object> stop()stopSandbox(id)
Map<String, Object> delete()
Map<String, Object> delete(Options.Delete o)
deleteSandbox(id) / deleteSandbox(id, o)
Map<String, Object> pull(String ref)pullIntoSandbox(id, ref), not the client's pull
Results.Exec run(String image, List<String> args)
Results.Exec run(String image, List<String> args, Options.Run o)
run(id, …)
String startContainer(String image, List<String> args)startContainer(id, image, args)
Results.Exec exec(String container, List<String> args, Options.Run o)exec(id, container, args, o); there is no two-argument form, pass null
void execFrames(String container, List<String> args, Consumer<Frame> fn, Options.Approval o) 0.1.1execFrames(id, …)
String startInteractive(String image, List<String> args, Options.Interactive o) 0.1.1startInteractive(id, …)
Map<String, Object> writeStdin(String container, InputStream data, Options.Approval o)
Map<String, Object> writeStdin(String container, byte[] data, Options.Approval o) 0.1.1
writeStdin(id, …)
Results.Exec attach(String container, Options.Run o) 0.1.1attach(id, container, o)
List<Map<String, Object>> containers()sandboxContainers(id)
List<String> images()sandboxImages(id)
Map<String, Object> egress()sandboxEgress(id)
Map<String, Object> snapshot(Options.Snapshot o)snapshot(id, o)
Results.Fork fork(int n, Options.Fork o)fork(id, n, o)
Results.Explore explore(List<String> variants, Options.Explore o)explore(id, variants, o)
Results.CheckpointSaved checkpoint(String label)checkpoint(id, label)
List<Checkpoint> checkpoints()listCheckpoints(id)
Results.Rewind rewind(String to)rewind(id, to)
String toString()"Sandbox(" + id + ")"

Not on the handle: restoreSnapshot, deleteSnapshot, and the client overloads that take an Options.Approval for start, stop, pull, startContainer, checkpoint and rewind. (0.1.0's handle also lacked execFrames, startInteractive, writeStdin and attach.)

Options

A holder class (no instances) for the per-call option builders. Each is a public class with a public no-argument constructor; every setter returns this. Fields are package-private, so you cannot read back what you set. Unset fields are not sent.

Options.Call<T> and Options.Approval

public static class Call<T extends Call<T>> {
  public T approval(String a);          // X-Kosa8-Approval
}
public static final class Approval extends Call<Approval> {
  public static Approval of(String sig);
}

Every option class below extends Call, so all of them have approval(…).

Options.Create

SetterBody fieldNotes
name(String)nameSandbox name and id.
cpus(int)cpusDaemon default seen: 2.
memMb(int)mem_mbDaemon default seen: 1024.
diskGb(int)disk_gbDaemon default seen: 32.
mounts(List<String>)mounts: a list of {"host_path","guest_path","read_only"} objectsEach string host:guest, host:guest:ro or host:guest:rw; any other shape (wrong number of colons, an empty part) throws Kosa8Exception("mount \"…\": want host:guest or host:guest:ro") when the request is built. A host path that is not absolute (Path.of(host).isAbsolute()) or a guest path not starting with / throws Kosa8Exception("mount \"…\": both paths must be absolute (use Path.toAbsolutePath() for the host side)") 0.1.2 (seen). Visible inside containers (seen: a host file read at /work). 0.1.1: 0.1.0 sent the strings and every mount failed with 500 mount has no guest path.
egress(String)egressdeny | any | fakenet | allow:host,host.
egressAllow(List<String>)egress = allow: + the hosts, trimmed, comma-joinedIgnored when empty. With egress set to anything but deny: Kosa8Exception at send time.
capture(String hostFile) 0.1.1capture: {"path": hostFile}Record the sandbox's traffic to this host file as pcapng; kosa8 0.9.36 writes one file there, mode 0600 (seen). The folder must exist. Empty string: Kosa8Exception("capture needs a host file path to write the capture to") (seen). (0.1.1 named the parameter hostDir and said "directory"; corrected in 0.1.2.)
@Deprecated capture(boolean)nothingcapture(true) throws Kosa8Exception("capture(true) is not enough: the daemon needs a host file to write the capture to; use capture(\"/path/to/capture.pcapng\")") when the request is built (seen); capture(false) does nothing. A later capture(String) takes precedence.
labels(Map<String, String>)labelsFree-form tags, returned by listSandboxes.
broker(Object)brokerWritten with Json.write: pass a Map/List structure. Any other object is written as its toString() string.

Options.Delete

SetterDefaultEffect
wipe(boolean)false?wipe=1: overwrite the disks, not just unlink. Also passed to the checkpoint snapshots' deletes.
keepCheckpoints(boolean)falseLeave the sandbox's checkpoint records and snapshots in place.

Options.Run

For run, exec and attach.

SetterDefaultEffect
env(List<String>)noneKEY=VALUE strings. Sent by run only.
env(Map<String, String>)noneConverted to KEY=VALUE, in the map's iteration order. Replaces any earlier env.
onStdout(Consumer<byte[]>)noneCalled with each stdout chunk as it arrives, on the calling thread. Chunks are not lines.
onStderr(Consumer<byte[]>)noneThe same for stderr.
onStatus(Consumer<byte[]>)noneFrames on the status stream (pull and progress lines). Not collected into the result.
timeout(Duration)the client'sTime limit for this call. Ends the request, not the process.

Options.Interactive

SetterDefaultEffect
env(List<String>)nullKEY=VALUE for the container.
tty(boolean)falseAllocate a terminal.

Options.Snapshot, Options.Restore, Options.SnapshotDelete, Options.Fork

ClassSetterSent asEffect
Snapshotid(String)idThe snapshot id; the daemon picks one if unset.
encrypt(boolean)encrypt: trueEncrypt at rest once taken.
Restorename(String)nameName of the restored sandbox.
label(String)labelRaise the classification above the snapshot's (never lower).
allowTampered(boolean)allow_tampered: trueRestore a sealed snapshot whose seal does not verify; audited.
SnapshotDeletewipe(boolean)?wipe=1Overwrite the files.
force(boolean)?force=1Passed through to the daemon.
Forkprefix(String)prefixForks are named prefix1, prefix2, … (seen).
label(String)labelRaise the forks' classification above the parent's (never lower).

Options.Explore

SetterDefaultEffect
image(String)"alpine:latest"The image each variant runs in, as sh -c VARIANT.
pull(boolean)truePull image into the sandbox first. Set false when it is already there.
timeout(Duration)120 secondsPer branch.
prefix(String)<sandbox>-explore-<epoch seconds mod 100000>Branch name prefix.
keep(String)noneKeep this branch instead of the fastest success.
keepAll(boolean)falseDelete no branch.
approval(String)noneUsed for the pull, the fork, every run and every delete.

Results

A holder class for the result records. Records are immutable, but Exec's byte arrays are returned as is, and record equality compares arrays by reference, so two Execs with the same output are not equals.

public record Exec(int exitCode, byte[] stdoutBytes, byte[] stderrBytes) {
  public String stdout();    // UTF-8
  public String stderr();    // UTF-8
  public boolean ok();       // exitCode == 0
}
public record Fork(List<Map<String, Object>> forks, Object snapshot, long forkMs, int count) {}
public record ExploreBranch(String name, String variant, int exit, String output, String error, long durationMs) {}
public record Explore(String parent, List<ExploreBranch> branches, String winner, int removed) {}
public record CheckpointSaved(Checkpoint checkpoint, List<String> evicted, Map<String, Object> snapshot) {}
public record Rewind(Object sandbox, Checkpoint checkpoint, List<String> dropped, Object restoreMs) {}
Record componentMeaning
Fork.forksThe new sandboxes, as listSandboxes shows them.
Fork.snapshotThe daemon's snapshot value: the id of the snapshot the forks came from (a String, seen).
Fork.forkMs, Fork.countThe daemon's fork_ms and count; 0 when absent.
ExploreBranch.exitThe variant's exit code; -1 if it never ran.
ExploreBranch.outputstdout then stderr, trailing newlines removed.
ExploreBranch.errorThe exception message if the run failed; null otherwise.
ExploreBranch.durationMsWall time of the branch, retries included.
Explore.winnerThe kept branch's name; "" when no branch succeeded (and then none is kept, unless keepAll).
Explore.removedBranches deleted successfully.
CheckpointSaved.evictedIds of old checkpoints removed to stay at 10.
CheckpointSaved.snapshotThe daemon's answer to the snapshot: snapshot (object) and snapshot_ms.
Rewind.sandboxThe restored sandbox's map (from the restore answer).
Rewind.droppedIds of checkpoints after the target, now gone.
Rewind.restoreMsThe daemon's restore_ms (a Long).

Checkpoint

public record Checkpoint(String id, String sandbox, String label, boolean auto, Instant created) {}

id is <sandbox>-ckpt-<n>. label is never null ("" when none). auto is false for every checkpoint the SDK takes. created is Instant.EPOCH if the stored timestamp cannot be parsed.

Checkpoint stores

public interface CheckpointStore {
  Map<String, List<Checkpoint>> load();                                   // a copy of the stacks
  <T> T update(Function<Map<String, List<Checkpoint>>, T> fn);          // run fn on a mutable copy, save, serialised
}

public final class FileCheckpointStore implements CheckpointStore {
  public FileCheckpointStore();                 // ~/.kosa8/checkpoints.json (from user.home)
  public FileCheckpointStore(Path path);                          // lock timeout 10 s
  public FileCheckpointStore(Path path, Duration lockTimeout);    // 0.1.1
  public Path path();
  public Map<String, List<Checkpoint>> load();
  public <T> T update(Function<Map<String, List<Checkpoint>>, T> fn);
}

public final class MemoryCheckpointStore implements CheckpointStore {
  public MemoryCheckpointStore();
  public synchronized Map<String, List<Checkpoint>> load();
  public synchronized <T> T update(Function<Map<String, List<Checkpoint>>, T> fn);
}
StoreBehaviour
FileCheckpointStore.loadReads the file. Missing file: empty map. Unparseable file: empty map. Other read errors: UncheckedIOException. Records are {"id","sandbox","label","auto"?,"created"}; created is parsed with OffsetDateTime, which accepts the CLI's local offsets and nanoseconds.
FileCheckpointStore.updateTakes a JVM-wide ReentrantLock shared by every store on the same absolute, normalised path 0.1.1; creates the parent folder; takes <file>.lock with FileChannel.tryLock() (an fcntl lock on Unix), polling every 50 ms; loads; runs fn; writes <file>.tmp, sets it to 0600 where POSIX permissions exist, and renames it over the file atomically (falling back to a plain replace if the filesystem cannot). Both locks together wait at most lockTimeout (default 10 s), then throw CheckpointException("timed out after Ns waiting for …lock; another kosa8 process is holding it") (seen); an interrupt while waiting throws CheckpointException and sets the interrupt flag again. auto is omitted when false; created is written as an ISO-8601 UTC instant. An IOException becomes CheckpointException("update the checkpoint store …"). Two stores on one file in one JVM now queue (seen: the second waited 1027 ms, no exception); in 0.1.0 they threw OverlappingFileLockException. The CLI's flock excludes this lock on macOS (seen) but not on Linux.
MemoryCheckpointStoreA HashMap behind the instance's monitor. load and update work on copies, and update saves the copy only after fn returns. Nothing is shared with the CLI or other instances.

A custom store must return copies from load (the SDK does not modify them) and make update atomic with respect to other updates. If fn throws, the store should not save; both built-in stores propagate the exception without writing.

Lower-level pieces

Endpoint

public record Endpoint(String socketPath, String scheme, String host, int port, String basePath, String token) {
  public boolean isUnix();                     // socketPath != null
  public String target();                      // "unix://" + socketPath, or scheme://host:port (no token)
  public static String defaultSocketPath();    // $user.home/.kosa8/run/kosa8.sock
  public static Endpoint resolve(String host, String socketPath, String token, boolean insecure,
                                 Function<String, String> getenv);
}

resolve applies the CLI's KOSA8_HOST rules (what build() calls):

  1. No host and an empty KOSA8_HOST: the unix socket socketPath, or the default.
  2. The value is trimmed and trailing slashes removed; the scheme is lower-cased.
  3. unix:///path: that socket. tcp:// is treated as http://. No scheme: http://. Any other scheme: Kosa8Exception.
  4. No host in the URL: Kosa8Exception("KOSA8_HOST \"…\" names no host"). Default port 443 for https, 80 for http.
  5. Plain http to a host other than localhost, ::1 or 127.*, without insecure or KOSA8_INSECURE=1: Kosa8Exception.
  6. token: the argument, else KOSA8_TOKEN only when the host came from the environment; an empty token is null.
  7. basePath: the URL's path without trailing slashes, prefixed to every request.

getenv null means System::getenv. toString() is overridden and never prints the token 0.1.1: it gives Endpoint[unix:///path] or Endpoint[scheme://host:port/base, token=***] (seen: Endpoint[https://worker.example.com:443/k8, token=***]). token() still returns it.

Frame

public record Frame(String stream, byte[] data, boolean done, boolean ok, String error, Integer exit, String id) {}

One NDJSON line of a run, exec or attach stream. data is base64-decoded (empty array when absent); empty strings in stream, error and id become null; exit is null except on a final frame that carries one. On the final frame (done true), ok says the stream ended cleanly, not that the command succeeded (seen: ok=true exit=4). Frames reach you only through execFrames.

Json

public final class Json {
  public static Object parse(String s);     // throws IllegalArgumentException("bad JSON: …") on bad input
  public static String write(Object v);
}

parse: objects to LinkedHashMap<String, Object>, arrays to ArrayList<Object>, integers to Long (to Double if too big for a long), numbers with a fraction or exponent to Double, plus String, Boolean and null. Trailing data is an error. write: Map (keys via String.valueOf), Iterable, Object[], String, Boolean, Integer/Long/Short as is, other Numbers through double (whole values under 1015 without a fraction), null; anything else as the JSON string of its toString(). Non-ASCII is written as is (UTF-8).

Kosa8Exception

public class Kosa8Exception extends RuntimeException {
  public Kosa8Exception(String message);
  public Kosa8Exception(String message, Throwable cause);
  public Kosa8Exception(String message, int status, String daemonMessage, String remediation,
                        String method, String path, Object body, Throwable cause);
  public int status();              // HTTP status, or 0
  public String daemonMessage();    // the daemon's "error" text, verbatim
  public String remediation();      // the daemon's "remediation"
  public String method();
  public String path();
  public Object body();             // decoded response body
}
Nested classExtendsThrown forExtra
ConnectionException(String m, String method, String path, Throwable cause)Kosa8ExceptionDaemon unreachable; any I/O or parse failure while connecting or reading the status line and headers; an I/O failure reading the body of a JSON answer.getCause()
ApiException(String m, int st, String dm, String rem, String method, String path, Object body)Kosa8ExceptionHTTP status ≥ 400 with no subclass below (e.g. 500).
BadRequestException(…)ApiException400
AuthException(…)ApiException401
PolicyException(…)ApiException403
ApprovalRequiredException(…, Map<String, Object> approval)PolicyException403 whose body has an approval object with a string why.Map<String, Object> approval()
NotFoundException(…)ApiException404
ConflictException(…)ApiException409
UnsupportedException(…)ApiException501
StreamException(String m, String stdout, String stderr, String daemonMessage)Kosa8ExceptionError frame, broken stream, bad frame, or no final frame in run/exec/attach.String stdout(), String stderr()
CheckpointException(String m), CheckpointException(String m, Throwable cause)Kosa8ExceptionInvalid name, no checkpoint, store write failure, failed restore inside a rewind.
TimeoutException(String m, String method, String path)Kosa8ExceptionA request's time limit passed.

For an HTTP error the message is the body's error (or message), with (remediation) appended when the body has one; a non-JSON body is used as the message, trimmed; an empty one gives "request failed with HTTP <status>". Real messages and handling advice are on Java errors and troubleshooting.

See also

Quickstart · Developer guide · Errors and troubleshooting · Native HTTP API, which every method here wraps.