Agent Input-Ouput System (AIOS)

Agent Class Template

function agentclass(p1,p2..) {
    // Body Variables //
    this.v = initial value
    // Activtites //
    this.act = {
        a1: function () { .. },
        a2: function () { .. },
        ..
    }
    // Activity Transitions //
    this.trans = {
        a1: anext,
        a2: function () { return anext },
        ..
    }
    // Signal Handler //
    this.on = {
        signal: function (arg) { .. },    
        ..
    }
    // Current and Initial Activity //
    this.next = ainit
}

Basic structure of an agent class constructor function

AgentJS API

The following sections describe the agent programming interface of AgentJS.

Computational Functions

There are various powerful and extended computational functions that can be used by agents. Please note that for some reason arrays and objects cannot be iterated in agent processes by using the for(p in a) statement. Instead the iter function has to be used. Furthermore, the this object inside function callbacks references always the agent object, i.e., body variables and functions can be accessed by the this object.

abs

function(number) → number

Returns absolute value of number.

add

function(a:number|array|object, b:number|array|object) → number|array|object

General purpose addition operation for scalar numbers, arrays, and objects of numbers.

iter

function(object|array, function (@element,@index?))

Iteration over object attributes or array elements.

concat

function(array|string|object,array|string|object) → array|string|object

Concatenation operation for arrays, strings, and objects.

contains

function(array|object,(number|string)|(number|string)[]) → boolean

Checks existence of an element or an array of elements in an array or object (attribute)

copy

function(array|object|string) → array|object|string

Returns a copy of an array, object, or string. The object may not contain cyclic references.

div

function(number,number) → number

Integer division operation

empty

function(array|object|string) → boolean

Checks if an object, string, or array is empty ({} [] "")

equal

function(number|string|array|object, number|string|array|object) → boolean

Checks equality of numbers, strings, arrays, and objects.

filter

function(array|object, function (@element,@index?) → boolean) → array|object

Filter operation for arrays and objects.

head

function(array) → *

Returns head (first) element of array.

int

function(number) → number

Returns integer number.

isin

function(array|object,number|string|(number|string)[]) → boolean

Checks existence of an element in an array or object (attribute). The element can be an array, too.

iter

function(array|object, function (@element,@index?))

Iterator for arrays and objects.

length

function(array|object|string) → number

Returns length of an array, object or string.

map

function(array|object, function (@element,@index?) → *|none) → array|object

Map and filter operation for arrays and objects. If the user function returns undefined the element is discarded.

matrix

function(@cols,@rows,@init) → [] array

Create a matrix (array of arrays).

max

function(a:number|array,b?:number) → number

Returns largest number from two numbers or from array of numbers.

min

function(a:number|array,b?:number) → number

Returns smallest number from two numbers or from array of numbers.

neg

function(number|array|object) → number|array|object

Returns negative number, array or object of numbers.

random

function(a:number|array|object,b?:number,frac?:number) → number|*

Returns a random number from the interval [a,b] or an element from an array or object. The optional fraction parameter specified the rounding precision (frac=1 return integer numbers).

reverse

function(array|string) → array|string

Reverses elements of an array or string.

sort

function(array, function (@element1,@element2) → number) → array

Sorts an array by a user function returning {-1,0,1} numbers. Descending order is reached if a<b return a positive value, otherwise if a negative value is returned an ascending order is reached.

sum

function(array|object,map?:function) → number

Returns the sum of elements of an array or attribute values of an object. The optional user mapping function can be used to return a computed value for each element.

string

function(*) → string

Returns string representation of argument.

tail

function(* array) → *

Returns tail (last) element of array.

zero

function(number|array|object) → boolean

Checks if a number, all elements of an array or all attributes of an object are zero.

Examples

this.a=[1,2,3];
this.o={real:2.0,img:3.1};

this.sq = function (objORarray) {
  var res=0;
  iter(objORarray,function (elem,index) {
    res=res+elem*elem;
  });
  return res;
}
..
    var x,y,z;
    x=this.sq(a); // x==14
    y=this.sq(o); // y==13.61
    z=sum(a);     // z==6
    if (zero(this.o)) this.o={real:1.0,img:1.0};
..

Environmental Information and Modification

info

function (kind) → {}

