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}.
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.
ConventionsTypes, nulls, approvals, threads.
Kosa8ClientThe builder and every method with its endpoint.
SandboxThe handle bound to one sandbox.
OptionsWhat methods take.
ResultsWhat methods return.
Checkpoint storesWhere checkpoint stacks live.
Endpoint, Frame, JsonLower-level pieces.
Kosa8ExceptionEvery exception class.
Conventions
- Artifact.
com.kosa8:kosa8:0.1.2on Maven Central. Class files for Java 17 (major version 61); no dependencies. The jar has nomodule-infoand noAutomatic-Module-Name, so on the module path it is the automatic modulekosa8. - Exceptions are unchecked. Every method may throw a subclass of
Kosa8Exception(aRuntimeException). The column "throws" below lists only what is specific to a method; any call to the daemon can also throwConnectionException,TimeoutExceptionand anApiExceptionfor an HTTP error. - JSON values. Methods with no result record return the daemon's JSON decoded by
Json: objects asMap<String, Object>(insertion-ordered), arrays asList<Object>, whole numbers asLong, other numbers asDouble. An empty answer decodes tonull, which methods returning a map turn into an empty map. - Options may be
null. EveryOptions.*argument acceptsnull, meaning "none". Sandbox, container and snapshot ids may not benull. Ids are URL-encoded into the path. - Approvals. Every option class extends
Options.Calland carries an optional signed approval, sent as theX-Kosa8-Approvalheader (trimmed; not sent when blank). Methods whose only option is an approval takeOptions.Approval. - Transport. One connection per request, HTTP/1.1 with
Connection: close,Accept: application/jsonandX-Kosa8-Actoron every request. NoUser-Agentis sent. - Threads.
Kosa8ClientandSandboxare immutable and safe to share. Option builders are not. See Thread safety for the two checkpoint caveats.
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();
}
| Method | Default | Effect |
|---|---|---|
socketPath | Endpoint.defaultSocketPath() | The daemon's unix socket. Used only when no host is set here or in KOSA8_HOST. |
host | $KOSA8_HOST | A worker. Forms and rules under Endpoint.resolve. An explicit value wins over the variable. |
token | $KOSA8_TOKEN, only when the host came from KOSA8_HOST | Sent as Authorization: Bearer … to a network endpoint only, never over a unix socket. An empty string means none. |
insecure | false (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_TOKEN | Sent as X-Kosa8-Agent-Token when non-empty. |
headers | none | Extra 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. |
timeout | none | Limit for every request without its own. Zero means none. Fires as TimeoutException by closing the connection. |
checkpoints | new FileCheckpointStore() (a new instance per build()) | The checkpoint store. See Checkpoint stores. |
getenv | System::getenv | Where KOSA8_HOST, KOSA8_TOKEN, KOSA8_INSECURE and KOSA8_AGENT_TOKEN are read. |
build | Resolves the endpoint, checks the actor, picks the store. Sends nothing. Throws Kosa8Exception for a refused host or actor. |
Daemon
| Method | Returns | HTTP |
|---|---|---|
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 absent | GET /kosa8/v1/health |
Map<String, Object> pull(String ref)Map<String, Object> pull(String ref, Options.Approval o) | The daemon's answer | POST /kosa8/v1/pull {"ref"}: into the host's shared engine, not a sandbox |
Sandboxes
| Method | Returns | HTTP 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 does | GET /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:latest | GET …/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
| Method | Returns | HTTP 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 exits | POST …/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 result | POST …/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 frame | POST …/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 id | POST …/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 answer | The 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 exits | POST …/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
| Method | Returns | HTTP 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 included | GET /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.Fork | POST …/fork {"n", "prefix"?, "label"?}; n below 1 is sent as 1 |
Results.Explore explore(String sandbox, List<String> variants, Options.Explore o) | Results.Explore | Client-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
| Method | Returns | What it does |
|---|---|---|
Results.CheckpointSaved checkpoint(String sandbox, String label)… checkpoint(String sandbox, String label, Options.Approval o) | CheckpointSaved | Validates 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 none | Reads the store only; no request |
Results.Rewind rewind(String sandbox, String to)… rewind(String sandbox, String to, Options.Approval o) | Rewind | Resolves 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.
| Method | Same 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.1 | execFrames(id, …) |
String startInteractive(String image, List<String> args, Options.Interactive o) 0.1.1 | startInteractive(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.1 | attach(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
| Setter | Body field | Notes |
|---|---|---|
name(String) | name | Sandbox name and id. |
cpus(int) | cpus | Daemon default seen: 2. |
memMb(int) | mem_mb | Daemon default seen: 1024. |
diskGb(int) | disk_gb | Daemon default seen: 32. |
mounts(List<String>) | mounts: a list of {"host_path","guest_path","read_only"} objects | Each 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) | egress | deny | any | fakenet | allow:host,host. |
egressAllow(List<String>) | egress = allow: + the hosts, trimmed, comma-joined | Ignored when empty. With egress set to anything but deny: Kosa8Exception at send time. |
capture(String hostFile) 0.1.1 | capture: {"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) | nothing | capture(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>) | labels | Free-form tags, returned by listSandboxes. |
broker(Object) | broker | Written with Json.write: pass a Map/List structure. Any other object is written as its toString() string. |
Options.Delete
| Setter | Default | Effect |
|---|---|---|
wipe(boolean) | false | ?wipe=1: overwrite the disks, not just unlink. Also passed to the checkpoint snapshots' deletes. |
keepCheckpoints(boolean) | false | Leave the sandbox's checkpoint records and snapshots in place. |
Options.Run
For run, exec and attach.
| Setter | Default | Effect |
|---|---|---|
env(List<String>) | none | KEY=VALUE strings. Sent by run only. |
env(Map<String, String>) | none | Converted to KEY=VALUE, in the map's iteration order. Replaces any earlier env. |
onStdout(Consumer<byte[]>) | none | Called with each stdout chunk as it arrives, on the calling thread. Chunks are not lines. |
onStderr(Consumer<byte[]>) | none | The same for stderr. |
onStatus(Consumer<byte[]>) | none | Frames on the status stream (pull and progress lines). Not collected into the result. |
timeout(Duration) | the client's | Time limit for this call. Ends the request, not the process. |
Options.Interactive
| Setter | Default | Effect |
|---|---|---|
env(List<String>) | null | KEY=VALUE for the container. |
tty(boolean) | false | Allocate a terminal. |
Options.Snapshot, Options.Restore, Options.SnapshotDelete, Options.Fork
| Class | Setter | Sent as | Effect |
|---|---|---|---|
Snapshot | id(String) | id | The snapshot id; the daemon picks one if unset. |
encrypt(boolean) | encrypt: true | Encrypt at rest once taken. | |
Restore | name(String) | name | Name of the restored sandbox. |
label(String) | label | Raise the classification above the snapshot's (never lower). | |
allowTampered(boolean) | allow_tampered: true | Restore a sealed snapshot whose seal does not verify; audited. | |
SnapshotDelete | wipe(boolean) | ?wipe=1 | Overwrite the files. |
force(boolean) | ?force=1 | Passed through to the daemon. | |
Fork | prefix(String) | prefix | Forks are named prefix1, prefix2, … (seen). |
label(String) | label | Raise the forks' classification above the parent's (never lower). |
Options.Explore
| Setter | Default | Effect |
|---|---|---|
image(String) | "alpine:latest" | The image each variant runs in, as sh -c VARIANT. |
pull(boolean) | true | Pull image into the sandbox first. Set false when it is already there. |
timeout(Duration) | 120 seconds | Per branch. |
prefix(String) | <sandbox>-explore-<epoch seconds mod 100000> | Branch name prefix. |
keep(String) | none | Keep this branch instead of the fastest success. |
keepAll(boolean) | false | Delete no branch. |
approval(String) | none | Used 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 component | Meaning |
|---|---|
Fork.forks | The new sandboxes, as listSandboxes shows them. |
Fork.snapshot | The daemon's snapshot value: the id of the snapshot the forks came from (a String, seen). |
Fork.forkMs, Fork.count | The daemon's fork_ms and count; 0 when absent. |
ExploreBranch.exit | The variant's exit code; -1 if it never ran. |
ExploreBranch.output | stdout then stderr, trailing newlines removed. |
ExploreBranch.error | The exception message if the run failed; null otherwise. |
ExploreBranch.durationMs | Wall time of the branch, retries included. |
Explore.winner | The kept branch's name; "" when no branch succeeded (and then none is kept, unless keepAll). |
Explore.removed | Branches deleted successfully. |
CheckpointSaved.evicted | Ids of old checkpoints removed to stay at 10. |
CheckpointSaved.snapshot | The daemon's answer to the snapshot: snapshot (object) and snapshot_ms. |
Rewind.sandbox | The restored sandbox's map (from the restore answer). |
Rewind.dropped | Ids of checkpoints after the target, now gone. |
Rewind.restoreMs | The 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);
}
| Store | Behaviour |
|---|---|
FileCheckpointStore.load | Reads 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.update | Takes 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. |
MemoryCheckpointStore | A 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):
- No
hostand an emptyKOSA8_HOST: the unix socketsocketPath, or the default. - The value is trimmed and trailing slashes removed; the scheme is lower-cased.
unix:///path: that socket.tcp://is treated ashttp://. No scheme:http://. Any other scheme:Kosa8Exception.- No host in the URL:
Kosa8Exception("KOSA8_HOST \"…\" names no host"). Default port 443 for https, 80 for http. - Plain http to a host other than
localhost,::1or127.*, withoutinsecureorKOSA8_INSECURE=1:Kosa8Exception. token: the argument, elseKOSA8_TOKENonly when the host came from the environment; an empty token isnull.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 class | Extends | Thrown for | Extra |
|---|---|---|---|
ConnectionException(String m, String method, String path, Throwable cause) | Kosa8Exception | Daemon 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) | Kosa8Exception | HTTP status ≥ 400 with no subclass below (e.g. 500). | |
BadRequestException(…) | ApiException | 400 | |
AuthException(…) | ApiException | 401 | |
PolicyException(…) | ApiException | 403 | |
ApprovalRequiredException(…, Map<String, Object> approval) | PolicyException | 403 whose body has an approval object with a string why. | Map<String, Object> approval() |
NotFoundException(…) | ApiException | 404 | |
ConflictException(…) | ApiException | 409 | |
UnsupportedException(…) | ApiException | 501 | |
StreamException(String m, String stdout, String stderr, String daemonMessage) | Kosa8Exception | Error 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) | Kosa8Exception | Invalid name, no checkpoint, store write failure, failed restore inside a rewind. | |
TimeoutException(String m, String method, String path) | Kosa8Exception | A 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.
Quickstart · Developer guide · Errors and troubleshooting · Native HTTP API, which every method here wraps.