{"id":16906106,"url":"https://github.com/timkam/js-son","last_synced_at":"2025-04-10T02:19:40.296Z","repository":{"id":34110039,"uuid":"168965952","full_name":"TimKam/JS-son","owner":"TimKam","description":"Light-weight reasoning-loop agent library for JavaScript","archived":false,"fork":false,"pushed_at":"2025-02-19T15:43:51.000Z","size":11155,"stargazers_count":40,"open_issues_count":49,"forks_count":10,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-03T01:08:17.373Z","etag":null,"topics":["multi-agent-systems","multiagent-systems"],"latest_commit_sha":null,"homepage":"https://arxiv.org/pdf/2003.04690.pdf","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-2-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TimKam.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-03T16:28:04.000Z","updated_at":"2025-02-19T15:43:56.000Z","dependencies_parsed_at":"2024-01-17T05:38:06.126Z","dependency_job_id":"d0a79fe1-a9bf-4332-b022-960e2648f039","html_url":"https://github.com/TimKam/JS-son","commit_stats":{"total_commits":156,"total_committers":3,"mean_commits":52.0,"dds":0.3846153846153846,"last_synced_commit":"b312844ee184fae41ef17d846d7fa71040896164"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimKam%2FJS-son","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimKam%2FJS-son/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimKam%2FJS-son/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TimKam%2FJS-son/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TimKam","download_url":"https://codeload.github.com/TimKam/JS-son/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248142915,"owners_count":21054672,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["multi-agent-systems","multiagent-systems"],"created_at":"2024-10-13T18:41:03.540Z","updated_at":"2025-04-10T02:19:40.266Z","avatar_url":"https://github.com/TimKam.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JS-son - a Lean, Extensible JavaScript Agent Programming Library\n\n![Tests](https://github.com/TimKam/JS-son/actions/workflows/test.yml/badge.svg)\n![Docs](https://readthedocs.org/projects/js-son/badge/?version=latest)\n\n``JS-son`` is a lean and extensible JavaScript library for programming agents.\nIt has a focus on reasoning loops (agent-internals), and supports the belief-desire-intention approach, among others.\nInstall it with:\n\n```\nnpm install js-son-agent\n```\n\n## Belief-Desire-Intention (BDI) Agents\n*JS-son* follows the belief-desire-intention(-plan) (BDI) approach; a popular model for developing intelligent agents.\nHowever, it is also possible to implement agents that follow simpler reasoning-loop approaches.\nFor example, in its simplest form, *JS-son* agents can follow a *belief-plan* approach, that means based on their perception of their environment and their own internal state, the execution of plans--which act on the environment and update the agent's own beliefs--is determined.\n\nIn this section, we explain how *JS-son* agents make use of the BDI (and plan) concepts and how the *Environment* object type processes agent actions.\nFor detailed documentation of the corresponding *JS-son* object types and functions, generate the JSDoc (see below).\n\n**Agent**:\n\n* **Beliefs**: Beliefs specify what the agent beliefs about the state of its environment, as well as about is own state. Each belief has a unique ID.\n\n* **Desires**: In *JS-son* desires are functions (each with a unique ID) that determine, based on the agent's beliefs, what the agents *desires* to realize, that means what it would *ideally see realized*.\n\n* **Intentions**: Intentions specify which desires an agent intends to realize, that means what it *in fact wants to work towards realizing*. An agent needs to specify a preference function that derives intentions from desires.\n\n* **Plans**: A plan consists of a *head* and a *body*. The head specifies the intent that needs to be active (be ``true`` or have any other specified value) for the plan to be executed. The *body* determines who the agent fulfills the desire by changing specific beliefs and \"executing\" actions (i.e., handing over actions to the environment).\n\n*JS-son* also supports a simpler belief-plan model: i.e., in a plan's head, it is possible to specify a function that determines whether a plan should be executed based on the agent's current beliefs.\n\n**Environment**:\n\nThe environment provides the agents' \"perceptors\" with belief updates and processes the agents' actions to determine the actions' impact on the environment's state.\n\n\n## Requirements \u0026 Installation\nInstalling *JS-son* requires [npm](https://nodejs.org/) or [yarn](https://yarnpkg.com).\n\nTo install *JS-son*, run:\n\n```\nnpm install js-son-agent\n```\n\nor:\n\n```\nyarn add js-son-agent\n```\n\n## Dependencies\n*JS-son* does not have any dependencies! This means you can require it in your application without worrying about bloat or unstable/insecure upstream modules.\nOnly when you want to work on the *JS-son* code base, you should install some *dev dependencies* for linting and testing.\nNote that the JS-son examples use dependencies as well, e.g. for UI-level abstractions, but these dependencies are not installed when requiring JS-son in a project.\n\n## Tutorials\nTo illustrate how *JS-son* works, we first present two basic tutorials.\nIn the first one, we use the simplified belief-plan approach; the second tutorial presents the full belief-desire-intention-plan approach.\nYou find the source code of these tutorials at [https://github.com/TimKam/JS-son/tree/master/examples/node](https://github.com/TimKam/JS-son/tree/master/examples/node).\nIn addition, we provide a set of advanced tutorials that show how *JS-son* can be applied in different contexts: in web apps, Jupyter notebooks, grid worlds, and *serverless* (Function-as-a-Service) environments.\n\n### Belief-Plan Approach\nIn this tutorial, we use basic belief-plan approach to implement the [Jason _room_ example](https://github.com/jason-lang/jason/tree/master/examples/room) with *JS-son*.\n\nIn the example, three agents are in a room:\n\n1. A porter, that locks and unlocks the room's door if requested.\n\n2. A paranoid agent, that prefers the door to be locked and asks the porter to lock the door if this\n   is not the case.\n\n3. A claustrophobe agent, that prefers the door to be unlocked and asks the porter to unlock the\n   door if this is not the case.\n\nThe simulation runs twenty iterations of the scenario. In an iteration, each agent acts once.\n\nFirst, we import *JS-son* and assign Belief, Plan, Agent, and Environment to separate ``consts`` for the sake of convenience:\n\n```JavaScript\nconst JSson = require('js-son-agent')\n\nconst Belief = JSson.Belief\nconst Plan = JSson.Plan\nconst Agent = JSson.Agent\nconst Environment = JSson.Environment\n```\n\nAll agents start with the same belief set.\nThe belief with the ID ``door`` is assigned the object ``{ locked: true}``.\nI.e., the door is locked.\nAlso, nobody has so far requested any change in door state (``requests: []``).\n\n```JavaScript\nconst beliefs = {\n  ...Belief('door', { locked: true }),\n  ...Belief('requests', [])\n}\n```\n\nFirst, we define the porter agent.\nThe porter has the following plans:\n\n1. If it does not believe the door is locked and it has received a request to lock the door (head), lock the door (body).\n\n2. If it believes the door is locked and it has received a request to unlock the door (head), unlock the door (body).\n\n```JavaScript\nconst plansPorter = [\n  Plan(\n    beliefs =\u003e !beliefs.door.locked \u0026\u0026 beliefs.requests.includes('lock'),\n    () =\u003e [{ door: 'lock' }]\n  ),\n  Plan(\n    beliefs =\u003e beliefs.door.locked \u0026\u0026 beliefs.requests.includes('unlock'),\n    () =\u003e [{ door: 'unlock' }]\n  )\n]\n```\n\nNote that an agent can update its own beliefs (and also plans or any other property it has) in a **body** of its plans (not in a plan's *head*).\nFor this, simply re-assign the corresponding property, for example as follows:\n\n```JavaScript\nPlan(\n    beliefs =\u003e beliefs.door.locked \u0026\u0026 beliefs.requests.includes('unlock'),\n    function () {\n      this.beliefs.door.locked = false\n      return [{ door: 'unlock' }]\n    }\n  )\n```\nNote that it is necessary to use the ``function`` keyword so that *JS-son* can set the scope of the plan body correctly.\nThe feature can be deactivated for an agent by instantiating it with the ``selfUpdatesPossible`` parameter set to ``false``.\n\nWe instantiate a new agent with the belief set and plans.\nBecause we are not making use of  *desires* in this simple belief-plan scenario, we pass an empty object as the agent's desires:\n\n```JavaScript\nconst porter = new Agent('porter', beliefs, {}, plansPorter)\n```\n\nNote that alternatively, we can use a single configuration object to instantiate the agent:\n\n```JavaScript\nconst porter = new Agent({\n  id: 'porter',\n  beliefs,\n  plans: plansPorter\n})\n```\n\nNext, we create the paranoid agent with the following plans:\n\n1. If it does not belief the door is locked (head), it requests the door to be locked (body).\n\n2. If it beliefs the door is locked (head), it broadcasts a thank you message for locking the door (body).\n\n```JavaScript\nconst plansParanoid = [\n  Plan(\n    beliefs =\u003e !beliefs.door.locked,\n    () =\u003e [{ request: 'lock' }]\n  ),\n  Plan(\n    beliefs =\u003e beliefs.door.locked,\n    () =\u003e [{ announce: 'Thanks for locking the door!' }]\n  )\n]\n\nconst paranoid = new Agent('paranoid', beliefs, {}, plansParanoid)\n```\n\nThe last agent we create is the *claustrophobe*. It has these plans:\n\n1. If it beliefs the door the door is locked (head), it requests the door to be unlocked (body).\n\n2. If it does not belief the door is locked (head), it broadcasts a thank you message for unlocking the door (body).\n\n```JavaScript\nconst plansClaustrophobe = [\n  Plan(\n    beliefs =\u003e beliefs.door.locked,\n    () =\u003e [{ request: 'unlock' }]\n  ),\n  Plan(\n    beliefs =\u003e !beliefs.door.locked,\n    () =\u003e [{ announce: 'Thanks for unlocking the door!' }]\n  )\n]\n\nconst claustrophobe = new Agent('claustrophobe', beliefs, {}, plansClaustrophobe)\n```\n\nNow, as we have the agents defined, we need to specify the environment.\nFirst, we set the environments state, which is--in our case--consistent with the agents' beliefs:\n\n```JavaScript\nconst state = {\n  door: { locked: true },\n  requests: []\n}\n```\n\nTo define how the environment processes agent actions, we implement the ``updateState`` function.\nThe function takes an agent's actions, as well as the agent ID and the current state to determine the environment's state update that is merged into the new state ``state = { ...state, ...stateUpdate }``.\n\n```JavaScript\nconst updateState = (actions, agentId, currentState) =\u003e {\n  const stateUpdate = {\n    requests: currentState.requests\n  }\n  actions.forEach(action =\u003e {\n    if (action.some(action =\u003e action.door === 'lock')) {\n      stateUpdate.door = { locked: true }\n      stateUpdate.requests = []\n      console.log(`${agentId}: Lock door`)\n    }\n    if (action.some(action =\u003e action.door === 'unlock')) {\n      stateUpdate.door = { locked: false }\n      stateUpdate.requests = []\n      console.log(`${agentId}: Unlock door`)\n    }\n    if (action.some(action =\u003e action.request === 'lock')) {\n      stateUpdate.requests.push('lock')\n      console.log(`${agentId}: Request: lock door`)\n    }\n    if (action.some(action =\u003e action.request === 'unlock')) {\n      stateUpdate.requests.push('unlock')\n      console.log(`${agentId}: Request: unlock door`)\n    }\n    if (action.some(action =\u003e action.announce)) {\n      console.log(`${agentId}: ${action.find(action =\u003e action.announce).announce}`)\n    }\n  })\n  return stateUpdate\n}\n```\n\nTo simulate a partially observable world, we can specify the environment's ``stateFilter`` function, which determines how the state update should be shared with the agents.\nHowever, in our case we simply communicate the whole state update to all agents, which is also the default behavior of the environment, if no ``stateFilter`` function is specified.\n\n```JavaScript\nconst stateFilter = state =\u003e state\n```\n\nWe instantiate the environment with the specified agents, state, update function, and filter function:\n\n```JavaScript\nconst environment = new Environment(\n  [paranoid, claustrophobe, porter],\n  state,\n  updateState,\n  stateFilter\n)\n```\n\nFinally, we run 20 iterations of the scenario:\n\n```JavaScript\nenvironment.run(20)\n```\n\n### Goal-based Approach\nJS-son supports an alternative goal-based reasoning loop. Here, we show a minimal working example of an agent that employs this approach.\nOur agent has merely one goal:\n\n```JavaScript\nconst goals = {\n  praiseDog: Goal('praiseDog', false, { dogName: 'Hasso' })\n}\n```\n\nThe goal has the ID ``praiseDog``, is ``false`` (when starting) and has a ``value`` object with the property ``dogname``, which is ``Hasso``.\nThe agent starts with the belief that Hasso has not been a nice dog:\n\n```JavaScript\nconst beliefs = {\n  ...Belief('dogNice', false)\n}\n```\n\nThe agent's *goal revision function*  takes the agent's current beliefs and goals and returns a revised goal object (that can feature new goals, revised goals, and/or have previously existing goals removed):\n\n```JavaScript\nconst reviseGoals = (beliefs, goals) =\u003e {\n  if (beliefs.dogNice) {\n    goals.praiseDog.isActive = true\n  }\n  return goals\n}\n```\n\nOur agent has only one plan, which is attached to the ``praiseDog`` goal, *i.e.*, if the goal is active, the plan's *body* function is executed (the agent praises the dog):\n\n```JavaScript\nconst plans = [ Plan(goals.praiseDog, (beliefs, goalValue) =\u003e ({ action: `Good dog, ${goalValue.dogName}!` })) ]\n```\nNote that the value of a plan's goal can be accessed in the body of the plan.\nBased on the goals, beliefs, goal revision function, and plans, we instantiate the agent:\n\n```JavaScript\nconst newAgent = new Agent({\n  id: 'MyAgent',\n  beliefs,\n  goals,\n  plans,\n  reviseGoals\n})\n```\n\nFinally, we run the agent's reasoning loop for one iteration, and provide an updated belief update the dog's niceness:\n\n```JavaScript\nnewAgent.next({ ...Belief('dogNice', true) }\n```\nNote that this activates the ``praiseDog`` goal and hence triggers the execution of the agent's only plan.\n\n### Belief-Desire-Intention-Plan Approach\nIn this tutorial, we implement a simple information spread simulation, using JS-son's full belief-desire-intention-plan approach.\nWe simulate the spread of a single boolean belief among 100 agents.\nThe belief spread is simulated as follows:\n\n* The scenario starts with each agent announcing their beliefs.\n\n* In each iteration, the environment distributes two belief announcements to each agent. Based on these beliefs and possibly (depending on the agent type) the past announcements the agent was exposed to, each agent *announces* a new belief: either ``true`` or ``false``.\n\nThe agents are of two different agent types (``volatile`` and ``introspective``):\n\n1. Type ``volatile``: Volatile agents only consider their current belief and the latest belief set they received from the environment when deciding which belief to announce. Volatile agents are \"louder\", i.e. the environment is more likely to spread beliefs of volatile agents. We also add bias to the announcement spread function to favor ``true`` announcements.\n\n2. Type ``introspective``: In contrast to volatile agents, introspective agents consider the past five belief sets they have received, when deciding which belief they should announce. Introspective agents are \"less loud\", i.e. the environment is less likely to spread beliefs of volatile agents.\n\nThe agent type distribution is 50, 50.\nHowever, 30 volatile and 20 introspective agents start with ``true`` as their belief, whereas 20 volatile and 30 introspective agents start with ``false`` as\ntheir belief.\n\n\nFirst, we import the JS-son dependencies:\n\n```JavaScript\nconst {\n  Belief,\n  Desire,\n  Intentions, // eslint-disable-line no-unused-vars\n  Plan,\n  Agent,\n  Environment\n} = require('js-son-agent')\n```\n\nThen, we create the belief sets the agents start with:\n\n```JavaScript\nconst beliefsTrue = {\n  ...Belief('keyBelief', true),\n  ...Belief('pastReceivedAnnouncements', [])\n}\n\nconst beliefsFalse = {\n  ...Belief('keyBelief', false),\n  ...Belief('pastReceivedAnnouncements', [])\n}\n```\n\nNow, we define the desires of the two agent types.\nBoth agents base their announcement desires on the predominant belief in previous announcements (see the ``determinePredominantBelief`` function).\nHowever, volatile agents only consider the most recent round of announcements, while introspective agents consider the whole history they have available.\nIf both ``true`` and ``false`` occur equally often in the considered announcement history, the currently held belief is considered to reach a decision:\n\n```JavaScript\nconst determinePredominantBelief = beliefs =\u003e {\n  const announcementsTrue = beliefs.pastReceivedAnnouncements.filter(\n    announcement =\u003e announcement\n  ).length\n  const announcementsFalse = beliefs.pastReceivedAnnouncements.filter(\n    announcement =\u003e !announcement\n  ).length\n  const predominantBelief = announcementsTrue \u003e announcementsFalse ||\n    (announcementsTrue === announcementsFalse \u0026\u0026 beliefs.keyBelief)\n  return predominantBelief\n}\n\nconst desiresVolatile = {\n  ...Desire('announceTrue', beliefs =\u003e {\n    const pastReceivedAnnouncements = beliefs.pastReceivedAnnouncements.length \u003e= 5\n      ? beliefs.pastReceivedAnnouncements.slice(-5)\n      : new Array(5).fill(beliefs.keyBelief)\n    const recentBeliefs = {\n      ...beliefs,\n      pastReceivedAnnouncements\n    }\n    return determinePredominantBelief(recentBeliefs)\n  }),\n  ...Desire('announceFalse', beliefs =\u003e {\n    const pastReceivedAnnouncements = beliefs.pastReceivedAnnouncements.length \u003e= 5\n      ? beliefs.pastReceivedAnnouncements.slice(-5)\n      : new Array(5).fill(beliefs.keyBelief)\n    const recentBeliefs = {\n      ...beliefs,\n      pastReceivedAnnouncements\n    }\n    return !determinePredominantBelief(recentBeliefs)\n  })\n}\n\nconst desiresIntrospective = {\n  ...Desire('announceTrue', beliefs =\u003e determinePredominantBelief(beliefs)),\n  ...Desire('announceFalse', beliefs =\u003e !determinePredominantBelief(beliefs))\n}\n```\n\nThe agents desires are mutually exclusive.\nHence, the agents' intentions merely relay their desires, which is reflected by the default preference function generator.\n\nThe agents' plans are to disseminate the announcement (``true`` or ``false``) as determined by the desire functions:\n\n```JavaScript\nconst plans = [\n  Plan(intentions =\u003e intentions.announceTrue, () =\u003e [ { announce: true } ]),\n  Plan(intentions =\u003e intentions.announceFalse, () =\u003e [ { announce: false } ])\n]\n```\n\nBefore we instantiate the agents, we need to create an object for the environment's initial state.\nThe object will be populated when the agents will be created:\n\n```JavaScript\nconst state = {}\n```\n\nTo instantiate the agents according to the scenario specification, we create the following function:\n\n```JavaScript\nconst createAgents = () =\u003e {\n  const agents = new Array(100).fill({}).map((_, index) =\u003e {\n    // assign agent types--introspective and volatile--to odd and even numbers, respectively:\n    const type = index % 2 === 0 ? 'volatile' : 'introspective'\n    const desires = type === 'volatile' ? desiresVolatile : desiresIntrospective\n    /* ``true`` as belief: 30 volatile and 20 introspective agents\n       ``false`` as belief: 20 volatile and 30 introspective agents:\n    */\n    const beliefs = (index \u003c 50 \u0026\u0026 index % 2 === 0) || (index \u003c 40 \u0026\u0026 index % 2 !== 0) ? beliefsTrue\n      : beliefsFalse\n    // add agent belief to the environment's state:\n    state[`${type}${index}`] = { keyBelief: beliefs.keyBelief }\n    // create agent:\n    return new Agent(\n      `${type}${index}`,\n      { ...beliefs, ...Belief('type', type) },\n      desires,\n      plans\n    )\n  })\n  const numberBeliefsTrue = Object.keys(state).filter(\n    agentId =\u003e state[agentId].keyBelief\n  ).length\n  const numberBeliefsFalse = Object.keys(state).filter(\n    agentId =\u003e !state[agentId].keyBelief\n  ).length\n  console.log(`True: ${numberBeliefsTrue}; False: ${numberBeliefsFalse}`)\n  return agents\n}\n```\n\nTo define how the environment processes agent actions, we implement the ``updateState`` function.\nThe function takes an agent's actions, as well as the agent ID and the current state to determine the environment's state update that is merged into the new state\n``state = { ...state, ...stateUpdate }``:\n\n```JavaScript\nconst updateState = (actions, agentId, currentState) =\u003e {\n  const stateUpdate = {}\n  actions.forEach(action =\u003e {\n    stateUpdate[agentId] = {\n      keyBelief: action.find(action =\u003e action.announce !== undefined).announce\n    }\n  })\n  return stateUpdate\n}\n```\n\nWe simulate a partially observable world: via the environment's ``stateFilter`` function, we determine an array of five belief announcements that should be made available to an agent.\nAs described in the specification, announcements of volatile agents will be \"amplified\": i.e. the function pseudo-randomly picks 3 announcements of volatile agents and 2 announcements of introspective agents.\nIn addition, we add bias that facilitates ``true`` announcements:\n\n```JavaScript\nconst stateFilter = (state, agentKey, agentBeliefs) =\u003e {\n  const volatileAnnouncements = []\n  const introspectiveAnnouncements = []\n  Object.keys(state).forEach(key =\u003e {\n    if (key.includes('volatile')) {\n      volatileAnnouncements.push(state[key].keyBelief)\n    } else {\n      introspectiveAnnouncements.push(state[key].keyBelief)\n    }\n  })\n  const recentVolatileAnnouncements = volatileAnnouncements.sort(\n    () =\u003e 0.5 - Math.random()\n  ).slice(0, 3)\n  const recentIntrospectiveAnnouncements = introspectiveAnnouncements.sort(\n    () =\u003e 0.5 - Math.random()\n  ).slice(0, 2)\n  // add some noise\n  let noise = Object.keys(state).filter(agentId =\u003e state[agentId].keyBelief).length \u003c 50 * Math.random() ? [true] : []\n  noise = Object.keys(state).filter(agentId =\u003e state[agentId].keyBelief).length \u003c 29 * Math.random() ? [false] : noise\n  // combine announcements\n  const pastReceivedAnnouncements =\n    recentVolatileAnnouncements.concat(\n      recentIntrospectiveAnnouncements, agentBeliefs.pastReceivedAnnouncements, noise\n    )\n  return { pastReceivedAnnouncements, keyBelief: state[agentKey].keyBelief }\n}\n```\n\nThe last function we need is ``render()``.\nIn our case, we simply log the number of announcements of ``true`` and ``false`` to the console:\n\n```JavaScript\nconst render = state =\u003e {\n  const numberBeliefsTrue = Object.keys(state).filter(\n    agentId =\u003e state[agentId].keyBelief\n  ).length\n  const numberBeliefsFalse = Object.keys(state).filter(\n    agentId =\u003e !state[agentId].keyBelief\n  ).length\n  console.log(`True: ${numberBeliefsTrue}; False: ${numberBeliefsFalse}`)\n}\n```\n\nWe instantiate the environment with the specified agents, state, update function, render function, and filter function:\n\n```JavaScript\nconst environment = new Environment(\n  createAgents(),\n  state,\n  updateState,\n  render,\n  stateFilter\n)\n```\n\nFinally, we run 50 iterations of the scenario:\n\n```JavaScript\nenvironment.run(50)\n```\n\n## Belief Revision\nBy default, JS-son agents get their belief update from the environment and revise their existing\nbeliefs as follows:\n\n```JavaScript\nbeliefs = {\n  ...oldBeliefs,\n  ...newBeliefs\n}\n```\nHere, ``oldBeliefs`` are the agent's existing beliefs, whereas ``newBeliefs`` are the belief updates the agent receives; *i.e.*, the agent always accepts the belief update.\nHowever, JS-son supports the implementation of a custom belief revision function that allows agents to (partially or fully) reject belief updates received from their environment, to post-process beliefs in any other manner, or to acquire additional beliefs on their own.\nFor example, let us implement the following simple agent:\n\n```JavaScript\nconst agent = new Agent('myAgent', { ...Belief('a', true) }, {}, [])\n```\n\nNow, let us run the agent so that the environment changes the agent's belief about ``a``.\n\n```JavaScript\nagent.next({ ...Belief('a', false) })\n```\n\n``agent.beliefs.a`` is ``false``.\n\nWe can implement a custom belief revision function that guarantees that the belief about ``a`` must not be overwritten:\n\n```JavaScript\nconst (oldBeliefs, newBeliefs) =\u003e ({\n  ...oldBeliefs,\n  ...newBeliefs,\n  a: true\n})\nconst agent = new Agent('myAgent', { ...Belief('a', true) }, {}, [], undefined, false, reviseBeliefs)\n```\n\nTo test the change, proceed as follows:\n\n```JavaScript\nagent.next({ ...Belief('a', false) })\n```\n\n``agent.beliefs.a`` is ``true``.\n\nJS-son provides an out-of-the-box belief revision function that handles priority rules.\nWe can import this function as follows:\n\n```JavaScript\nconst revisePriority = JSson.revisionFunctions.revisePriority\n```\n\nLet us now specify an initial belief base and an update thereof.\nIn both, each belief has a numerical priority value:\n\n```JavaScript\nconst beliefBase = {\n  isRaining: Belief('isRaining', true, 0),\n  temperature: Belief('temperature', 10, 0),\n  propertyValue: Belief('propertyValue', 500000, 1)\n}\n\nconst update = {\n  isRaining: Belief('isRaining', false, 0),\n  temperature: Belief('temperature', 15, 1),\n  propertyValue: Belief('propertyValue', 250000, 0)\n}\n\nconst agent = new Agent('myAgent', beliefBase, {}, [], undefined, false, revisePriority)\n```\n\nAfter applying the belief update with `agent.next(update)`, our agent's belief base is as follows:\n\n```JavaScript\n{\n  isRaining: Belief('isRaining', false, 0),\n  temperature: Belief('temperature', 15, 1),\n  propertyValue: Belief('propertyValue', 500000, 1)\n}\n```\n\nNote that in detail, the priorities are interpreted as follows:\n\n  * If a belief exists in the update, but not in the agent's belief base, this belief is added.\n  * If the belief's priority is 0 in the belief base and a belief with the same key exists in the update, the agent's belief is overridden; this behavior is desired for beliefs that are generally defeasible.\n  * If a belief's priority in the update is higher than the same belief's priority in the agent's belief base, the agent's belief is overridden.\n\nA potential issue that the belief revision function we use above does not address is that it essentially requires the *inflation* of priorities in case of regular successful revisions of beliefs with a non-zero priority.\nFor example, in order to update ``the belief \\verb|propertyValue: { value: 500000, priority: 1 }``, a new ``propertyValue`` belief can only defeat the belief if its priority is ``2`` or higher; the subsequent defeater will then require a priority of ``3``, and so on.    \nWe can address this issue by defining whether a particular belief (or beliefs in general) should, when defeated, adopt the priority of their defeater.\n\nWhen using ``JSson.revisionFunctions.revisePriorityStatic`` as our belief revision function, the priority of the initial beliefs are maintained. Alternatively, we can specify whether or not a belief's priority should be updated, on the level of the individual belief:\n\n```JavaScript\nconst beliefBase = {\n      isRaining: Belief('isRaining', true, Infinity, true),\n      temperature: Belief('temperature', 10, Infinity, false)\n    }\n```\n\nWe may want to specify beliefs that are not simply updated as static objects/properties, but dynamically inferred, based on our current belief base or updates thereof.\nTo supports this, JS-son uses the notion of a *functional belief*.\nA functional belief can be specified as follows, for example:\n\n```JavaScript\nconst isSlippery = FunctionalBelief(\n      'isSlippery',\n      false,\n      (oldBeliefs, newBeliefs) =\u003e\n        (newBeliefs.isRaining \u0026\u0026 newBeliefs.isRaining.value) ||\n        (!newBeliefs.isRaining \u0026\u0026 oldBeliefs.isRaining \u0026\u0026 oldBeliefs.isRaining.value),\n      1\n    )\n```\n\nThe arguments of `FunctionalBelief` have the following meaning:\n\n* `isSlippery` (`id`) is the unique identifier of the (functional) belief,\n* `false` (`value`) is the belief's default/initial value;\n* The function:\n  ```JavaScript\n    (oldBeliefs, newBeliefs) =\u003e\n        (newBeliefs.isRaining \u0026\u0026 newBeliefs.isRaining.value) ||\n        (!newBeliefs.isRaining \u0026\u0026 oldBeliefs.isRaining \u0026\u0026 oldBeliefs.isRaining.value)\n  ```\n specifies the rule according to which the belief is inferred -- in this simple example, the value of `isSlippery` takes the value of the new belief `isRaining` unless the belief does not exist, in which it will check for the existing (old) belief `isRaining` and return `false` if neither a new nor an old `isRaining` belief exists.\n* `0` (`order`) is the number used for inducing the order in which the functional belief is revised relative to other functional beliefs: e.g., if another functional belief with order `1` is present, then the latter belief is revised later.\n* `2` (`priority`) is the priority that the belief takes when updating the function as well as the default value, analogous to how orders work for non-functional beliefs.\n\nGiven this functional belief, we can now demonstrate how functional belief revision works:\n\n1. First, we specify our agent:\n\n```JavaScript\n    const newAgent = new Agent({\n      id: 'myAgent',\n      beliefs: { isRaining: Belief('isRaining', true, 0) },\n      desires,\n      plans\n    })\n    newAgent.next(newBeliefs1)\n    expect(newAgent.beliefs.isSlippery.value).toBe(true)\n    newAgent.next(newBeliefs2)\n    expect(newAgent.beliefs.isSlippery.value).toBe(false)\n```\n\n2. Then, we specify the initial belief base and execute the agent's reasoning loop with a belief base update that merely contains the functional belief:  \n\n```JavaScript\n  newAgent.next({ isSlippery})\n```\nBecause ``isRaining`` is not present in the update, our agent infers ``isSlippery`` from its old belief base, i.e., the value of ``isSlippery`` remains ``true``.\n\n3. Finally, we executed the reasoning loop again, with a slightly different belief base update:\n\n```JavaScript\n  newAgent.next({\n      isSlippery,\n      isRaining: Belief('isRaining', false, 0)\n  })\n```\n\nNow, the value of ``isSlippery`` is updated to ``false``, as inferred from the update of ``isRaining``.\n\n## Messaging\nJS-son agents can send \"private\" messages to any other JS-son agent, which the environment will then relay to this agent only.\nAgents can send these messages in the same way they register the execution of an action as the result of a plan.\nFor example, in the plan below, an agent sends the message ``'Hi!'`` to the agent with the ID ``alice``:\n\n```JavaScript\nconst messagePlans = [\n  Plan(_ =\u003e true, () =\u003e ({ messages: [{ message: 'Hi!', agentId: 'alice' }] }))\n]\n```\n\nAssuming that the sending agent has the ID ``bob``, the agent ``alice`` will receive the following belief update:\n\n```JavaScript\nbeliefs = {\n  ...beliefs,\n  messages: {\n    bob: ['Hi!']\n  }\n}\n```\n\nNote that messages do not need to be strings, but can be of any type, for example objects.\n\n## Further Examples\n\n### Data Science\nTo show how *JS-son* can be used with state-of-the art data science tools, we provide a multi-agent simulation example that runs in a Jupyter notebook and integrates with Python data visualization libraries. The simulation compares belief spread among agents in different environments and is based on the [belief-desire-intention(-plan) tutorial](https://github.com/TimKam/JS-son#belief-desire-intention-plan-approach).\n\nYou find the Jupyter notebook in the example folder of the *JS-son* Github repository, as well as [here on Google Colab](https://colab.research.google.com/drive/1_viwoWyOIl8SH61WEAnEpndbR18WHPf3).\n\n**Note**: The interactive widget that is provided as part of the notebook only works with \"full\"/local Jupyter notebook tools and not on Google Colab, as it requires the [ipywidgets library](https://ipywidgets.readthedocs.io/en/stable/), which Google Colab does not support.\n\n### Web Application\nOf course, *JS-son* can also be used in web application development.\nTo illustrate how, we implemented [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway's_Game_of_Life), using *JS-son*'s belief-plan approach.\nWe integrated the Game of Life simulation in a [Framework7](https://framework7.io/) application. The web application runs online at [https://people.cs.umu.se/~tkampik/demos/js-son/](https://people.cs.umu.se/~tkampik/demos/js-son/).\n\nYou find the the source code of the web application [here in the examples directory](https://github.com/TimKam/JS-son/tree/master/examples/web).\nTo run the example, install its dependencies with ``npm install`` and run the application in development (hot-reload) mode with ``npm run dev``.\n\n### Grid World\nBy default, *JS-son* supports grid world environments.\nA comprehensive multi-agent grid world tutorial is provided [here in the examples section](https://github.com/TimKam/JS-son/tree/master/examples/arena/README.md).\n\n### Distributed MAS\n*JS-son* supports distributed multi-agent systems, where the environment interacts with remotely running agents.\nA tutorial on how to implement distributed MAS with *JS-son*, alongside a running example, is available [here](https://github.com/TimKam/JS-son/tree/master/examples/distributed/README.md).\n\n### Serverless\n[This tutorial](https://github.com/TimKam/JS-son/tree/master/examples/serverless/README.md) describes how to run JS-son agents as *serverless* Google Cloud Functions.\n\n## Supported Platforms\n*JS-son* supports recent versions of Firefox, Chrome, and Node.js.\nIt is not tested for other platforms and does not use [Babel](https://babeljs.io/) to facilitate compatibility with legacy platforms.\nContributions that change this are welcome!\n\n## Further Content\n* [Grid world tutorial](./gridworld.md)\n* [Serverless tutorial](./serverless.md)\n* [API reference](https://js-son.readthedocs.io/en/latest/api.html)\n\n## Testing\nThe project uses [Jasime](https://jasmine.github.io/2.0/node.html) for testing.\nRun the tests with ``npm test``.\nThe tests also run on CircleCI.\n\n## Documentation\n*JS-son* is documented with [Sphinx](http://www.sphinx-doc.org/en/master/).\nBuilding the documentation requires Python and CMake.\nInstall the Python dependencies for the documentation with ``pip install -r doc-requirements.txt``.\nGenerate the documentation by navigating to the ``doc`` directory and running ``make html``.\nThe documentation will be placed (as HTML files) to ``doc/_build/html``.\n\n## Contributions\nWe welcome contributions.\nContributors should consider the following conventions:\n\n  * Be nice.\n\n  * Add tests for your code and make sure all tests pass.\n\n  * Add/update JSdoc comments.\n\n  * Ensure ESLint does not show any errors or warnings.\n\n  * Reference relevant issues in commits and branch names.\n\n## Acknowledgements\n**Author**: Timotheus Kampik - [@TimKam](https://github.com/TimKam)\n\n**Agent Architecture Co-Designer**: Juan Carlos Nieves\n\n**Cite as**:\n\n```\n@InProceedings{10.1007/978-3-030-51417-4_11,\nauthor=\"Kampik, Timotheus\nand Nieves, Juan Carlos\",\neditor=\"Dennis, Louise A.\nand Bordini, Rafael H.\nand Lesp{\\'e}rance, Yves\",\ntitle=\"JS-son - A Lean, Extensible JavaScript Agent Programming Library\",\nbooktitle=\"Engineering Multi-Agent Systems\",\nyear=\"2020\",\npublisher=\"Springer International Publishing\",\naddress=\"Cham\",\npages=\"215--234\",\nisbn=\"978-3-030-51417-4\"\n}\n```\n\nThis work was partially supported by the Wallenberg AI, Autonomous Systems and Software Program (WASP) funded by the Knut and Alice Wallenberg Foundation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimkam%2Fjs-son","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftimkam%2Fjs-son","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftimkam%2Fjs-son/lists"}