ƒog ARA — il runtime per agenti in Java
Agent Runtime Architecture · Apache 2.0

Architettura agentica in puro Java. Nessuna magia.

ARA è un runtime Java 21 per agenti autonomi e sistemi multi-agente. Zero annotazioni. Zero reflection. Niente runtime Kotlin, niente Spring. Lo stack di chiamate che debugghi è lo stack di chiamate che hai scritto tu.

4 moduli 6 strategie di esecuzione 0 annotazioni 0 reflection 0 token per i contratti I/O
HelloAgent.java funziona offline — nessuna API key
try (AraRuntime runtime = AraRuntime.builder()
        .llmClient(ScriptedLlmClient.script()
                .thenFinalAnswer("Virtual threads are lightweight JVM threads.")
                .build())
        .build()) {

    AraAgent agent = runtime.createAgent(AgentConfig.defaults()
            .agentType("assistant")
            .systemPrompt("You are a concise technical assistant.")
            .build());

    AgentResponse response = agent.execute(AgentTask.of("Explain virtual threads"));

    System.out.println(response.content());  // deterministic, in CI, for free
}

Perché ARA

Java esegue già la tua logica di business.
Dovrebbe eseguire anche i tuoi agenti.

La maggior parte degli stack per agenti JVM sono toolkit di integrazione: forniscono un client e lasciano a te orchestrazione, determinismo e testing. ARA è l'altra metà — un runtime, costruito sopra i provider di LangChain4j invece che contro di essi.

Contratti I/O deterministici

AgentContract esegue una catena di processori prima e dopo ogni chiamata — sanifica l'input, rimuove i blocchi markdown, valida il JSON contro uno schema, redige i dati PII. Puro Java, zero token. Nessun altro framework JVM offre un equivalente.

Thread virtuali, non un'opzione

Quando il modello richiede cinque tool in un'unica risposta, ARA li dispaccia tutti in parallelo su thread virtuali Java 21. Nessun executor da configurare, nessun pool da ottimizzare, nessun flag da attivare.

Sei strategie di esecuzione

ReAct, ReSpAct (conversa a metà task), ReflAct (auto-correzione dentro il loop), PlanExecute, Reflexion, più un decoratore RAG — "rag+react". Cambi strategia modificando una stringa in AgentConfig.

Testabile senza un LLM

ScriptedLlmClient ripropone turni scriptati — chiamate ai tool incluse — così un intero flusso multi-agente diventa un semplice test JUnit. CI deterministica, nessuna API key, nessun costo.

Segreti che il modello non vede mai

AgentInstanceContext conserva API key e tenant id che la formazione del prompt e l'esecuzione dei tool possono leggere — mai nel testo del prompt, mai nello schema degli argomenti di un tool. Aggiornabile a runtime, senza ricreare l'agente.

Il tour del codice

Quattro cose che si costruiscono per prime

Ogni snippet qui sotto è copia-incolla dal README. Interfacce leggibili in un colpo solo, nessun template di partenza richiesto.

Runtime multi-provider
// One runtime, many providers — agents reference them by name.
AraRuntime runtime = AraRuntime.builder()
        .llmClient("fast",  AraLlmClientFactory.openAi().apiKey(KEY).modelName("gpt-4o-mini").build())
        .llmClient("smart", AraLlmClientFactory.openAi().apiKey(KEY).modelName("gpt-4o").build())
        .llmClient("local", AraLlmClientFactory.ollama().modelName("gpt-oss-20b").build())
        .build();

AgentConfig config = AgentConfig.defaults()
        .agentType("analyst")
        .primaryLlm(LlmProfile.of("smart"))   // zero credentials inside AgentConfig
        .build();

I provider arrivano da ara-adapters (OpenAI · Anthropic · Ollama · qualsiasi endpoint compatibile OpenAI), costruito su LangChain4j — niente Kotlin, niente OkHttp, niente Spring.

Strategie di esecuzione

Il loop dell'agente è una strategia, non una riscrittura

Una stringa in AgentConfig.plannerStrategy(...) cambia il modo in cui l'agente ragiona. Tutte supportano la cancellazione cooperativa e registrano una traccia di esecuzione completa — inclusa la traccia parziale in caso di fallimento.

