GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — GitHub - xmor/ara: ARA — Agent Runtime Architecture: Java 21 framework for building autonomous AI agents and multi-agent systems. LLM integration, tool calling, deterministic I/O contracts, RAG and human-in-the-loop. On Maven Central (io.github.xmor).

BranchesTags
Open more actions menu

Latest commit

 

History

235 Commits
235 Commits

Folders and files

NameName
Last commit message
Last commit date
.github
.github
 
 
ara-adapters
ara-adapters
 
 
ara-core
ara-core
 
 
ara-examples
ara-examples
 
 
ara-runtime
ara-runtime
 
 
docs
docs
 
 
.gitignore
.gitignore
 
 
AGENTS.md
AGENTS.md
 
 
LICENSE
LICENSE
 
 
README.md
README.md
 
 
pom.xml
pom.xml
 
 

Repository files navigation

ARA — Agent Runtime Architecture

ARA — Agent Runtime Architecture for Java

Build autonomous AI agents and multi-agent systems in plain Java 21. LLM integration, tool calling, deterministic I/O contracts, RAG and human-in-the-loop — no annotations, no reflection, no Spring, no Kotlin.

Maven Central Java License Last commit Stars

Quick start · Documentation · Examples · Website · Discussions


ARA is a Java 21 agent runtime: a place where agents actually run, with sessions, memory, execution strategies, cost budgets, approval gates and a full execution trace — not a thin wrapper around a chat completion endpoint.

It works with OpenAI, Anthropic, Ollama, Mistral, LM Studio, Groq and any OpenAI-compatible endpoint, and it is published on Maven Central as io.github.xmor:ara-runtime.

Your code ──▶ AraRuntime ──▶ Agent (strategy + contract + session)
                                │
                     ┌──────────┼───────────┐
                     ▼          ▼           ▼
                 LlmClient   Tools      Retriever
             OpenAI/Claude/  (yours,   (in-memory
             Ollama/Mistral  parallel   or Qdrant)
                             on vthreads)

Quick start — 60 seconds, no API key

1. Add the dependency (check the badge above for the latest version):

<dependency>
    <groupId>io.github.xmor</groupId>
    <artifactId>ara-runtime</artifactId>
    <version>1.0.3</version>
</dependency>

The version shown above is the latest published release and is recommended for production use. During development and testing, you may also use the next -SNAPSHOT version, provided that the corresponding snapshot repository is configured.

2. Run an agent offline. ScriptedLlmClient replays canned responses, so the first run needs no key, no network and no local model:

import io.ara.runtime.AraRuntime;
import io.ara.runtime.stubs.ScriptedLlmClient;

try (var runtime = AraRuntime.builder()
        .llmClient(ScriptedLlmClient.script()
                .thenFinalAnswer("Virtual threads are lightweight JVM threads.")
                .build())
        .build()) {

    System.out.println(runtime.askText("Explain virtual threads"));
}

Virtual threads are lightweight JVM threads.

That is the whole loop, runnable as basics/MinimalAgentExample. What just happened: runtime.askText(...) runs the prompt on a shared default agent and returns the answer string directly — the runtime auto-started on first use, and close() shuts it down. The default react strategy called the LLM, ended at a final answer, and returned it.

When the agent needs a role, a system prompt, tools or a specific model, create one explicitly and call agent.execute(...): you get the full AgentResponse — iteration count, token usage, cost and step trace — instead of just the string. AgentConfig.of("assistant", "...") is the one-line form; reach for AgentConfig.defaults() as soon as a third field is involved. See basics/AraSimpleExample for that fuller shape.

3. Swap in a real model — one line changes, everything else stays:

LlmClient gpt4o = AraLlmClientFactory.openAi()
        .apiKey(System.getenv("OPENAI_API_KEY"))
        .modelName("gpt-4o")
        .build();

// or fully local, no API key:
LlmClient llama = AraLlmClientFactory.ollama()
        .model(OllamaLlmClient.Models.LLAMA_3_2)
        .build();

