Jv
Java SDK
v1.2.0 · MIT License · Java 21+ · Bouncy Castle
Java 21 records and sealed interfaces throughout. Ed25519 via Bouncy Castle, async HTTP via Java 11 HttpClient, Spring AI bridge included.
Installation
Gradle
dependencies {
implementation 'io.aroha:aroha-sdk:1.0.0'
}Maven
<dependency> <groupId>io.aroha</groupId> <artifactId>aroha-sdk</artifactId> <version>1.0.0</version> </dependency>
Java 21+ required. The SDK uses virtual threads (
Thread.ofVirtual()) for non-blocking I/O without a reactive framework dependency.ArohaServer
Minimal agent
import io.aroha.transport.ArohaServer;
import io.aroha.transport.ArohaServerOptions;
import io.aroha.messages.ArohaEnvelope;
import io.aroha.identity.DidUtils;
import java.security.KeyPair;
// Generate keypair and DID (persist in production)
KeyPair keyPair = DidUtils.generateKeyPair();
String myDID = DidUtils.generateDid(keyPair.getPublic());
var server = ArohaServer.builder()
.agentDID(myDID)
.port(3000)
.devMode(true) // skip crypto in development
.onMessage((envelope, respond, stream) -> {
if ("ArohaRequest".equals(envelope.type())) {
var body = envelope.body();
var capability = (String) body.get("capability");
var params = (Map<String, Object>) body.get("params");
if ("greet".equals(capability)) {
respond.accept(ArohaResponse.of(
capability,
Map.of("message", "Hello, " + params.get("name") + "!")
));
}
}
})
.resolvePublicKey(did -> Optional.empty()) // return the sender's public key bytes
.build();
server.start();
System.out.println("Agent " + myDID + " listening on :3000");Production server
import io.aroha.transport.ArohaServer;
import io.aroha.credentials.RbacMiddleware;
var server = ArohaServer.builder()
.agentDID(myDID)
.privateKey(keyPair.getPrivate())
.port(3000)
.clockToleranceMs(5000)
.maxBodyBytes(1_048_576)
.minClientVersion("1.0")
.resolvePublicKey(did -> registry.lookupPublicKey(did))
.middleware(List.of(
RbacMiddleware.ofTrustLevel(2)
))
.onMessage(this::handleMessage)
.build();
server.start();ArohaClient
import io.aroha.transport.ArohaClient;
import io.aroha.messages.ArohaEnvelopeBuilder;
var client = new ArohaClient();
var envelope = ArohaEnvelopeBuilder.builder()
.type("ArohaRequest")
.from(myDID)
.to("did:aroha:travel-agent")
.body(Map.of(
"capability", "search-flights",
"params", Map.of("from", "JFK", "to", "LHR", "date", "2026-08-01")
))
.correlationId(ArohaEnvelopeBuilder.newCorrelationId())
.privateKey(keyPair.getPrivate())
.build();
var response = client.send("http://travel-agent.example.com", envelope);
response.ifPresent(resp -> {
if ("ArohaResponse".equals(resp.type())) {
System.out.println(resp.body().get("result"));
}
});Identity & DIDs
import io.aroha.identity.DidUtils;
import java.security.KeyPair;
// Self-sovereign DID
KeyPair keyPair = DidUtils.generateKeyPair();
String did = DidUtils.generateDid(keyPair.getPublic());
// → "did:aroha:base58encodedPublicKey"
// Domain-anchored DID
String webDid = DidUtils.buildWebDid("myco.ai", "agents", "travel");
// → "did:aroha-web:myco.ai:agents:travel"
// Resolve a DID to its document
DidDocument doc = DidUtils.resolve("did:aroha:someagent");
byte[] publicKeyBytes = doc.verificationMethods().get(0).publicKeyBytes();Envelopes
import io.aroha.messages.ArohaEnvelopeBuilder;
import io.aroha.messages.ArohaEnvelope;
import io.aroha.messages.ValidationResult;
// Build — all fields set automatically (id, created, expires, nonce, proof)
ArohaEnvelope env = ArohaEnvelopeBuilder.builder()
.type("ArohaReserve")
.from(orchestratorDID)
.to(providerDID)
.body(Map.of(
"capability", "book-flight",
"params", Map.of("from", "JFK", "to", "LHR"),
"budgetUsd", 500
))
.correlationId(ArohaEnvelopeBuilder.newCorrelationId())
.privateKey(privateKey)
.ttlSeconds(300)
.build();
// Validate an inbound envelope
ValidationResult result = ArohaEnvelopeValidator.validate(
env, senderPublicKey, myDID, nonceRegistry,
ValidationOptions.withClockToleranceMs(5000)
);
if (!result.valid()) {
throw new ArohaException(result.reason());
}Spending Mandates
import io.aroha.mandate.ArohaMandateCredential;
import io.aroha.mandate.ArohaMandateCredential.*;
// Define constraints
var constraints = new SpendingConstraints.Builder()
.spendLimitUsd(500.0)
.allowedCapabilities(List.of("search-flights", "reserve-flight"))
.expiresAt(Instant.now().plus(Duration.ofHours(1)))
.build();
// User issues a mandate to the orchestrator
var mandate = ArohaMandateCredential.issueIntent(
userDID,
orchestratorDID,
constraints,
userPrivateKey
);
// Orchestrator attenuates to a sub-agent
var subMandate = ArohaMandateCredential.attenuate(
mandate,
flightAgentDID,
new SpendingConstraints.Builder()
.spendLimitUsd(300.0) // ≤ 500
.allowedCapabilities(List.of("reserve-flight"))
.build(),
orchestratorPrivateKey
);
// Verify on the receiving agent
var verifyResult = ArohaMandateCredential.verify(subMandate, flightAgentDID);
System.out.println(verifyResult.valid()); // true
System.out.println(verifyResult.reason()); // "ok"Saga Orchestration
import io.aroha.orchestrator.SagaEngine;
import io.aroha.orchestrator.SagaStep;
import io.aroha.orchestrator.SagaResult;
var engine = SagaEngine.builder()
.client(client)
.orchestratorDID(myDID)
.privateKey(privateKey)
.build();
var result = engine.run(List.of(
SagaStep.builder()
.agentDID("did:aroha:flight-agent")
.endpoint("http://flights.example.com")
.capability("reserve-flight")
.params(Map.of("from", "JFK", "to", "LHR"))
.budgetUsd(300)
.build(),
SagaStep.builder()
.agentDID("did:aroha:hotel-agent")
.endpoint("http://hotels.example.com")
.capability("reserve-hotel")
.params(Map.of("city", "London", "nights", 3))
.budgetUsd(200)
.build()
));
System.out.println(result.status()); // SagaStatus.COMMITTED or COMPENSATED
result.steps().forEach(s ->
System.out.println(s.commitToken()) // use for billing
);Spring AI Bridge
import io.aroha.bridges.SpringAiBridge;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.tool.FunctionToolCallback;
// Wrap an Aroha capability as a Spring AI tool
FunctionToolCallback flightTool = SpringAiBridge.toSpringAiTool(
"search-flights",
SpringAiBridge.ArohaToolOptions.builder()
.endpoint("http://travel-agent.example.com")
.agentDID("did:aroha:travel-agent")
.callerDID(myDID)
.callerPrivateKey(privateKey)
.description("Search for available flights between airports")
.build()
);
// Use with Spring AI ChatClient
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultTools(flightTool)
.build();
String response = chatClient.prompt()
.user("Find flights from JFK to London on August 1st")
.call()
.content();
System.out.println(response);All Packages
io.aroha.cryptoEd25519 via Bouncy Castle, AES-256-GCM
io.aroha.identityDID generation, did:aroha-web:, resolution
io.aroha.messagesArohaEnvelope (Java 21 record), builder, validator, NonceRegistry
io.aroha.transportArohaServer (virtual threads), ArohaClient (HttpClient)
io.aroha.orchestratorSagaEngine, SagaStep, AgentSelector, CSNHandler
io.aroha.mandateArohaMandateCredential: issueIntent, attenuate, verify
io.aroha.reputationReputationEngine (Bayesian Beta)
io.aroha.bridgesSpringAiBridge — Spring AI FunctionToolCallback adapter