Salta al contenuto principale

03 - JADE framework: implementare il primo agente

JADE nel 2026: versione, fork e Maven Central controllati, due agenti Java come schema non eseguito e un simulatore Python che ne mostra il comportamento.

Cosa ne pensi di questo articolo?

03 - JADE framework: implementare il primo agente
Sistemi Multi-Agentefedericocalo.dev

In Breve

JADE nel 2026: versione, fork e Maven Central controllati, due agenti Java come schema non eseguito e un simulatore Python che ne mostra il comportamento.

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. Il pom.xml dichiara la versione 4.5.3, l'API di GitLab riporta come ultimo commit del ramo master quello 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.mas e com.gitlab.jade-project non trovano nulla; la ricerca per la classe jade.core.Agent trova solo net.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.

OggettoChe cosa fa (dal Javadoc 4.5.0)
jade.core.AgentLa 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 TickerBehaviourIl 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.ACLMessageIl 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, ServiceDescriptionIl 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 Messaggio non 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 di Behaviour, CyclicBehaviour, TickerBehaviour, ACLMessage, MessageTemplate, DFService, DFAgentDescription, ServiceDescription e Boot stanno 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.Agent su Maven Central - le sole ripubblicazioni trovate, di terzi; le ricerche per gruppo com.tilab.jade, eu.mas e com.gitlab.jade-project hanno dato zero risultati, consultata il 2026-11-13
  • Le uscite del simulatore nascono da python3 -I mini_piattaforma.py sul 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

Prova Questi Strumenti

Strumenti gratuiti correlati a questo argomento.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti