Agents

A JADE platform consists of one or more agent containers.

Containers may run on different JVMs running on different machines.

Exactly one container is designated as the main container. It hosts the DF and AMS agents.

An agent is an active object (i.e., owns its own thread of control). It has a unique identifier (AID) and a state. An agent also has a scheduler and a message queue.

The lifecycle of an agent is shown in the following diagram:

When an agent's constructor terminates it is in the INITIATED state. Now the agent's run() method is called. The agent registers its AID with the platform's AMS, enters its ACTIVE state, calls the setup() method, then begins its main loop.

Format of an AID identifier:

localName@hostName:port/JADE

Agent Declaration (heavily abbridged)

public class Agent implements Runnable, Serializable {
   private int myState = INITIATED;
   private Scheduler myScheduler;
   private AID myAID;
   private MessageQueue messageQueue;
   public void run() {
      myState = ACTIVE;
      setup();
      mainLoop();
   }
   protected void setup() {
      // modify AMS registration?
      // register services with DFs?
      // add behaviors
   }
   protected void takeDown() {
      // cleanup before de-registration
   }
   // etc.
}

The main loop:

private void mainLoop() {
   while(myState != DELETED) {
      if(myState == ACTIVE) {
         currentBehaviour = myScheduler.schedule();
         currentBehaviour.action();
         if(currentBehaviour.done()) {
            currentBehaviour.onEnd();
            myScheduler.remove(currentBehaviour);
         } else if(!currentBehaviour.isRunnable()) {
            myScheduler.block(currentBehaviour);
         }
      } // else if etc.
   } // while
} // mainLoop

Each agent is equipped with a message queue. Agents can perform various non-blocking or blocking receives on this queue. An agent sends an ACL message to another agent through the ACC.

Behaviours implement message exchange protocols. A behaviour is basically a FSM.

An agent's scheduler implements a non-preemptive round robin scheduling algorithm.

When a behaviour's action method calls block, the behaviour enters a special blocked state when the action method terminates. A behaviour returns to its active state when:

1. The behaviour's agent receives a message
2. A timeout occurs
3. The behaviour's restart() method is called.

Sleeping behaviors are awakened by the agent at predetermined times and the handleElapsedTimeout() method is called.

Behaviors

Blocking Behaviors

In this example B1.action() is called 5 times by an A1 agent with no guarantees made on the time elapsed between calls:

class B1 extends SimpleBehaviour {
   private int count = 0;
   public void action() {
      System.out.println("B1 time = " +
         System.currentTimeMillis() % 1000);
   }
   public boolean done() { return 5 <= ++count; }
}

public class A1 extends Agent {
   public void setup() {
      Behaviour b = new B1();
      addBehaviour(b);
   }
}

B1 time = 466
B1 time = 486
B1 time = 496
B1 time = 496
B1 time = 496

We can postpone the next call to B1.action using the block() method.:

class B1 extends SimpleBehaviour {
   private int count = 0;
   public void action() {
      System.out.println("B1 time = " +
         System.currentTimeMillis() % 1000);
      block(100); // if !done call me again in 100 ms
   }
   public boolean done() { return 5 <= ++count; }
}

B1 time = 285
B1 time = 395
B1 time = 496
B1 time = 606
B1 time = 716

Several points to notice:

1. The intervals between calls are at least 100 ms.

2. Calling block causes the agent to place the behavior in a blocked behaviors queue as soon as action returns.

3. The behavior will be restarted when the time interval has elapsed or when a message is received, or if the agent explicitly calls the behavior's restart method.

3. It doesn't matter where block is called within action nor how many times it is called.

Basic FSM Behavior:

class FSM extends SimpleBehaviour {
   private int state = 1;
   public FSM(Agent a) { super(a); }
   public void action() {
      System.out.println("entering FSM.action()");
      System.out.println("time = " +
         System.currentTimeMillis() % 1000);
      System.out.println("state = " + state);
      switch(state) {
         case 1:
            block(200);
            System.out.println("state 1 behavior goes here");
            state++;
            break;
         case 2:
            block(300);
            System.out.println("state 2 behavior goes here");
            state++;
            break;
         case 3:
            System.out.println("state 3 behavior goes here");
            state++;
            break;
      }
   }

