Independent Java Consumer¶
Use the published com.axisvela:praxo-core:0.9.0 artifact from Maven Central in
your own application. Java 21 or newer and Maven 3.9 or newer are required. No
PRAXO-CORE source checkout, API key, model service, or AxisVela product is
needed. Installation needs Maven Central access or a populated local repository.
Choose the Boundary¶
This fits an embedded model/tool loop with explicit tool scope and policy. Your application still owns model access, authentic identity, persistent approvals, external effects, and isolation. It is not a hosted agent platform, durable graph engine, or sandbox service. See when not to use Core.
Create the Application¶
Declare the exact dependency in pom.xml:
<dependency>
<groupId>com.axisvela</groupId>
<artifactId>praxo-core</artifactId>
<version>0.9.0</version>
</dependency>
Do not use a system scope, a local JAR path, or a substitute build of the
repository source. The tests verify the SDK version and that Version loaded
from the published praxo-core-0.9.0.jar.
Application Source¶
Use the following as src/main/java/starter/App.java, rendered directly from the
tested source.
package starter;
import com.axisvela.praxo.PraxoException;
import com.axisvela.praxo.agent.Agent;
import com.axisvela.praxo.agent.AgentOptions;
import com.axisvela.praxo.agent.Session;
import com.axisvela.praxo.model.Message;
import com.axisvela.praxo.model.Model;
import com.axisvela.praxo.model.Request;
import com.axisvela.praxo.model.Response;
import com.axisvela.praxo.model.ToolCall;
import com.axisvela.praxo.model.ToolSpec;
import com.axisvela.praxo.policy.Action;
import com.axisvela.praxo.policy.Decision;
import com.axisvela.praxo.tool.Registry;
import com.axisvela.praxo.tool.Tool;
import java.io.PrintStream;
import java.util.List;
import java.util.Map;
public final class App {
private App() {
}
static final class FixtureModel implements Model {
private final String target;
FixtureModel(String target) {
this.target = target;
}
@Override
public Response generate(Request request) {
Message last = request.messages().get(request.messages().size() - 1);
if (Message.TOOL.equals(last.role())) {
return Response.of(Message.assistant("recorded", List.of()));
}
ToolCall call = new ToolCall("record-1", "record", Map.of("target", target));
return Response.of(Message.assistant("", List.of(call)));
}
}
static final class RecordTool implements Tool {
private int calls;
@Override
public String name() {
return "record";
}
@Override
public String description() {
return "Record a target in an in-memory counter.";
}
@Override
public Object invoke(Map<String, Object> args) {
calls += 1;
return "recorded";
}
public ToolSpec spec() {
return new ToolSpec(
"record",
description(),
Map.of(
"type", "object",
"required", List.of("target"),
"properties", Map.of("target", Map.of("type", "string"))),
"write");
}
int calls() {
return calls;
}
}
public record Built(Agent agent, RecordTool tool) {
}
static Built buildAgent(String target) {
Registry tools = new Registry();
RecordTool tool = new RecordTool();
tools.register(tool);
AgentOptions options = new AgentOptions(new FixtureModel(target));
options.tools = tools;
options.maxSteps = 3;
options.allowedTools = List.of("record");
options.policy = (Action action) -> {
if ("model.generate".equals(action.kind())) {
return Decision.allow("model generation");
}
if ("tool.invoke".equals(action.kind())) {
Object value = action.arguments() == null
? null
: action.arguments().get("target");
return "record".equals(action.toolName()) && "public".equals(value)
? Decision.allow("public target")
: Decision.deny("only the public target is permitted");
}
return Decision.deny("unsupported action");
};
return new Built(new Agent(options), tool);
}
public static void run(PrintStream out) throws Exception {
Built allowed = buildAgent("public");
var result = allowed.agent().run(
new Session("starter-public"), "Record the requested target.");
if (!"recorded".equals(result.output) || allowed.tool().calls != 1) {
throw new IllegalStateException(
"allow verification failed: output=" + result.output
+ " effects=" + allowed.tool().calls);
}
out.println("allow: recorded; effects=1");
Built denied = buildAgent("private");
try {
denied.agent().run(
new Session("starter-private"), "Record the requested target.");
} catch (PraxoException error) {
if (!"AGENT_POLICY_DENIED".equals(error.code)) {
throw error;
}
if (denied.tool().calls != 0) {
throw new IllegalStateException(
"denied call produced effects=" + denied.tool().calls);
}
out.println("deny: policy rejected; effects=0");
return;
}
throw new IllegalStateException("private target was not denied");
}
public static void main(String[] args) throws Exception {
run(System.out);
}
}
Acceptance Tests¶
Use the following as src/test/java/starter/AppTest.java, rendered from the
tested source.
package starter;
import com.axisvela.praxo.PraxoException;
import com.axisvela.praxo.Version;
import com.axisvela.praxo.agent.Session;
import org.junit.jupiter.api.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AppTest {
@Test
void allowedToolRunsOnce() throws Exception {
var built = App.buildAgent("public");
var result = built.agent().run(
new Session("test-public"), "Record the requested target.");
assertEquals("recorded", result.output);
assertEquals(1, built.tool().calls());
}
@Test
void deniedToolHasNoEffect() {
var built = App.buildAgent("private");
PraxoException error = assertThrows(PraxoException.class, () -> built.agent().run(
new Session("test-private"), "Record the requested target."));
assertEquals("AGENT_POLICY_DENIED", error.code);
assertEquals(0, built.tool().calls());
}
@Test
void starterOutputIsDeterministic() throws Exception {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
try (PrintStream out = new PrintStream(buffer, true, StandardCharsets.UTF_8)) {
App.run(out);
}
String expected = "allow: recorded; effects=1" + System.lineSeparator()
+ "deny: policy rejected; effects=0" + System.lineSeparator();
assertEquals(expected, buffer.toString(StandardCharsets.UTF_8));
}
@Test
void dependencyComesFromPublishedJar() throws Exception {
assertEquals("0.9.0", Version.SDK_VERSION);
URL location = Version.class.getProtectionDomain().getCodeSource().getLocation();
String filename = Path.of(location.toURI()).getFileName().toString();
assertEquals("praxo-core-0.9.0.jar", filename);
assertTrue(location.toURI().toString().contains("/com/axisvela/praxo-core/0.9.0/"));
}
}
Verify¶
mvn -B test
mvn -q -B exec:java
Expected program output:
allow: recorded; effects=1
deny: policy rejected; effects=0
The model is a deterministic fixture, not an LLM. allowedTools limits the tool
name; Policy also checks the target argument. A denied call raises
PraxoException with code AGENT_POLICY_DENIED and must not increment the
in-memory counter. An empty allowlist is not deny-all in the current API. Keep
these tests when replacing the fixture with a real model adapter.
Governed Acceptance¶
GovernedAcceptanceTest adds five negative checks:
mvn -B -Dtest=GovernedAcceptanceTest test
| Check | Required outcome |
|---|---|
| Missing mandatory policy | application guard rejects before a run; zero tool effects |
| Empty tool scope | application guard rejects; zero tool effects |
| Middleware changes final arguments | final operation is reauthorized; denial prevents the effect |
| Approval edit changes arguments | edited operation is reauthorized; denial prevents the effect |
| Policy service error | run fails closed; zero tool effects |
Use the tested source. The guard is application-level: Core keeps its ordinary nil-policy behavior. These checks do not prove authentic identity, durable approvals, effect reconciliation, or a general security profile.
These checks establish only the stated artifact and fixture behavior, not live model quality, authentic identity, isolation, durable effects, or production safety. Continue with the Java implementation guide and the security model.