Requires Java 21+ and Maven 3.9+. Add ara-adapters for real providers → Providers guide. Models can also be declared in ara.yml and loaded with AraLlmClientFactory.fromConfig → Models in a configuration file.

Build from source
git clone https://github.com/xmor/ara.git && cd ara
mvn clean install -DskipTests

One step further — three agents at once

crew/CodeReviewCrewExample is the same runtime doing multi-agent work. Three specialist reviewers — security, performance, style — run concurrently on virtual threads, then a lead agent merges their findings into a prioritised report. The fan-out is a pipeline step; there are no threads to wire up:

AgentPipeline pipeline = AgentPipeline.builder()
        .parallel("review", List.of(security, performance, style),
                  runtime.executor(), AgentChain.MergeStrategy.joining("\n\n"))
        .step("synthesize", lead)
        .build();

AraAgent crew = PipelineAgents.of(AgentId.of("code-review-crew"), config, pipeline);

Run it offline with main(): the console shows the three reviewers starting together on different threads and finishing in an order decided by their work, then the lead's report and the crew's token/cost summary. Pass live to run the same crew against a real model.


Why ARA

  • Plain Java, no magic. Pure interfaces — zero annotations, zero reflection, no Kotlin runtime, no Spring. The call stack you debug is the call stack you wrote.
  • Deterministic I/O contracts. AgentContract validates, sanitises and transforms in plain Java before and after every call, spending zero tokens.
  • Java 21 by design. Virtual threads are the concurrency model, not an option: when the LLM asks for several tools at once they are dispatched in parallel automatically, with no executor to wire up.
  • A runtime, not a toolkit. Execution strategies, FSM pipelines, session isolation, human-in-the-loop and cost budgets come in the box rather than assembled from parts.
  • Built on LangChain4j, not against it. Provider integration is inherited through ara-adapters, so you get LangChain4j's provider coverage plus the runtime on top.

Where ARA fits in the JVM ecosystem

If you use… ARA's relationship
LangChain4j ARA builds on it. ara-adapters wraps LangChain4j clients and adds the runtime layer above: sessions, strategies, contracts, HITL.
A Spring-based AI stack ARA has no DI container and no Spring dependency. It drops into a Spring app as an ordinary library, or into a plain main() with none.
Python agent frameworks ARA keeps agentic workloads on the JVM stack you already deploy, monitor and secure — no Python service to operate alongside it.
Raw provider SDKs Everything above the HTTP call — retries, failover, circuit breaking, tracing, output validation, approval gates — is already written.

Capabilities that rarely come as one piece

  • Human-in-the-loop as a runtime primitive — an ApprovalGate wired into the tool dispatch chain; ApprovalDecision is a sealed interface, so approve / reject / modify is exhaustive at compile time. → HITL guide
  • A per-caller tool catalog — a tool that declares requiredScopes() is hidden from the model's catalog for any task that doesn't carry the scopes, before the LLM ever reads it; scopes come from the task's execution context, attenuated through delegation like any other call. → Tools guide
  • Conversation and self-correction inside the loop — "respact" asks a clarifying question mid-task and resumes on the same session; "reflact" recovers from a failed tool call without discarding what the run already accomplished.
  • Private per-agent data — AgentInstanceContext holds API keys or tenant ids that both prompt shaping and tool execution can read, and that never reach the LLM.
  • Per-agent cost accounting — unit prices and a costBudget cap are part of the LLM profile, not an afterthought.
  • Multimodal input — attach a PDF or an image and the model reads it natively; a provider that can't handle the type fails loudly before the request goes out.
  • Tools that return images — a tool can hand the model an image (a crop, a rendered page) and the model looks at it; the one it cites in its answer comes back to the caller as an artifact. See Media returned by tools.
  • A full execution trace, always — AgentResponse.steps() records the reasoning and tool trace on every run, including the partial trace when a run fails.
  • LLM failover and circuit breaking — an ordered model chain with a passive breaker per candidate, so an outage stops costing a timeout per request.