Return environmental information. Supported information requests are kind='node'|'version'|'host'. The node information returns {id:id struig, position:{x:number,y:number},location:undefined|{lat:number,lon:number},type:string}. The node type is a string identifier from the set {'shell', 'webshell', 'relay', 'webapp', 'mobileapp'}. The host information request returns information about the host platform {type:string='node'|'browser'}.

myClass

function () → string

Returns the class of the agent (if known). Same result is returned by accessing the this.ac variable.

myNode

function () → string

Returns the identity name of the current JAM node.

myParent

function () → string

Returns the identity name of the parent agent of this agent (if any).

me

function () → string

Returns the identity name of this agent.

negotiate

function (resource:string,value:*,capability?) → boolean with @resource='CPU'|'SCHED'|'MEM'|'TS'|'AGENT'|'LEVEL'

Negotiate an agent constraint parameter. Level 0 and 1 agents require a valid access capability with sufficient rights (0x80). The LEVEL resource is the agent privilege level.

privilege

function () → number={0,1,2,3}

Returns the current privilege level of the agent

clock

function (ms:boolean) → number|string

Returns current system clock in milliseconds (ms argument is true) or in time format HH&MM;SS.

Tuple Space Operations

Tuple spaces are data bases storing vectors of values. Each tuple has a dimension (the number of values) and a type interface. Tuples can be read or consumed by using patterns. Patterns are like tuple but allowing wild-card values (none). If there is no matching tuple found in the data base, the agent is suspended until a matching tuple arrives or a timeout occurs (by using the try_* operations). Since JavaScript programs cannot block, a callback function has to be provided and the blocking operation must be placed at the end of an activity or inside a scheduling block. Commonly the first value of a tuple (a string) is used as a key, but this is only a weak constraint that has not to be satisfied. If the first value is a string it is used as a hash key in the tuple data base speeding up tuple pattern matching. A tuple space has a linear structure and is non-persistent. To support complex hierarchical data bases, JAM provides a SQLite data base server and access to this data base for level 3 agents (see section [SQL Operations]). Tuple spaces can be mapped on tables in this SQL data base (by tuple space provider and consumer functions passed to JAM).

Types

type tuple = (number|string|boolean|array|object) []
type pattern = (number|string|boolean|array|object|null) []

Examples for tuple access

out(['MARKING1',1]);
out(['SENSORA',100,true]);
inp(['SENSORA',_,_],function (tuple) {
  if (tuple) this.s =tuple[1];
});
rm(['SENSORA',_,_],true);
try_rd(0,function (tuple) { .. });
ts(['MARKING',_],function (t) { t[1]++ });
alt([
  ['SENSORA',_,_],
  ['SESNORB',_],
  ['EVENT'],
  ],function (tuple) {
    if (tuple && tuple[0]=='EVENT') {..}
    else ..
  });

Operations

alt

function(pattern [],callback:function,all?:boolean,tmo?:number)

Input operation with multiple search patterns that can have different type interfaces and arities. The first tuple matching one of the pattern is consumed and passed to the callback function. If there are multiple tuples matching a specific pattern and the flag is set than all matching tuples are consumed and returned.

collect^1,2,3^

function (to:path,pattern) → number

The collect operation moves tuples from this source TS that match template pattern into destination TS specified by path to (a node destination).

copyto^1,2,3^

function (to:path,pattern) → number

Copies all matching tuples form this source TS to a remote destination TS specified by path to (a node destination).

evaluate

function (pattern,callback:function (tuple|none)) → tuple

Access an evaluator tuple created by a listen operation. The evaluator evaluates the given pattern to a tuple and passes the tuple back to the callback function of the requesting agent.

exists

function (pattern) → boolean

Check if a tuple matches the given patterns.

inp

function (pattern,callback:function,all?:boolean,tmo?:number)

Consumes a tuple matching the given pattern that is passed to the callback function. If there are multiple tuples matching a specific pattern and the all flag is set than all matching tuples (array) are consumed and returned. If there is no matching tuple and tmo is zero (immediate reply) or positive (timeout) than the callback handler is called with a none value argument.

listen

function (pattern,callback:function (pattern) → tuple)

Install a tuple evaluator (active tuple) that can be accessed by the evaluate oepration.