StrategiaValoreCosa fa
ReactStrategy"react"Pensa → Agisci → Osserva. Quella di default.
ReSpActStrategy"respact"ReAct più un'azione di parola — fai una domanda di chiarimento a metà task e riprendi sulla stessa sessione invece di ripartire da capo.
ReflActStrategy"reflact"ReAct più auto-correzione nel loop: una chiamata a tool fallita inietta una correzione di rotta nella stessa memoria di lavoro, mantenendo tutto quanto già ottenuto.
PlanExecuteStrategy"plan_execute"Produce un piano strutturato, poi lo esegue passo per passo.
ReflexionStrategy"reflexion"Genera → critica → rivedi, riavviando l'episodio con la critica iniettata.
RetrievalAugmented"rag+<name>"Decoratore: inietta contesto recuperato prima di ogni chiamata all'LLM — "rag+react", "rag+respact", "rag+plan_execute", "rag+reflact".

Due tipi di auto-critica — e si compongono

"reflexion" reagisce a un intero passaggio fallito: azzera la memoria di lavoro, riprova l'episodio. "reflact" reagisce dentro un passaggio e il loop semplicemente continua. Usa reflact quando i singoli step falliscono in modo recuperabile, reflexion quando il fallimento è rilevabile solo dopo un passaggio completo.

L'human-in-the-loop è una primitiva del runtime

AgentState.WAITING, un gate di approvazione e notificatori collegabili. ApprovalDecision è un'interfaccia sealed — gestire in modo esaustivo approva / rifiuta / modifica è imposto dal compilatore, non da una convenzione.

ara-private · non pubblico

Oltre il runtime open source

ARA Gateway ed ARA Voice — lo stesso agente, altri canali

Il runtime sopra è il nucleo, pubblico e sufficiente da solo. Sopra di esso viviamo due moduli privati che espongono lo stesso AraAgent a canali diversi da una chiamata Java diretta — già in produzione, ma fuori dal repository pubblico.

ara-gateway — un runtime, più canali

Espone un AraRuntime su HTTP con run sincroni, in background e in streaming (SSE), e una superficie AgentOS opt-in: un RemoteAgent di Agno non modificato — o il control plane os.agno.com — può guidare un agente ARA come se fosse un agente Agno nativo. Un runtime senza gateway attorno non apre nessuna porta: ara-runtime non acquisisce mai una dipendenza HTTP, il gateway resta un livello opzionale sopra di esso.

ara-voice — canale vocale in tempo reale

Un modulo separato che porta lo stesso agente su una chiamata vocale: segnalazione WebRTC, riconoscimento vocale in streaming e sintesi vocale con barge-in, così l'utente può interrompere l'agente mentre sta ancora parlando. Vive fuori dal repository pubblico e non è distribuito.

Esempio — Agno come client e come server

La stessa integrazione, in entrambe le direzioni

1. Agno come client — guida un agente ARA

Esponi la superficie AGENTOS: un agno.agent.RemoteAgent non modificato la tratta come un'istanza AgentOS qualunque.

Gateway.java
AraRuntime runtime = AraRuntime.builder().llmClient(llm).build();
runtime.start();

AraAgent agent = runtime.createAgent(AgentConfig.defaults()
        .agentId(AgentId.of("research-agent"))
        .agentType("research")
        .name("Research Agent")
        .build());

AraGateway gateway = AraGateway.builder(runtime)
        .port(8090)
        .expose(Surface.AGENTOS)          // wire format compatibile Agno
        .build();
gateway.start();
// -> http://localhost:8090/agentos
client.py — lato Agno
from agno.agent import RemoteAgent

remote = RemoteAgent(
    base_url="http://localhost:8090/agentos",
    agent_id="research-agent",
    protocol="agentos",
)

response = remote.run("Summarize the latest ARA release notes")
print(response.content)

2. Agno come server — ARA delega a un agente Agno

Non serve un client Agno dedicato: un AraTool qualsiasi che parla lo stesso wire format, nella direzione opposta, basta a far delegare l'agente ARA a uno specialista ospitato su Agno.

AgnoAgentTool.java
class AgnoAgentTool implements AraTool {
    private final HttpClient http = HttpClient.newHttpClient();
    private final String agnoBaseUrl;   // es. istanza Agno AgentOS
    private final String agnoAgentId;

    @Override public String toolId()      { return "ask_agno_specialist"; }
    @Override public String description() { return "Delega una domanda a uno specialista ospitato su Agno."; }
    @Override public String argumentSchema() {
        return """
               {"type":"object","properties":{"question":{"type":"string"}},"required":["question"]}
               """;
    }