What you can build

  • Single agents on any LLM (OpenAI, Anthropic, Ollama, Mistral, LM Studio, Groq, …)
  • Deterministic I/O contracts: sanitize input, validate output, strip markdown fences — zero tokens consumed
  • Multi-agent pipelines with conditional routing and FSM-style state machines
  • Parallel specialist crews: a fan-out step runs several agents at once on virtual threads and merges their answers with a pluggable strategy
  • Classify-and-act triage: one classification decides the single worker that handles the task, escalating from keyword rules to a model to a human as confidence drops — the whole dispatch table loadable as a JSON document
  • Tool calling from LLM responses, including parallel dispatch on virtual threads
  • Conversational agents that ask clarifying questions mid-task ("respact") and self-correcting ones that recover from failed tool calls without restarting ("reflact")
  • RAG as a strategy decorator — retrieval before every LLM call, no tool configuration needed
  • Fully offline testing with ScriptedLlmClient and AssociativeLlmClient

Core concepts in 4 snippets

Tool calling — implement AraTool, opt in per agent
class WeatherTool implements AraTool {
    @Override public String toolId()      { return "get_weather"; }
    @Override public String description() { return "Returns current weather for a city."; }
    @Override public String argumentSchema() {
        return """
               {"type":"object","properties":{"city":{"type":"string"}},"required":["city"]}
               """;
    }
    @Override public ToolResult execute(String argumentJson) {
        return ToolResult.success(toolId(), "Milan: Sunny, 22°C");
    }
}

AgentConfig config = AgentConfig.defaults()
        .agentType("travel-assistant")
        .plannerStrategy("react")
        .enabledTools(List.of("get_weather"))
        .build();

When the LLM requests several tools in one response, ARA dispatches them concurrently on virtual threads — no configuration. → Tool calling in depth

Deterministic I/O contracts — validate and transform without spending tokens
AgentContract contract = AgentContract.builder()
        .addInputProcessor(InputSanitizer.instance())      // blocks prompt-injection patterns
        .addInputProcessor(ContentTruncator.to(4000))
        .addPromptShaper(PromptTemplate.withDefaults(Map.of("date", LocalDate.now().toString())))
        .outputSchema(JsonSchemaValidator.forOutput(SCHEMA))
        .addOutputProcessor(MarkdownFenceStripper.instance())
        .build();

AraAgent agent = runtime.createAgent(config, contract);

Validators, extractors, PII redaction and media limits ship built in. → Contracts & processors

Multi-agent pipeline — an FSM over agents
AgentPipeline pipeline = AgentPipeline.fsmBuilder()
        .state("draft",  draftAgent)
        .state("review", reviewAgent)
        .state("revise", reviseAgent)
        .state("done",   doneAgent)
        .initial("draft")
        .terminal("done")
        .transition("draft",  "review")
        .transition("review", execution -> execution.lastOutput().contains("APPROVED") ? "done" : "revise")
        .transition("revise", "review")
        .maxSteps(12)
        .build();

PipelineAgents.of(pipeline) hosts it inside a real agent, so it gains session isolation, cancellation and telemetry — and can be nested inside another pipeline. → Pipeline README

Classify-and-act — triage with confidence-driven escalation
IntentRouter router = IntentRouter.onField("intent")
        .route("BILLING", "billing")
        .route("TECH",    "tech")
        .confidenceField("confidence")
        .escalateBelow(0.7, "human")       // low confidence → escalate, not guess
        .orElse("fallback");

AgentPipeline triage = AgentPipeline.builder()
        .classify("classify", classifierAgent, router)
        .worker("billing", billingAgent)
        .worker("tech",    techAgent)
        .worker("human",   humanReviewAgent)
        .worker("fallback", fallbackAgent)
        .build();