out

function (tuple)

Store a tuple in the data base.

mark

function (tuple,tmo:number)

Store a tuple with a limited lifetime in the data base.

rd

function (pattern,callback:function,all?:boolean,tmo?:number)

Read a tuple matching the given pattern that is passed to the callback function. If there are multiple tuples matching a specific pattern and the all flag is set than all matching tuples (array) are read. If there is no matching tuple and tmo is zero (immediate reply) or positive (timeout) than the callback handler is called with a none value argument.

rm

function (pattern,all?:boolean)

Remove a tuple or if the all flag is set all matching tuples from the data base.

store^1,2,3^

function (to:path,tuple) → number

Stores a tuple in a remote TS specified by path to (a node destination). Returns number of stored tuples.

ts

function (pattern,callback:function(tuple) → tuple)

Atomic and non-blocking test-and-set operation that can be used to modify a tuple in place found based on the provided pattern.

alt.try, inp.try, rd.try, evaluate.try

function (tmo:number,..)

Try operation and execute an alternation, input, or read operation with a given timeout (Milliseconds). If there was no matching tuple found and the timeout elapsed the callback is fired with a null argument, followed by the continuation of the agent execution with the next activity.

rd.try(timout,tuple,function (t) {
  if (t) this.data=t,log('GOTIT');
  else log('DONT GOTIT');
})

Active Tuples

Passive tuples are produced via the out operation and consumed via the rd and inp operations. Among passive tuples, there are active tuples that are evaluated by a consumer and passed back to the original producer (bidirectional tuple exchange) by using the listen and evaluate operations.

listen(pattern, function (tuple) {
  Modification of tuple: Replace formal parameters with actual
  return tuple'
})
evaluate(pattern, function (tuple) {
  Process evaluated tuple 
})

Definition: Active Tuple Template

Signals and Signal Handler

Signals are used as a low-level inter-agent communication. In contrast to tuple, signals can be send directly to specific agents. Although there are remote tuple space operations, signals should be used for remote agent communication. Signals can carry an argument (data). The delivery of signals is only reliable if the source and destination agents are processed on the same platform node. If the destination agent is processed on a remote platform the signals are delivered as messages to the destination node along the travel path of the destination agent.

There is no agent localization, and only agent traces are used to deliver a signal to a remote agent, i.e., each node remembers the direction/link an agent used to migrate to another node. Therefore, remote signals can only be send to agents that were previously processed on the node of the source agent! To enable back propagation of signals, each node remembers the direction/link of incoming signals and its source agent, too. The entries of these trace caches have a timeout and are removed automatically. Each time a signal is propagated along the trace path of an agent, the cache entries of all path nodes are refreshed. After a timeout of a trace cache entry, signals cannot be delivered to an agent along a path using this node!

A signal can be received by an agent by installing a signal handler in the this.on section of the agent class.

The destination agent is specified by the agent identifier. Usually agent identifiers should not made be public for security reasons (An agent at least with privilege level 1 can control another agent on the same node if it knows its agent identifier). Hence, signals are often used between parent-child agents. Each child knows the agent identifier of its parent, and vice versa.

Signals should carry only simple arguments. Objects may not contain cyclic references. Complex data structures should only be exchanged between agents by using the tuple space.

Types and Templates

// Type definitions
type aid = string
type range = hops:number|region:{dx:number,dy:number,..}

// Template
this.child=none;
this.act = {
  a1: function () {
    // Create child agent
    this.child=fork({child:none});
  }
  a2: function () {
    // Raise signal
    if (this.child) send(this.child,'PARENT',me());
  }
}
// Installation of signal handlers
this.on : {
  'PARENT' : function (arg) {
    log('Got signal from my parent '+arg);
  }, ..
}

send^1,2,3^

function (to:aid,sig:string|number,arg?:*)

Send a signal @sig (string or number) to an agent with identification string @to with an optional argument @arg.

broadcast^1,2,3^

fucntion (class:string,range,@sig,@arg?)

Broadcasts a signal to multiple agents of class @class with the specified range.

sendto^1,2,3^

function (to:dir,sig:string|number,arg?:*)

Send a signal @sig (string or number) to a remote node specified by @to with an optional argument @arg. If there is an agent on the remote node handling the specific signal it will be passed to the listening agent.