    @Override
    public ToolResult execute(String argumentJson) {
        String question = JsonFieldExtractor.field(argumentJson, "question");
        String body = "message=" + URLEncoder.encode(question, UTF_8) + "&stream=false";
        HttpRequest req = HttpRequest.newBuilder(
                        URI.create(agnoBaseUrl + "/agents/" + agnoAgentId + "/runs"))
                .header("Content-Type", "application/x-www-form-urlencoded")
                .POST(HttpRequest.BodyPublishers.ofString(body))
                .build();
        HttpResponse resp = http.send(req, HttpResponse.BodyHandlers.ofString());
        return ToolResult.success(toolId(), resp.body());
    }
}

// Registralo come un tool qualsiasi — l'agente ARA ora può chiamare Agno.
AgentConfig config = AgentConfig.defaults()
        .agentType("coordinator")
        .enabledTools(List.of("ask_agno_specialist"))
        .build();

Tutte le superfici del gateway

Otto route indipendenti, ognuna attivabile a sé

Ogni route è registrata sempre — a decidere se risponde o restituisce 404 surface_disabled è la configurazione, per singola richiesta. Nessuna superficie è attiva per caso.

SurfaceEndpoint principaliAuthClient tipico
SYSTEMGET /health, /infonessunamonitoring, load balancer
DISCOVERYGET /agents, /agents/{id}, /agents/type/{type}, /agents/state/{state}nessunadashboard, service discovery
RUNSPOST /agents/{id}/runs (sync, ?background=true, ?stream=true), GET/DELETE /runs/{taskId}bearer/customfrontend proprio, backend interno
CONTROLPOST .../terminate, gestione sessioni, PUT .../configbearer/custompannello di amministrazione
SESSIONSGET .../history, .../state, .../memorybearer/customdebug, audit, supporto
AGENTOSGET/POST sotto /agentosbearer/customAgno RemoteAgent, os.agno.com
A2AGET /.well-known/agent.json, POST /.well-known/a2a (JSON-RPC 2.0)bearer/custom (AgentCard esclusa)AWS Bedrock AgentCore, Google ADK, LangGraph, ag2
APPROVALSGET /approvals, POST /approvals/{id}/decisionbearer/custompannello human-in-the-loop

3. REST nativo — sincrono e in streaming

Nessun formato di compatibilità: JSON in ingresso, JSON o Server-Sent Events in uscita — per qualunque frontend o backend che parli HTTP.

sync
curl -X POST http://localhost:8080/agents/research-agent/runs \
  -H "Authorization: Bearer $ARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"input": "Summarize the latest ARA release notes"}'
streaming — event: token / tool_call / speak / final
curl -N -X POST "http://localhost:8080/agents/research-agent/runs?stream=true" \
  -H "Authorization: Bearer $ARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"input": "Summarize the latest ARA release notes", "sessionId": "s-1"}'
# event: token       {"text":"ARA"}
# event: token       {"text":" is"}
# event: tool_call   {"toolId":"web_search"}
# event: final       {"content":"...", "tokensUsed":...}

4. Agent2Agent (A2A) — interoperabilità cross-vendor

JSON-RPC 2.0 sui tipi ufficiali del protocollo A2A — lo stesso standard che AWS Bedrock AgentCore, Google ADK, LangGraph e ag2 sanno già parlare, senza codice specifico per ARA.

message/send
curl -X POST http://localhost:8080/.well-known/a2a \
  -H "Authorization: Bearer $ARA_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "message/send",
    "params": {
      "message": {
        "parts": [{"kind": "text", "text": "Summarize the latest ARA release notes"}],
        "metadata": {"agentId": "research-agent"}
      }
    }
  }'

Stato attuale

  • ara-gateway e ara-voice fanno parte di ara-private, un repository separato e non pubblico.
  • Condividono le stesse interfacce di ara-core: un agente scritto per il runtime open source gira su entrambi i canali senza modifiche.
  • L'integrazione con Agno riguarda solo ara-gateway — espone la superficie AgentOS, non richiede alcuna modifica lato Agno.
  • Se diventeranno mai pubblici non è deciso.

Come si confronta ARA

Tabella onesta. Comprese le righe che perdiamo.

Confrontato con LangChain4j 1.17, Spring AI 2.0 e Koog (JetBrains) — ultima verifica 2026-08-03. Questo ecosistema si muove velocemente; apri una issue se una riga è diventata obsoleta.