Three interchangeable classifiers fill the same slot — keyword rules (zero tokens), a prompted model, or a human behind an ApprovalGate — and the whole dispatch table can be loaded from JSON. → Classify-and-act


Execution strategies

Strategy plannerStrategy What it does
ReactStrategy "react" Reasoning–Action loop: Think → Act → Observe. The default
ReSpActStrategy "respact" ReAct + a speak action: converse with the user mid-task without closing it
ReflActStrategy "reflact" ReAct + in-loop self-correction on tool failures or stalled reasoning
PlanExecuteStrategy "plan_execute" Generate a structured (JSON) plan, execute each step in its own context, and have each step close with an outcome and notes for the next
ReflexionStrategy "reflexion" Generate → critique → revise, restarting the whole episode
RetrievalAugmentedStrategy "rag+<name>" Inject retrieved context before every LLM call

All strategies support cooperative cancellation and record a full execution trace, including on failure paths. → Strategy README


Modules

Module What's inside
ara-core Pure interfaces and domain model: AraAgent, LlmClient, LlmException, MemoryManager, ToolRegistry, AgentContract, ExecutionStrategy, …
ara-runtime AraRuntime, the execution strategies, ContractEnforcer, AgentPipeline, the classify-and-act building blocks, the offline LLM stubs and the built-in processors
ara-adapters LangChain4j-backed LlmClient adapters for OpenAI, Anthropic, Ollama, Mistral and ChatJimmy. No Kotlin, no OkHttp, no Spring
ara-examples Runnable examples for offline (stub) and live (real LLM) scenarios

ara-gateway — an optional HTTP layer (Javalin/Jetty) for HTTP-side HITL approvals — ships separately and is not part of this build.


Runnable examples

Everything below lives in ara-examples and runs with main().

Class LLM What it shows
basics/MinimalAgentExample stub The smallest program: runtime + askText(...) — two lines
basics/AraSimpleExample stub End-to-end: ReAct loop, tool call, interceptor, agent reuse
basics/SimpleStreamingExample stub / live The smallest streaming agent, tokens printed as they arrive
basics/StreamingWithToolExample stub / live Token streaming through a ReAct loop that calls a tool
basics/InterceptorEventsExample stub Every AgentInterceptor event in order, around one run
pipeline/ClassifyAndActExample none Classify-and-act at its smallest — no model, no API key
pipeline/TicketTriageCascadeExample stub The three-tier cascade: rules → model → human
contract/StructuredContractExample stub An agent's input and output contract: full JSON Schema on both sides with every violation and its path, a refusal as data (AgentResponse.violation(), Phase.callerFault()), a custom rule with structured issues, and outputRepairAttempts sending a rejected answer back — the example checks its own claims
crew/CodeReviewCrewExample stub / live Three specialist reviewers fan out on virtual threads; a lead agent merges their findings
hitl/HumanInTheLoopExample stub A tool call parked on an ApprovalGate until an operator decides
memory/MemoryAgentExample stub Token-budgeted working memory: summarise, offload, and recall
rag/RagAgentExample stub rag+react over an InMemoryDocumentStore, plus delegation
failover/FailoverExample stub Failover and circuit breaking across LLM and embedding endpoints
multimodal/MultimodalInputExample live A PDF to Mistral and an image to Ollama, one provider-agnostic method
multimodal/ToolReturnsImageExample stub / live A crop_region tool returns the crop of a scanned document; the model looks at it, cites it, and it comes back in AgentResponse.artifacts() — the example checks its own claims
spec/AgentFromFileExample stub An agent defined in a JSON file: load it, check the names it refers to, run it, write it back
spec/ImportAgentFromJsonExample stub A fully described agent (fallback model, prices, spend cap, strategy, tools, memory) imported from a JSON file on the classpath, then run
events/RunEventsExample stub A listener on the task receives every step as it happens, including a delegated agent's, printed as a tree
background/BackgroundDelegationExample stub / live delegate_task with "wait": false: two slow labs run in the background while the coordinator reads notes; each result is put in front of the model by itself, and a run that answers with a task still open waits for it — with the clock on every line, and the example checks its own claims
scheduler/AgentSchedulerExample none Interval and cron schedules, with pause / resume / trigger
planner/PlannerExample stub / live plan_execute on a small coding task: a JSON plan with dependencies, a person who approves (or changes, or rejects) the plan through the HITL gate, notes that carry a choice from one step to the next, and a failed step that replans — in an in-memory workspace
web/StreamingChatWebExample stub / live A chat page on a JDK HttpServer, streaming over SSE

