Vuoi far dialogare più agenti su una stessa piattaforma e il nome che esce ovunque è JADE, il framework Java per agenti conforme alle specifiche FIPA. Prima di scrivere una riga conviene una domanda poco glamour: in che stato è oggi? Una libreria a cui non puoi aggiungere una dipendenza con una riga di pom.xml cambia il modo in cui la usi, e in questo caso la risposta può non essere quella che ti aspetti.
La risposta in breve, controllata il 2026-11-13: il sito storico del progetto indica come ultima versione la 4.6.0 del 7 dicembre 2022; esiste un fork comunitario su GitLab, fermo alla versione 4.5.3 e senza commit sul ramo principale dopo il 7 febbraio 2024; e JADE non è su Maven Central con coordinate ufficiali. Per questo il codice Java di questo articolo è uno schema da adattare, non eseguito: l'ho scritto sul Javadoc ufficiale, non l'ho mai compilato. Il comportamento che il codice descrive, invece, lo dimostri con un simulatore Python tuo, eseguito due volte con la stessa uscita.
Prima di installare: in che stato è JADE
Ogni riga della lista è un fatto con la sua fonte, tutti consultati il 2026-11-13. Dove la fonte non risponde da questa macchina, la riga lo dice.
- Sito storico (
jade.tilab.com): da questa macchina le richieste di pagina sono andate in timeout. Ho letto una copia conservata da Internet Archive il 2 dicembre 2025, che riporta «Jade 4.6.0 latest version (07/12/2022)» e la licenza LGPL (versione 2) con Telecom Italia come titolare dei diritti. - Fork comunitario (
gitlab.com/jade-project/jade): il README dice che è un fork di JADE 4.5, nato perché il gruppo che lo sviluppava non può più continuare. Ilpom.xmldichiara la versione 4.5.3, l'API di GitLab riporta come ultimo commit del ramomasterquello del 2024-02-07 e tre tag (2026.02,m4.5.3,4.5.3). - Distribuzione del fork: la pagina di download del fork spiega come aggiungerlo a un progetto Maven tramite JitPack, con versione
master-SNAPSHOT. Non ho provato JitPack. - Maven Central: le ricerche per
com.tilab.jade,eu.masecom.gitlab.jade-projectnon trovano nulla; la ricerca per la classejade.core.Agenttrova solonet.sf.ingenias:jade, una ripubblicazione di terzi che si ferma alla 4.3 (settembre 2015). - Compatibilità con il JDK in uso: il sito storico dichiara come requisito minimo Java 5; non ho verificato che il codice giri su un JDK recente, perché non l'ho avviato.
Il quadro che ne esce non è «abbandonato», ma nemmeno «mantenuto» nel senso in cui si intende per una libreria viva: c'è una versione recente sul sito storico, un fork che conserva la 4.5 e un solo canale dichiarato per chi usa Maven, che passa da un servizio terzo e che non ho provato. È una decisione di progetto da prendere con questi fatti in mano, non con quelli che trovi in un vecchio tutorial.
Le idee di JADE in cinque oggetti
Il Javadoc che citi è quello della versione 4.5.0, pubblicato sul sito del fork. Sono cinque oggetti, e sono quelli che servono per leggere il codice che segue.
| Oggetto | Che cosa fa (dal Javadoc 4.5.0) |
|---|---|
jade.core.Agent | La classe da estendere. setup() e takeDown() sono segnaposti vuoti per avvio e pulizia, addBehaviour(Behaviour) aggiunge un comportamento, receive(MessageTemplate) restituisce il primo messaggio che corrisponde al modello oppure null. |
CyclicBehaviour e TickerBehaviour | Il primo è un comportamento atomico da eseguire per sempre (il suo done() restituisce sempre false); il secondo chiama onTick() a intervalli di un certo numero di millisecondi. |
Behaviour.block() | Non è una chiamata bloccante. Al ritorno di action() il comportamento passa in una coda di bloccati e torna fra gli attivi quando arriva un messaggio o viene riavviato con restart(). |
jade.lang.acl.ACLMessage | Il messaggio: costanti come REQUEST e INFORM per la performativa, setContent, addReceiver e createReply(), che crea la risposta impostando destinatario, linguaggio, ontologia, protocollo e identificativo di conversazione. |
DFService, DFAgentDescription, ServiceDescription | Il Directory Facilitator, il servizio di pagine gialle: register pubblica un agente, search restituisce gli agenti che offrono un servizio, deregister lo toglie. |
Gli oggetti sono pochi perché la piattaforma fa il resto: il programmatore scrive classi che estendono Agent e comportamenti con un metodo action(), e JADE decide quando chiamarli. L'oggetto che conta di più per capire le prestazioni è block(): senza, un comportamento che aspetta un messaggio viene richiamato in continuazione. Lo vedrai misurato nel simulatore fra poco.
Lo schema Java: un agente che risponde e uno che chiede
Il primo blocco è l'agente che offre un servizio di «eco». In setup() costruisce una DFAgentDescription con il proprio identificativo, ci aggiunge una ServiceDescription di tipo servizio-eco e la registra con DFService.register; se la registrazione fallisce, chiama doDelete() e termina. Il comportamento ciclico chiede receive con un modello che accetta solo messaggi REQUEST: se la risposta è null chiama block() ed esce, altrimenti costruisce con createReply() una risposta INFORM con il contenuto ripetuto e la invia. Togliendo block(), il comportamento continua a ripetere receive a vuoto. takeDown() deregistra l'agente dal DF.
import jade.core.Agent;
import jade.core.behaviours.CyclicBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
public class AgenteEco extends Agent {
@Override
protected void setup() {
DFAgentDescription dfd = new DFAgentDescription();
dfd.setName(getAID());
ServiceDescription sd = new ServiceDescription();
sd.setType("servizio-eco");
sd.setName("eco-" + getLocalName());
dfd.addServices(sd);
try {
DFService.register(this, dfd);
} catch (FIPAException e) {
doDelete();
return;
}
addBehaviour(new CyclicBehaviour(this) {
private final MessageTemplate soloRichieste =
MessageTemplate.MatchPerformative(ACLMessage.REQUEST);
@Override
public void action() {
ACLMessage msg = myAgent.receive(soloRichieste);
if (msg == null) {
block();
return;
}
ACLMessage risposta = msg.createReply();
risposta.setPerformative(ACLMessage.INFORM);
risposta.setContent("eco:" + msg.getContent());
myAgent.send(risposta);
}
});
}
@Override
protected void takeDown() {
try {
DFService.deregister(this);
} catch (FIPAException e) {
// il DF potrebbe essere gia' sparito: non c'e' altro da pulire
}
}
}
Il secondo blocco è l'agente che chiede. Usa un TickerBehaviour con periodo di 5000 millisecondi; a ogni onTick() cerca nel DF un agente di tipo servizio-eco con DFService.search. Se non ne trova torna senza fare nulla, e riprova al tick successivo; se ne trova uno, costruisce un messaggio REQUEST, aggiunge il destinatario con addReceiver e lo invia. È la ragione per cui il chiamante non conosce il nome dell'altro agente: conosce solo il tipo di servizio.
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
public class AgenteChiamante extends Agent {
@Override
protected void setup() {
addBehaviour(new TickerBehaviour(this, 5000) { // onTick() ogni 5000 ms
private int n = 0;
@Override
protected void onTick() {
DFAgentDescription modello = new DFAgentDescription();
ServiceDescription sd = new ServiceDescription();
sd.setType("servizio-eco");
modello.addServices(sd);
try {
DFAgentDescription[] trovati = DFService.search(myAgent, modello);
if (trovati.length == 0) {
return; // nessun eco registrato: riprova al prossimo tick
}
n++;
ACLMessage richiesta = new ACLMessage(ACLMessage.REQUEST);
richiesta.addReceiver(trovati[0].getName());
richiesta.setContent("ping " + n);
myAgent.send(richiesta);
} catch (FIPAException e) {
// ricerca fallita: lo si vedra' al prossimo tick
}
}
});
}
}
Dichiaro il limite in chiaro: questi due file sono uno schema da adattare, non eseguito. Per ogni classe, metodo e costante ho controllato nome e firma sul Javadoc della 4.5.0, ma non li ho mai compilati, perché la libreria non è su Maven Central e non ho scaricato né compilato il fork. La classe di avvio della piattaforma è jade.Boot, che il Javadoc descrive come ciò che fa partire JADE leggendo gli argomenti della riga di comando, e le opzioni si elencano con Boot.printUsage(): non le riporto perché non le ho provate. Non ho nemmeno avviato un container principale, quindi non ho misurato quali porte di rete apra.
Lo stesso comportamento in un simulatore Python
Quello che si può dimostrare qui è la logica: comportamenti che il sistema chiama a turno, receive che non blocca, block() che sospende fino all'arrivo di un messaggio, una rubrica di servizi. Il simulatore non è JADE e non ne riproduce niente altro: niente rete, niente contenitori, niente codifica FIPA dei messaggi, niente thread. I nomi dei metodi seguono quelli del Javadoc (receive, block, send, crea_risposta per createReply). Il primo blocco contiene i messaggi, i comportamenti e la piattaforma con il suo ciclo.
"""Simulatore di una piattaforma ad agenti: NON e' JADE, ne riproduce il modello di esecuzione
(comportamenti cooperativi, receive() non bloccante, block(), Directory Facilitator)."""
REQUEST, INFORM = "REQUEST", "INFORM"
class Messaggio:
def __init__(self, performativa, mittente, destinatario, contenuto):
self.performativa, self.mittente = performativa, mittente
self.destinatario, self.contenuto = destinatario, contenuto
def crea_risposta(self):
# come ACLMessage.createReply(): il destinatario diventa il mittente
return Messaggio(None, self.destinatario, self.mittente, None)
class Comportamento:
def __init__(self, agente):
self.agente, self.bloccato, self.chiamate = agente, False, 0
def block(self):
self.bloccato = True # NON e' una chiamata bloccante: agisce a action() finita
class Piattaforma:
def __init__(self):
self.agenti, self.df, self.t, self.log = {}, {}, 0, []
def aggiungi(self, agente):
self.agenti[agente.nome] = agente
agente.piattaforma = self
agente.setup()
def invia(self, msg):
dest = self.agenti[msg.destinatario]
dest.coda.append(msg)
for c in dest.comportamenti: # l'arrivo di un messaggio risveglia i bloccati
c.bloccato = False
self.log.append(f"t={self.t:<2} {msg.mittente} -> {msg.destinatario} "
f"{msg.performativa} {msg.contenuto!r}")
def esegui(self, istanti):
for self.t in range(1, istanti + 1):
for ag in list(self.agenti.values()): # round-robin, ordine di creazione
for c in list(ag.comportamenti):
if not c.bloccato:
c.chiamate += 1
c.action()
Il ciclo di esegui passa a turno su ogni agente, nell'ordine in cui sono stati creati, e su ogni suo comportamento non bloccato: chiama action() e conta la chiamata. La regola importante è in invia: quando arriva un messaggio, tutti i comportamenti bloccati dell'agente destinatario vengono sbloccati, come dice il Javadoc di block() per l'arrivo di un messaggio. Se togli quella riga, un comportamento bloccato non ripartirebbe più.
Il secondo blocco contiene l'agente base e l'agente eco. La classe Ricevi dentro setup è il comportamento ciclico: chiede un messaggio REQUEST, e se non c'è lo si blocca solo quando con_block è vero; se c'è, crea la risposta INFORM con il contenuto ripetuto e la invia. Il parametro con_block serve solo all'esperimento: con False il comportamento sonda a vuoto ogni passo.
class Agente:
def __init__(self, nome):
self.nome, self.coda, self.comportamenti, self.piattaforma = nome, [], [], None
def setup(self):
pass
def receive(self, performativa): # non bloccante, come in JADE
for m in self.coda:
if m.performativa == performativa:
self.coda.remove(m)
return m
return None
def send(self, msg):
self.piattaforma.invia(msg)
class Eco(Agente):
def __init__(self, nome, con_block=True):
super().__init__(nome)
self.con_block = con_block
def setup(self):
self.piattaforma.df.setdefault("servizio-eco", []).append(self.nome) # registra nel DF
agente, con_block = self, self.con_block
class Ricevi(Comportamento): # il CyclicBehaviour: non finisce mai
def action(self):
msg = agente.receive(REQUEST)
if msg is None:
if con_block:
self.block()
return
r = msg.crea_risposta()
r.performativa, r.contenuto = INFORM, "eco:" + msg.contenuto
agente.send(r)
self.ricevi = Ricevi(self)
self.comportamenti.append(self.ricevi)
Il terzo blocco contiene l'agente che chiede e l'esperimento. Chiamante ha un comportamento a intervalli: ogni 5 istanti cerca nella rubrica un servizio servizio-eco e invia una richiesta. La funzione prova mette in piattaforma un eco e un chiamante, esegue 30 istanti e restituisce il numero di chiamate di action() dell'eco. L'ultima assert è il controllo fatto a mano: l'eco gira prima del chiamante, quindi una richiesta inviata all'istante t viene servita a t+1; le chiamate sono 1 iniziale più 2 per ognuna delle 5 richieste servite (una che risponde, una che si blocca), e la sesta richiesta, inviata all'istante 30, non fa in tempo a essere servita.
class Chiamante(Agente):
def __init__(self, nome, periodo):
super().__init__(nome)
self.periodo, self.n = periodo, 0
def setup(self):
agente = self
class Ticker(Comportamento): # il TickerBehaviour: onTick ogni 'periodo'
def action(self):
t = agente.piattaforma.t
if t % agente.periodo == 0:
eco = agente.piattaforma.df["servizio-eco"][0] # ricerca nel DF
agente.n += 1
agente.send(Messaggio(REQUEST, agente.nome, eco, f"ping {agente.n}"))
agente.receive(INFORM) # consuma le risposte
self.comportamenti.append(Ticker(self))
def prova(con_block):
p = Piattaforma()
p.aggiungi(Eco("eco", con_block))
p.aggiungi(Chiamante("chiamante", 5))
p.esegui(30)
return p, p.agenti["eco"].ricevi.chiamate
if __name__ == "__main__":
p, con = prova(True)
print("\n".join(p.log[:4]), "...", sep="\n")
_, senza = prova(False)
print(f"messaggi scambiati: {len(p.log)}")
print(f"action() di Eco in 30 istanti: con block() = {con}, senza block() = {senza}")
# Controllo a mano: Eco gira prima del chiamante, quindi la richiesta inviata al
# tempo t viene servita a t+1. Chiamate: 1 iniziale + 2 per ognuna delle 5 richieste
# servite (quella che risponde, quella che si blocca). La sesta, a t=30, non e' servita.
assert con == 1 + 2 * 5 and senza == 30 and len(p.log) == 6 + 5
print("controlli a mano: ok")
# Output (due esecuzioni identiche):
# t=5 chiamante -> eco REQUEST 'ping 1'
# t=6 eco -> chiamante INFORM 'eco:ping 1'
# t=10 chiamante -> eco REQUEST 'ping 2'
# t=11 eco -> chiamante INFORM 'eco:ping 2'
# ...
# messaggi scambiati: 11
# action() di Eco in 30 istanti: con block() = 11, senza block() = 30
# controlli a mano: ok
Leggi i due numeri. Con block() l'eco esegue action() 11 volte in 30 istanti, senza block() 30: una per istante, a vuoto per la maggior parte. I messaggi scambiati sono 11, cioè 6 richieste e 5 risposte, perché l'ultima richiesta resta in coda. La differenza non è un'ottimizzazione: con block() l'eco consuma lavoro solo quando c'è qualcosa da fare, mentre senza il lavoro cresce con il tempo e non con i messaggi. È la ragione per cui nello schema Java il ramo msg == null chiama block().
Che cosa il simulatore non ti dice
I numeri del simulatore sono corretti per il simulatore. Non dicono nulla di JADE su queste questioni.
- I tempi reali: le «chiamate di
action()» sono un conteggio, non un costo in millisecondi o in CPU. - Il comportamento di più contenitori sulla rete e dei messaggi fra macchine diverse.
- La codifica dei messaggi e il rispetto delle specifiche FIPA, che il mio
Messaggionon tocca. - La compatibilità con la tua versione di Java, che richiede di compilare davvero la libreria o il fork.
Tutte e quattro le voci sono cose che solo un'esecuzione con la libreria vera può mostrare, ed è il motivo per cui il codice Java resta uno schema. Se decidi di adottare JADE, il primo passo non è scrivere agenti: è scaricare la libreria dal canale scelto, compilare i due file di questo articolo e vedere se l'uscita somiglia a quella del simulatore. Se non somiglia, conta quella della libreria.
Domande frequenti
JADE è ancora mantenuto?
Dipende da che cosa chiami JADE. Il sito storico indica la 4.6.0 del 7 dicembre 2022; il fork su GitLab è fermo alla 4.5.3 e il suo ultimo commit sul ramo principale è del 7 febbraio 2024. Non ho misurato l'attività della comunità oltre questi dati.
JADE si scarica da Maven Central?
Non con coordinate ufficiali, secondo le ricerche del 2026-11-13: nessun risultato per com.tilab.jade, eu.mas o com.gitlab.jade-project. Esiste una ripubblicazione di terzi, net.sf.ingenias:jade, ferma alla 4.3 del 2015. Il fork indica JitPack come canale.
Perché il codice Java non è stato eseguito?
Perché la libreria non è su Maven Central con coordinate ufficiali, e non ho scaricato né compilato il fork da GitLab o da JitPack. Non ho avviato nemmeno un container principale, quindi non ho misurato quali porte apra né se il codice funzioni. I due file Java sono uno schema da adattare, controllato nei nomi sul Javadoc e mai compilato.
Che cosa sai fare adesso
Sai valutare lo stato di una libreria prima di adottarla, con fonti e data, e sai dire che cosa quello stato implica per come la scarichi. Sai leggere il modello a comportamenti di JADE (setup, comportamento ciclico, receive non bloccante, block(), Directory Facilitator) su uno schema Java e vederne il significato in un simulatore che hai eseguito due volte con lo stesso risultato. L'articolo precedente ha dato struttura alla decisione di un agente, il successivo entra nei messaggi FIPA-ACL, e l'indice della serie elenca il resto.
Fonti
- jade-project/jade (GitLab) - README del fork, licenza, versione 4.5.3 nel
pom.xml, ultimo commit del ramo master (2024-02-07) e tag, letti tramite l'API pubblica di GitLab, consultata il 2026-11-13 - Download Jade (sito del fork) - indicazioni per JitPack e disponibilità della 4.5, consultata il 2026-11-13
- jade.core.Agent (Javadoc JADE 4.5.0) -
setup,takeDown,addBehaviour,receive,send, consultata il 2026-11-13; i Javadoc diBehaviour,CyclicBehaviour,TickerBehaviour,ACLMessage,MessageTemplate,DFService,DFAgentDescription,ServiceDescriptioneBootstanno sotto lo stesso indirizzo,/API/jade/... - jade.tilab.com, copia di Internet Archive del 2025-12-02 - versione 4.6.0 del 07/12/2022, licenza LGPL e requisito Java 5; il sito originale non ha risposto da questa macchina, consultata il 2026-11-13
- Ricerca per classe
jade.core.Agentsu Maven Central - le sole ripubblicazioni trovate, di terzi; le ricerche per gruppocom.tilab.jade,eu.masecom.gitlab.jade-projecthanno dato zero risultati, consultata il 2026-11-13 - Le uscite del simulatore nascono da
python3 -I mini_piattaforma.pysul listato dell'articolo (tre blocchi, porzioni contigue dello stesso file), eseguito due volte con uscita identica il 2026-11-13; i due file Java non sono stati compilati né eseguiti
Commenti
Caricamento commenti...
Accedi per commentare