sleep

function (tmo:number)

Suspend agent for a specific time. If @tmo is zero, the agent is suspended until it will be woken up by another agent using the wakeup operation.

wakeup

function (aid?:string)

Wake up a sleeping agent. Can be called from within an signal handler. If @aid is undefined, the agent calling wakeup will be woken up (if suspended).

timer.add

function (tmo:number,sig:string,arg:*,repeat:boolean) → string

Add and start a new timer that raises the signal sig after timeout. Returns a timer identifier.

timer.delete

function (sig:string)

Deletes a timer referenced by the identifier returned from timer.add.

Agent Control

Agents can be instantiated from an agent class template (previously loaded into the platform) by using the create operation with parameter initialization. Agent class parameters must be passed immediately to agent body variables. They are not accessible during run-time!The agent class ac must be loaded previously as an agent class template and is provided by the platform. Alternatively, the agent class can be a sub-class of the current agent.

Furthermore, agents can be forked from the current agent process inheriting the entire data and control state including the current agent behaviour (activities, transitions, ..). Specific body variables of the forked agent can be overridden by the attributes of the settings object passed on the fork call. Forking discards all current scheduling blocks, in contrast to migration!

A newly created agent is identified by a (node) unique identifier string (commonly 8 characters) that is returned by the create and fork operations.

At least privilege level 1 is required to use these operations.

create^1,2,3^

function (ac:string,[arg1,arg2,..],level?:number) → aid

function (ac:string,{arg1:*,arg2:*,..},level?:number) → aid

Creates a new agent from agent class ac with the given set of arguments. Agent class arguments are passed to agent class parameters during the creation or forking process. Arguments can either be passed in an array matching parameters in the order they are defined, or by using an argument object with arbitrary parameter order. Optionally the privilege level of the new agent can be specified, otherwise the new agent inherits the level of the creating agent. The highest level is limited to the level of the creating agent! The initial activity executed by the newly created agent is specified by the constructor function in the next attribute.

fork^1,2,3^

function (parameters:{var\(~1~\):*,var\(~2~\):*,..},level?:number) → aid

Forks a copy of the current agent process inheriting the entire data and control state of the parent agent. The new child agent can reference its parent agent by the this.parent attribute or by using the myParent function. The child agent body variables var\(~1~\),var\(~2~\),.. passed by the parameters object are overridden on forking with the given values. Note that agent class parameters cannot be accessed after the creation of an aent. The next activity executed after the fork is either computed by the current transition entry or by a next variable override passed with parameter object.

Examples

id = create('explorer',{dir:DIR.NORTH,radius:1});
child = fork({x:10,y:20});
kill(child);

Among the creation and destruction of agents, the agent behaviour can be modified by agents by adding, deleting, or updating of transitions and activities (modification of the ATG). Only whole activities can only be changed and not code parts. There are two objects accessible by agents providing modification operations: act and trans. ATG transformations can be temporarily, e.g., used to create child agents with different or reduced behaviour.

act.add

function (act:string,code:function)

Adds a new activity @act with the given code to the current agent object.

act.delete

function (act:string)

Deletes activity @act from the current agent object.

act.update

function (act:string,code:function)

Updates code of activity @act of the current agent object.

trans.add

function (trans0:string,code:function|string)

Adds a new transition starting from activity @trans0 with the given code to the current agent object.

trans.delete

function (trans0:string)

Deletes a transition from activity @trans0 from the current agent object.

trans.update

function (trans0:string,code:function|string)

Updates code of transition starting from activity @trans0 of the current agent object.

Examples

this.act = {
  a1: function () {..},
  a2: function () {
    act.delete(a1); trans.delete(a1);
    act.add('b1',function () { this.sensor=[]; .. });
    trans.update(a2,function () { return this.sensor.length>0?b1:a3 });
  },
  a3: ..
  ..
};
this.trans = {
  a1: a2,
  a2: a3,
  a3: ..
}

Agent Process Control Flow

The main control flow of and agent is related to the ATG and (conditional) transitions itself. An agent can call blocking statements within an activity. A blocked activity stops agent execution until an event occurs. But signal handlers can be still executed even the agent is in a blocked state. Among external suspend-wakeup control, the agent itself can suspend and resume its execution explicitly by the following operations. Blocking statements may only occur at the end of an activity (or at least there may be only one blocking statement in one activity).

