Skip to main content
Version: 🚧 Alpha 🚧

Developing Species

Most species are written in GAML. A plugin can also define built-in species in Java. Their agents are Java objects, and their attributes and actions are implemented by Java methods. Modelers use them as the parent of their own species. For example, graph_node is defined in Java in gama.core:

species node_agent parent: graph_node edge_species: edge_agent {
bool related_to (node_agent other) {
return self distance_to other < 10;
}
}

Before you write a Java species, check whether a skill is enough. A skill adds attributes and actions to any species (species x skills: [my_skill]) and does not force modelers to choose a parent. Most extensions (moving, network, physics...) use skills. If you only need structured data without agents (no location, no scheduling), use a GAML class instead.

For the plugin set-up, see Developing Plugins. The annotation processor (gama.processor) registers the species in the plugin's generated gaml/ folder (class gaml.additions.<shortcut>.GamlAdditions). Never edit that code by hand.

Defining a species​

A built-in species is a Java class annotated with @species. The processor checks two things:

  • the class must implement gama.api.kernel.agent.IAgent. In practice, extend gama.core.agent.GamlAgent (the class used by the agents of GAML species) or gama.core.agent.MinimalAgent (a lighter base class, used by the species agent);
  • the class must have a public constructor (IPopulation<? extends IAgent> population, int index). The generated code creates the agents with new MyAgent(population, index).

The @species annotation has the following attributes (see @species):

AttributeDefaultUse
name(required)The GAML name of the species.
skills{}The skills that all agents of the species have.
concept{}Keywords from IConcept, used by the documentation.
category{}Categories used by the documentation.
internalfalseHides the species from the documentation.
doc{}The documentation of the species.

In GAML, the parent of a built-in species declared in this way is agent, even if its Java class extends the class of another built-in species. The Java class of a GAML species that inherits from a built-in species (parent: graph_node) is the Java class of the built-in species.

Attributes and actions​

Attributes and actions are declared exactly as in skills:

  • @vars / @variable on the class declare the attributes;
  • @getter and @setter on methods give access to them. Here the methods are called on the agent itself, so they do not need an agent parameter;
  • @action / @arg on methods declare the actions. An action method receives the IScope and reads its arguments from it (for example scope.getArg("other", IType.AGENT));
  • @listener on a method calls it each time an attribute changes.

Attributes and actions declared on Java superclasses and interfaces are also available.

Example: graph_node​

gama.core/src/gama/core/util/graph/AbstractGraphNodeAgent.java defines the species graph_node. It has an attribute (my_graph) and a virtual action (related_to). Modelers must redefine this action in their sub-species, as shown above.

@species (
name = "graph_node",
concept = { IConcept.GRAPH, IConcept.NODE },
doc = @doc ("A base species to use as a parent for species representing agents that are nodes of a graph"))
@vars ({ @variable (
name = IKeyword.MYGRAPH,
type = IType.GRAPH,
doc = @doc ("A reference to the graph containing the agent")) })
public class AbstractGraphNodeAgent extends GamlAgent implements IGraphAgent {

public AbstractGraphNodeAgent(final IPopulation<? extends IAgent> s, final int index) throws GamaRuntimeException {
super(s, index);
}

@action (
doc = @doc ("This operator should never be called"),
name = "related_to",
virtual = true,
args = { @arg (doc = @doc ("The other agent"), name = "other", optional = false, type = IType.AGENT) })
public Boolean relatedTo(final IScope scope) {
DEBUG.LOG("Should never be called !");
return false;
}

@getter (IKeyword.MYGRAPH)
public IGraphEventProvider getGraph() { return (IGraphEventProvider) getTopology().getPlaces(); }
}

A built-in species can also be a simple subclass, only to give a new name to an existing implementation. For example, BaseGraphEdgeAgent (same folder) only adds @species(name = "base_edge").

Built-in species written in Java​

All of them are in the core of GAMA. No extension plugin currently defines a Java species.

SpeciesJava class
agentgama.core/src/gama/core/agent/MinimalAgent.java
(default base class of agents)gama.core/src/gama/core/agent/GamlAgent.java (extends MinimalAgent, no @species)
modelgama.core/src/gama/core/simulation/SimulationAgent.java
experimentgama.core/src/gama/core/experiment/ExperimentAgent.java
platformgama.api/src/gama/api/kernel/PlatformAgent.java
graph_node, graph_edge, base_edgegama.core/src/gama/core/util/graph/

They are listed with their attributes and actions in Built-in species. New experiment types are declared differently, with @experiment on a subclass of ExperimentAgent (e.g. BatchAgent in gama.extension.batch). See the index of annotations.

GAML classes (@gaml_class)​

GAMA 2026 adds class and object to GAML: structured data that are not agents (no location, no shape, no scheduling). A plugin can contribute built-in classes in Java with @gaml_class. The processor checks that:

  • the class is public, top-level or static nested, and implements gama.api.kernel.object.IObject;
  • it has a public constructor (IScope scope, IClass clazz);
  • it is not an agent (agents must use @species);
  • the name is not empty.

The easiest way is to extend gama.core.object.GamlObject (this requires a dependency on gama.core; implementing IObject directly requires only gama.api). Attributes and actions are declared with @vars, @getter, @setter and @action, as for species. Example from the migration guide:

@gaml_class(name = "counter", doc = @doc("A non-agent counter."))
@vars(@variable(name = "value", type = IType.INT, init = "0",
doc = @doc("The current count.")))
public class Counter extends GamlObject {
private int value;

public Counter(final IScope scope, final IClass clazz) {
super(clazz);
}

@getter("value")
public int getValue() { return value; }

@setter("value")
public void setValue(final int value) { this.value = value; }

@action(name = "increment", doc = @doc("Increment this counter."))
public Integer increment(final IScope scope) { return ++value; }
}
counter c <- counter();
counter initialized <- counter(value: 10);
int result <- initialized.increment();

class labeled_counter parent: counter {
string label <- "example";
}

Some important rules:

  • Call super(clazz) in the constructor. Do not use the old GamlObject(IScope, IClass, Map) constructor, and do not initialize GAML attributes in the constructor: the arguments, the init expressions and the default values are applied after construction, through the setters. A getter with initializer = true can give the initial value.
  • The GAML parent of the class is the nearest Java superclass that is also registered with @gaml_class. Otherwise it is object. Modelers can extend the class in GAML (class ... parent: counter).
  • An abstract Java class can be registered, but it cannot be instantiated.
  • The class can be used directly as parameter or return type of Java operators, without a separate @type.

A real example is gama.core/src/gama/core/util/CommandContext.java, which defines the class context used by the command operator:

@gaml_class (
name = CommandContext.CONTEXT,
concept = { IConcept.SYSTEM, IConcept.COMMUNICATION },
doc = @doc ("A persistent system shell. Commands run with command(string, context) share its state ..."))
@vars ({ @variable (name = CommandContext.DIRECTORY, type = IType.DIRECTORY, doc = @doc (...)),
... })
public class CommandContext extends GamlObject {
...
public CommandContext(final IScope scope, final IClass clazz) {
super(clazz);
}
...
}

See also Developing Skills, Developing Control Architectures, Developing Types and the index of annotations.