Documentation — read it in this order

The guides form a path, not a reference dump. Start at the top and stop when your use case is covered.

Step Guide What you'll be able to do
1 Quick start + Runnable examples Run your first agent offline, then read the example closest to your goal
2 Tool calling Give an agent a tool, with parallel dispatch on virtual threads
3 Contracts & processors Validate, sanitise and transform I/O without spending tokens; PromptShaper, multimodal input
4 Configuration reference Every AgentConfig and LlmProfile field: sessions, concurrency, cancellation, instance context, scheduling, and defining an agent in a file
5 RAG & human-in-the-loop Knowledge bases (in-memory or Qdrant), retrieval as a strategy vs. a tool, approval gates and notifiers
6 Providers & resilience Real endpoints (OpenAI / Anthropic / Ollama / Mistral / compatible), failover, circuit breaker, I/O logging, OpenTelemetry
7 Advanced usage Custom strategies and extension points
— Coding guidelines What a PR is expected to look like

FAQ

Do I need Spring, or any DI container?

No. ARA is a plain library with a builder API. It runs in a bare main(), and drops into a Spring or Quarkus application as an ordinary dependency.

Can I run it without any API key?

Yes, two ways. ScriptedLlmClient and AssociativeLlmClient replay scripted responses for tests and demos, and the Ollama adapter talks to a local model with no key at all.

Which providers are supported?

OpenAI, Anthropic, Ollama, Mistral and ChatJimmy have first-class adapters, and the OpenAI client points at any OpenAI-compatible endpoint — LM Studio, Groq, Together AI, a corporate gateway. Provider coverage is inherited from LangChain4j.

Why Java 21 and not 17?

Virtual threads. Parallel tool dispatch, session concurrency and the cheap parking of a thread waiting on a human approval are all built on them.

How is this different from calling a chat API in a loop?

The loop is the easy part. Session isolation, cooperative cancellation, output schema validation, failover with circuit breaking, cost budgets, approval gates and a complete execution trace are the parts you would otherwise write, and they are what ARA is.


Contributing

Issues, discussions and PRs are welcome — including README and documentation fixes, which are the easiest first contribution.

Code style is enforced through docs/CODING-GUIDELINES.md: simplicity first, no premature abstractions, short single-responsibility functions, honest naming. Read it before opening a PR.

ARA is developed with heavy AI assistance under human architectural review.

If ARA is useful to you, a ⭐ helps other Java developers find it.


License

Apache 2.0 — see LICENSE.

Keywords — Java AI agent framework · LLM orchestration on the JVM · multi-agent systems in Java · ReAct agents · tool calling · RAG · human-in-the-loop · OpenAI · Anthropic Claude · Ollama · Mistral · Java 21 virtual threads

About

ARA — Agent Runtime Architecture: Java 21 framework for building autonomous AI agents and multi-agent systems. LLM integration, tool calling, deterministic I/O contracts, RAG and human-in-the-loop. On Maven Central (io.github.xmor).

ara.open-solutions.it

Topics

agent-frameworkaiai-agentautonomous-agentshuman-in-the-loopjavajava21langchainlangchain4jllmllm-agentsmaven-centralmulti-agent-systemopenairagreact-patterntool-callingvirtual-threads

Resources

Readme
Apache-2.0 license
Activity

Stars

6 stars

Watchers

0 watching

Forks

1 fork
Report repository

Releases

Packages

Contributors

Languages

You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.