ARALangChain4jSpring AIKoog
Focus principale Runtime multi-agente + orchestrazione Toolkit di integrazione LLM + workflow agentici Integrazione LLM per app Spring Framework per agenti multipiattaforma
Contratti I/O deterministici ✓ AgentContract, zero token ———
Pipeline FSM con routing ✓ fsmBuilder() —— ✓ grafi FSM tipizzati
Dispatch parallelo dei tool ✓ automatico, thread virtuali opt-in executeToolsConcurrently(Executor) sequenziale di default coroutine; ExecutorService per stage
Annotazioni nessuna nessuna annotazioni Spring @Tool / @LLMDescription
Dipendenza da framework nessuna — pure interfacce Java nessuna richiede Spring Boot runtime Kotlin; Spring opzionale
Loop di esecuzione dell'agente 6 strategie built-in + decoratore RAG workflow + supervisor (langchain4j-agentic) catena di advisor + Spring AI Agents strategie a grafo e funzionali
Grafo multi-agente con cicli ✓ rami paralleli + back-edge workflow componibili; nessuna API per grafi ciclici non integrato ✓ nodi, archi, sottografi
Test senza un LLM ✓ ScriptedLlmClient parzialeparziale ✓ MockLLMBuilder
Gestione errori tipizzata LlmException · isRetryable() · ErrorType gerarchia Retriable / NonRetriable specifica per provider retry e fault tolerance integrati
Versione Java 21+17+21+17+
Checkpoint durevole / ripresa ✗ non implementato tramite LangGraph4j tramite LangGraph4j ✓ persistenza + punti di ripristino

Dove vincono le alternative

  • Koog copre gran parte dello stesso terreno con un modello a grafo maturo, persistenza degli agenti e un'API di mocking più ricca — e funziona anche fuori dalla JVM.
  • Ti serve il checkpoint durevole oggi? ARA non lo ha ancora. Koog o LangGraph4j sono la scelta più diretta.
  • Sei già su Spring Boot end to end? Spring AI risulterà più nativo di qualsiasi cosa qui.
  • Lo spazio JVM più ampio è più grande di questa tabella: LangGraph4j porta uno StateGraph in stile LangGraph, Embabel segue la strada del Goal-Oriented Action Planning, e l'ADK di Google per Java punta a sistemi multi-agente gerarchici attorno ad A2A.

Architettura

Quattro moduli. Prendi solo ciò che ti serve.

ara-core è interfacce e modello di dominio — nient'altro. Tutto ciò che sta sopra è un'implementazione che puoi sostituire.

ara-core

Pure interfacce: AraAgent, LlmClient, MemoryManager, ToolRegistry, AgentContract, ExecutionStrategy.

ara-runtime

AraRuntime, le quattro strategie, ContractEnforcer, AgentPipeline, ScriptedLlmClient, processori built-in.

ara-adapters

Client basati su LangChain4j per OpenAI, Anthropic e Ollama. Niente Kotlin, niente OkHttp, niente Spring.

ara-examples

Demo eseguibili: stub offline, LLM live, e un loop di coding autonomo con quality gate.

Guida rapida

Dal clone a un agente in esecuzione

Java 21+ e Maven 3.9+. Il primo agente non richiede alcuna API key.

Compilalo

ARA è attualmente una 1.0.0 — installala nel tuo repository locale.

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

Aggiungi il runtime

ara-runtime trascina ara-core transitivamente. Aggiungi ara-adapters quando vuoi un modello reale.

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

Fallo puntare su un modello reale

OpenAI, Anthropic, Ollama, o qualsiasi endpoint compatibile OpenAI — LM Studio, Groq, Together AI.

LlmClient claude = AraLlmClientFactory.anthropic()
        .apiKey(System.getenv("ANTHROPIC_API_KEY"))
        .model(AnthropicLlmClient.Models.CLAUDE_SONNET_4_6)
        .build();

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

Leggi il resto

Il README è il manuale completo — sessioni, budget di costo, telemetria, la knowledge base e ogni processore. Le decisioni di design vivono come ADR in docs/adr/.

Se la JVM è dove vivono i tuoi sistemi,
è lì che dovrebbero vivere anche i tuoi agenti.

ARA è Apache 2.0, sviluppato in modo aperto, con ampio uso di assistenza AI sotto revisione architetturale umana. Issue, discussioni sugli ADR e PR sono tutti benvenuti.