Quickstart: Java
The same pack as the TypeScript quickstart, in Java: two tools, an agent that calls them, a guardrail and a flow. Its tools are records and lambdas, built with Maven.
Before you start, set up what the Install page describes:
Docker, Node 22.12 or later, and access to the runtime image. The image is
in private preview: request access at contact@kindgi.com, then log in once
with kindgi auth registry. You also need a JDK 17 or later, with
JAVA_HOME set to it. Maven comes with the pack.
1. Create the pack
Section titled “1. Create the pack”npx --yes @kindgi/cli@0.1 init my-pack --template=javacd my-pack./mvnw -q test # the template's tests: the tools, called directlyWithout Node, run init from PyPI instead:
uvx --from "kindgi-cli>=0.1,<0.2" kindgi init my-pack --template=java.
The pack is a Maven project. Its config is kindgi.config.json; its tools,
guardrails, agents and flows are classes in four packages:
my-pack/├── kindgi.config.json # "language": "java", the pack's id and version├── pom.xml # com.kindgi:kindgi-pack, Java 17├── mvnw, .mvn/ # the Maven wrapper├── kindgiw, kindgiw.cmd # the Kindgi CLI wrapper (the version kindgi.config.json pins)├── src/main/java/mypack/│ ├── tools/Echo.java, tools/Greet.java # Tool.define(...)│ ├── guardrails/ResponseNotEmpty.java # Guardrail.define(...)│ ├── agents/EchoAgent.java # Agent.define(...)│ └── flows/EchoFlow.java # Flow.define(...)└── src/test/java/mypack/ToolsTest.javaThe package comes from the pack's id (my-pack → mypack). A tool, a
guardrail, an agent or a flow is a public static final field of a class
in its package.
The pack pins the Kindgi CLI it runs with, "cli" in kindgi.config.json,
as Maven's wrapper pins Maven. Run the CLI as ./kindgiw (kindgiw.cmd on
Windows): it uses Node's npx when Node is installed, else uvx (no Node
needed). kindgi upgrade moves the pin.
2. Run it
Section titled “2. Run it”./kindgiw devkindgi dev checks the JDK (JAVA_HOME's) and Maven (the pack's
mvnw), and starts the Kindgi runtime in Docker. Maven compiles the pack,
the Java indexer reads it, and its tools and checks run in a pack service.
It recompiles on every save. It writes the API's URL and a token to
.kindgirc.json, so the commands below find the runtime by themselves.
Leave it running.
3. Run the agent and the flow
Section titled “3. Run the agent and the flow”In a second terminal, in my-pack:
./kindgiw runs start --agent=my-pack.echo-agent --input='{"userMessage":"Ada"}'./kindgiw runs start --flow=my-pack.echo-flow --input='{"message":"Ada"}' "status": "completed",…⚠ dev-echo answered, and it isn't a real model: it only repeats what it's given. …… "echo": "Ada",Without a model, the agent's answer comes from dev-echo, a stand-in that
calls the agent's first tool with {"message": <your userMessage>} and
replies with what it returned, after a first line that says it isn't a real
model. The flow runs the echo tool on its input and returns what the tool
returned. Either way, your Java tool ran: the runtime called it over HTTP in
the pack service.
4. Look at the code
Section titled “4. Look at the code”A tool is a field: its input and output are records, and their schemas come from the records (a Jakarta Validation constraint becomes its schema keyword). The pack service checks both on every call:
package mypack.tools;
import com.kindgi.pack.Tool;import jakarta.validation.constraints.PositiveOrZero;import jakarta.validation.constraints.Size;import java.time.Instant;
/** my-pack.echo — echoes the message back with a UTC timestamp and its length. */public final class Echo { public record Input(@Size(min = 1, max = 500) String message) {}
public record Output(String echo, Instant echoedAt, @PositiveOrZero int characterCount) {}
public static final Tool<Input, Output> TOOL = Tool.define("my-pack.echo") .description("Echoes the caller-provided message with a UTC timestamp and character count.") .input(Input.class) .output(Output.class) .mutating(false) .handler((input, ctx) -> new Output(input.message(), Instant.now(), input.message().length()));
private Echo() {}}mutating(false) says the tool only reads. A dry run may call it, and approval
gates skip it. Left unset, a tool counts as one that writes.
An agent is data, and it refers to the tools and the guardrail themselves:
// in src/main/java/mypack/agents/EchoAgent.javapublic static final Agent AGENT = Agent.define("my-pack.echo-agent") .version("0.1.0") .name("Echo Agent") .instructions("For each user message: if the user sends a name, greet them with the greet tool. " + "Otherwise echo their message with the echo tool. Quote the tool result verbatim.") .capability(Map.of("needs", List.of(Map.of("feature", "tool-use")))) .tool(Echo.TOOL) .tool(Greet.TOOL) .guardrail(ResponseNotEmpty.GUARDRAIL) .build();Save a file with a mistake in it, and kindgi dev says where, keeping the
last good code running:
✗ refresh failed [bundle-failed] The pack's code did not build: src/main/java/mypack/tools/Greet.java:20:36: cannot find symbolFix it, and the pack reloads:
✓ loaded 5 primitives (2 tools, 1 guardrails, 1 agents, 1 flows) in 640ms5. Connect a real model
Section titled “5. Connect a real model”Store an Anthropic key as a secret (you're prompted for it; it isn't echoed), then register the provider:
./kindgiw secrets set ANTHROPIC_API_KEY --env=local --scope=tenant./kindgiw providers register --preset=anthropicIt takes over from dev-echo at the next turn. OpenAI (OPENAI_API_KEY,
--preset=openai), Gemini (GEMINI_API_KEY, --preset=gemini-api), Groq
(GROQ_API_KEY, --preset=groq) and OpenRouter (OPENROUTER_API_KEY,
--preset=openrouter) work the same way; kindgi providers presets lists
them, Gemini on Vertex AI too. Any OpenAI-compatible endpoint registers from
a short spec file.
The registration is in this project's dev database. To have kindgi dev
register the model on every boot, in each worktree and after --reset,
declare it in kindgi.config.json ("providers": [{"preset": "anthropic"}]):
Declare them in your pack's config.
- Add Kindgi to an existing Java app: your app's own classes as tools.
- Call Kindgi from a Java app: start runs and follow them with the Java client.
- Guides: one task at a time.
- Concepts: packs, runs and the journal, security.
- kindgi-pack's README: schemas from records, guardrails, flows, the call context and cancellation.