sleep

function (millisec?:number)

Suspend agent execution (current activity) for a specific amount of time (milli seconds resolution) or until a wakeup operation (from within a signal handler) is executed.

wakeup

function (process?)

Wake up a sleeping (suspended) agent process.

Agent Mobility

Agent processes can migrate to another node (either physical or logical) by transferring its current control and data snapshot via a message over a transport channel. The destination (specified by the transport channel) is selected by a direction DIR. If the moveto operation is executed at the end of an activity or the current scheduling block is empty after migration, the next activity is computed after migration on the new JAM node.

If a migration to a specific host or in a specific direction is not possible, a MOVE exception is thrown.

Types

enum DIR = {
  NORTH , SOUTH , WEST , EAST ,
  LEFT , RIGHT , UP , DOWN,
  ORIGIN ,
  NW , NE , SW , SE ,            
  PATH (path:string) -> {tag='DIR.PATH',path:string} ,
  IP   (ip:string) -> {tag='DIR.IP',ip:string} ,            
  NODE (node:string) -> {tag='DIR.NODE',node:string} ,            
  CAP  (cap:string|capability) -> {tag='DIR.CAP',cap:string|capability}
} : dir

Operations

moveto

function (to:dir)

Migrates current agent to a new node specified by the destination @to.

opposite

function (dir) → dir

Returns the opposite (back) direction (if any) of the given direction. E.g., opposite of NORTH is SOUTH. In the case of IP links and migration the opposite operation can return the IP address or the node name of the last node, i.e., opposite(DIR.IP()) and opposite(DIR.NODE()), respectively.

link

function (dir) → boolean|string|[]

Test a link direction. Should be used prior to migration (migration with not available link direction causes an exception). In the case of multi-cast links (e.g., IP), a list of connected/reachable IPs (routes, using pattern IP('*')) or Nodes (using pattern IP('%')) is returned.

Examples

// Activity in agent class template
move : function () {
  if (this.verbose>0) log('Move -> '+this.dir);
  if (!this.goback) this.backdir=opposite(this.dir);
  switch (this.dir) {
    case DIR.NORTH: this.delta.y--; break;
    case DIR.SOUTH: this.delta.y++; break;
    case DIR.WEST:  this.delta.x--; break;
    case DIR.EAST:  this.delta.x++; break;
  }
  if (this.dir!=DIR.ORIGIN && link(this.dir)) {
    this.hop++;
    moveto(this.dir);    
  }
}

The possible migration directions depend on the network ports available on the agent's current node and established links between nodes. IP (UDP/HTTP) links can be established between generic not directional (multicast) IP ports (DIR.IP("ip:ipport")) or between directional (unicast) ports, e.g., DIR.NORTH("ip:ipport")), commonly connected to a South DIR.SOUTH("ip:ipport")) port on the remote endpoint. Generic IP ports can spawn arbitrary mesh grids. Alternatively, a destination node can be specified, i.e., DIR.NODE(nodeid).

After an agent migration, the agent can retrieve its backpropagation direction, i.e., last node identifier or IP address by using the opposite(DIR.NODE()) and opposite(DIR.IP()) operations, respectively.

function mi(dest){
  this.src=null;
  this.dest=dest;
  this.act={
    init:function ()    { 
     log('Starting on '+myNode())},
    goto: function ()   { 
     log('Going to '+DIR.print(this.dest)); 
     if (link(this.dest)) moveto(this.dest); else log('No route')},
    goback: function () { 
     this.src=opposite(DIR.NODE());
     log('Going back to '+DIR.print(this.src)); moveto(this.src)},
    end: function ()    { 
     log('End'); kill() }
  }
  this.trans={
    init:goto, goto:goback, goback:end
  }
  this.next=init
}

Example: Agent forward and backward migration between two nodes

Security

Changing of agent privilege levels and roles requires secured capabilities. Furthermore, agents can use capability protection to ensure authentication and authorization of operations.

Capability

