Skip to content
Browse the documentation
SDKs

Java SDK

Record agent actions, push facts from systems of record, read verdicts and draft contracts. The runtime has zero dependencies (JDK HTTP client, its own JSON codec); Java 17+.

Last changed Markdown versionllms.txt

Record agent actions, push facts from systems of record, read verdicts and draft contracts. The runtime has zero dependencies (JDK HTTP client, its own JSON codec); Java 17+.

<dependency>
  <groupId>ai.commitlayer</groupId>
  <artifactId>commitlayer</artifactId>
  <version>0.1.0</version>
</dependency>
implementation("ai.commitlayer:commitlayer:0.1.0")   // Gradle
cd sdk/java && mvn install        # from this repository, into the local Maven repository
export COMMITLAYER_API_KEY=spk_...          # the agent runtime's key: a *recorder* key (records actions, nothing else)
export COMMITLAYER_EVIDENCE_KEY=spk_...     # the pipeline's key: an *evidence* key bound to the sources it pushes for
# COMMITLAYER_API_URL defaults to https://api.commitlayer.ai. One credential per side of the line: the key that
# records an action can never also supply the facts that verify it.

Every method returns the API's JSON answer as a Map<String,Object> (nested maps, lists, strings, Long, Double, booleans) rather than typed classes: the response shapes evolve with the API (see docs/API.md), and a map keeps an older SDK compatible with new fields.

Record an action where it happens

import ai.commitlayer.ActionOptions;
import ai.commitlayer.ActionRecorder;
import ai.commitlayer.Client;

Client cl = Client.builder().agentId("support-bot").build();   // key and url from the environment

Map<String, Object> refund(String orderId, double amount) {
    ActionRecorder rec = cl.action("refund_workflow", orderId, new ActionOptions()
            .params(Map.of("amount", amount))
            .subjectKind("order")
            .actionId("refund:" + orderId));   // retries update one claim instead of recording two
    return rec.run(() -> payments.refund(orderId, amount));   // the agent's own work, unchanged
}

The action is recorded before the function runs, so its request time precedes every effect. The function's return value is never changed and never consulted: verification reads the systems of record. If the API cannot be reached the function still runs and a warning is logged through System.Logger (failOpen(true), the default); call rec.failOpen(false) to rethrow instead and skip the function. rec.lastRecorded() holds the API's answer to the latest recording (action, verification, allowance), and rec.onRecorded(...) receives it as it arrives:

rec.onRecorded(r -> {
    Map<String, Object> verification = (Map<String, Object>) r.get("verification");
    Map<String, Object> summary = (Map<String, Object>) verification.get("summary");
    System.out.println(summary.get("headline"));
});

cl.recordAction(contractId, subjectId, opts) records without wrapping anything. An action that corrects an earlier one names it with .compensates("refund:order_10231").

Push facts, with a watermark

Use an evidence key here, bound to the sources it pushes for (Settings → API keys). The key your agent runtime records actions with is a recorder key and cannot push facts: the credential that records an action never also supplies the facts that verify it.

import ai.commitlayer.FactsOptions;

Client evidence = Client.builder().apiKey(System.getenv("COMMITLAYER_EVIDENCE_KEY")).build();  // the pipeline's own client
evidence.pushFacts(
        List.of(Map.of(
                "source", "crm",
                "subject_id", "order_10231",
                "fact_type", "note_added",
                "occurred_at", "2026-10-13T10:42:09Z")),
        new FactsOptions().watermarks(Map.of("crm", "2026-10-13T10:45:00Z")));  // everything up to here has been sent for crm

A verdict that is not yet a PASS settles only when its window has closed and every source it reads has a watermark past the end of the window. Declare one when your feed lags; a source that never declares one is taken as current. .links(...) on the same options records that two record ids are the same case.

Draft a contract from what was recorded

import ai.commitlayer.DraftOptions;

Map<String, Object> d = cl.draftContract("refund_order", new DraftOptions().lookbackDays(30));
String yaml = (String) d.get("yaml");       // expected effects, window, preconditions, limits, from the sample
System.out.println(((Map<?, ?>) d.get("stats")).get("effects"));
Map<String, Object> v = cl.validateContract(yaml);   // errors, and weakening warnings against the version it would replace
cl.saveContract(yaml);                                // the response carries the same warnings

Every value in a draft is what the sample showed, not what the policy says. Review it before saving.

Read a verdict

Map<String, Object> v = cl.getAction("refund:order_10231");
v.get("status");                                          // pending | settled
((Map<?, ?>) v.get("settlement")).get("waiting_on");      // sources still behind the window, if any
v.get("reversal");                                        // the later record that undid the action, if any
v.get("sources");                                         // trust level and watermark per source the contract read

cl.verify(new VerifyOptions().actionId("refund:order_10231")) verifies now; with .action(...), .facts(...) and .dryRun(true) an action and its facts are evaluated inline and nothing is stored.

Errors

An answer with status 400 or above throws CommitLayerException with status(), code(), message() and details() taken from the API's {error: {code, message, details}} body (for instance entitlement_limit with details.upgrade_to). Network failures throw java.io.UncheckedIOException.

Tests

The tests run offline: they inject a Transport (a functional interface, Response send(method, path, body)) and, for the default HTTP transport, a loopback HttpServer.

cd sdk/java && mvn test