   public boolean done() {
      boolean finished = (state == 4);
      if (finished) myAgent.doDelete();
      return finished;
   }
}

Running:

runjade a1:demos.A1
// brutal jibberish spewed here for a while and then:


entering FSM.action()
time = 265
state = 1
state 1 behavior goes here
entering FSM.action()
time = 466
state = 2
state 2 behavior goes here
entering FSM.action()
time = 776
state = 3
state 3 behavior goes here
A1 agent shutting down

Tickers

A ticker behavior blocks for a specified number of milliseconds, but unlike a normal blocked behavior, it is not restarted each time the agent receives a message:

class B2 extends TickerBehaviour {
   public B2(Agent a, int dt) { super(a, dt); }
   public void onTick() {
      System.out.println("B2 time = " +
         System.currentTimeMillis() % 1000);
   }
}

public class A1 extends Agent {
   protected void setup() {
      Behaviour b = new B2(this, 100);
      addBehaviour(b);
   }
}

B2 time = 525
B2 time = 635
B2 time = 736
B2 time = 846
B2 time = 956

Delayed behaviors

class B3 extends WakerBehaviour {
   public B3(Agent a, int dt) { super(a, dt); }
   public void handleElapsedTimeout() {
      System.out.println("B3 time = " + System.currentTimeMillis());
   }
}

public class A1 extends Agent {
   protected void setup() {
      System.out.println("A1 time = " + System.currentTimeMillis());
      Behaviour b = new B3(this, 1000);
      addBehaviour(b);
   }
}

A1 time = 1095965298914
B3 time = 1095965299415

Note that a waker wakes N millisecs after it is created, not after it starts.

In this version we reset the behavior:

class B3 extends WakerBehaviour {
   private int count = 0;
   public B3(Agent a, int dt) { super(a, dt); }
   public void handleElapsedTimeout() {
      System.out.println("B3 time = " + System.currentTimeMillis());
      if (++count < 5) reset(500);
   }
}

A1 time = 1095963853786
B3 time = 1095963854818
A1 time = 1095964612387
B3 time = 1095964612888
B3 time = 1095964613389
B3 time = 1095964613899
B3 time = 1095964614400
B3 time = 1095964614901

Reusable delayed behavior

public class DelayBehaviour extends SimpleBehaviour {
   private long timeout, wakeupTime;
   private boolean finished = false;

   public DelayBehaviour(Agent a, long timeout) {
      super(a);
      this.timeout = timeout;
   }

   public void onStart() {
      wakeupTime = System.currentTimeMillis() + timeout;
   }

   public void action() {
      long dt = wakeupTime - System.currentTimeMillis();
      if (dt <= 0) {
         finished = true;
         handleElapsedTimeout();
      } else {
         block(dt);
     }
   } //end of action

   protected void handleElapsedTimeout() {
      // by default do nothing !
   }

   public boolean done() { return finished; }
}

A message receiver

public class MyReceiver extends SimpleBehaviour {
   private MessageTemplate template;
   private long timeOut, wakeupTime;
  private boolean finished;
  private ACLMessage msg;
  public ACLMessage getMessage() { return msg; }
  public boolean done () { return finished; }

  public MyReceiver(Agent a, int millis, MessageTemplate mt) {
      super(a);
      timeOut = millis;
      template = mt;
  }

   public void onStart() {
      wakeupTime = (
         timeOut<0 ? Long.MAX_VALUE
                    :System.currentTimeMillis() + timeOut);
   }



   public void action() {
      if(template == null)
            msg = myAgent.receive();
      else
         msg = myAgent.receive(template);

      if( msg != null) {
         finished = true;
         handle( msg );
         return;
      }
         long dt = wakeupTime - System.currentTimeMillis();
         if ( dt > 0 )
            block(dt);
         else {
         finished = true;
         handle( msg );
         }
   }

   public void handle( ACLMessage m) {
      if (msg == null)
         System.out.println("Timeout");
         else
         System.out.println("Received: "+ msg);
    }

   public void reset() {
      msg = null;
      finished = false;
      super.reset();
  }

   public void reset(int dt) {
      timeOut= dt;
      reset();
  }
}