type port = string[6]
type privat = {
    prv_obj : number[0..65535],
    prv_rights : number[0..255],
    prv_rand : port
}
type capability = {
    cap_port: port,
    cap_prv: privat
}

Operations

The following capability and security functions are available.

Port

function (port_vals: numner []) → port

Creates a port (if port_vals is undefined a null port is returned).

Port.toString

function (port) → string

Returns a string representation of a port (XX&XX;XX&XX;XX)

Port.ofString

function (string) → port

Returns a port from a string representation (XX&XX;XX&XX;XX)

Port.unique

function () → port

Returns a fresh unique port from a random generator.

Private

function (obj:number,rights:number,rand:port) → privat

Creates a private object (if obj is undefined a null private object is returned).

Private.toString

function (privat) → string

Returns a string representation of a private object (obj(rights)[XX&XX;XX&XX;XX])

Private.ofString

function (string) → privat

Returns a private object from a string representation (obj(rights)[XX&XX;XX&XX;XX])

Capability

function (port, privat) → capability

Creates a capability object (if port is undefined a null capability object is returned).

Capability.toString

function (capability) → string

Returns a string representation of a capability object ([XX&XX;XX&XX;XX:XX]obj(rights)[XX&XX;XX&XX;XX])

Capability.ofString

function (string) → capability

Returns a capability object from a string representation ([XX&XX;XX&XX;XX:XX]obj(rights)[XX&XX;XX&XX;XX])

Ad-hoc Connectivity

connectTo^3^

function connectTo(dir:dir,@options)

Connects this node to another node using a virtual or physical channel link. Common ports are non-directed multi-cast IP ports. E.g., for connecting a node IP port to another IP port of a remote agent platform, the direction argument is DIR.IP("<ipaddr>:<ipport>") or by using the remote node name DIR.NODE(<nodename>). Directional ports (supporting uni-cast P2P links only) like DIR.NORTH can be connected to another directional port by using the geometric opposite direction (in this example using DIR.SOUTH as destination). A different situation occurs if a directional port is established by IP communication (with an IP address and unique IP port). In this case the source port has to be specified (!) with the destination IP as an argument, e.g., DIR.NORTH("<ipaddr>:<ipport>").

Scheduling Blocks

There are many operations that can block (suspend) the agent processing. But the JavaScript programming model does not support code blocking. For this reason, blocking AgentJS/AIOS statement (e.g., sleep, inp, ..) have to be placed at the end of an activity that is the only scheduling point. And there may be only one blocking activity. To support scheduling of a sequence of blocking statements, a scheduling block can be defined within an agent activity (but not within a transition that may not block).

B

function(block:function [])

Defines a scheduling block that is executed after the current activity defining the block has terminated. Each element of the function array is treated as an anonymous (sub-)activity and may contain a blocking statement.

I

function (object,next:function,block: function [],finalize:function)

Iterates over object or array and applies the function block to each element.

L

function (init:function,cond:function,next:function,block: function ]})

Loop block iteration with initialization, conditional, and next computation function.

SQL Operations^3^

Level 3 (stationary) agents can access or create SQLite data bases. Requires a native sqlite3 plug-in (embedded already in jx+, node.js requires loading of an external native module).

db.Database

function (filepath:string,options?:{mode:"r"|"r+"|"w+"}) → sqldb

Creates a new data base or opens an existing from a file. A volatile data base can be created in memory by specifying a &memory; file path.

sqldb.createMatrix

function (matname:string, header:string|number|boolean [], callback?:function) → boolean

Creates a new numeric matrix in the data base. The header argument provides the type interface for all rows.

sqldb.createTable

function (tblname:string,header:{},callback?:function) → boolean

Creates a new data table in the data base. The header object specifies the column names.

sqldb.init

function ()

Initialize the SQL data base and start server.

sqldb.insertMatrix

function (mat:string,row:[],callback?:function) → boolean

Insert a new row in an already created matrix

sqldb.insertTable

function (tbl:string,row:[]|{},callback?:function) → boolean

Insert a new row in an already created table

sqldb.readMatrix

function (mat:string,callback?:function) -> [][]|none

Read entire matrix

sqldb.readTable

method (tbl:string,callback?:function) → {}[]|none

Read entire table

Meta Data

Version: 1.1.6
Author: Dr. Stefan Bosse